1mod ids;
2mod queries;
3mod tables;
4#[cfg(test)]
5pub mod tests;
6
7use crate::{executor::Executor, Error, Result};
8use anyhow::anyhow;
9use collections::{BTreeMap, HashMap, HashSet};
10use dashmap::DashMap;
11use futures::StreamExt;
12use rand::{prelude::StdRng, Rng, SeedableRng};
13use rpc::{
14 proto::{self},
15 ConnectionId, ExtensionMetadata,
16};
17use sea_orm::{
18 entity::prelude::*,
19 sea_query::{Alias, Expr, OnConflict},
20 ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr,
21 FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
22 TransactionTrait,
23};
24use semantic_version::SemanticVersion;
25use serde::{Deserialize, Serialize};
26use sqlx::{
27 migrate::{Migrate, Migration, MigrationSource},
28 Connection,
29};
30use std::ops::RangeInclusive;
31use std::{
32 fmt::Write as _,
33 future::Future,
34 marker::PhantomData,
35 ops::{Deref, DerefMut},
36 path::Path,
37 rc::Rc,
38 sync::Arc,
39 time::Duration,
40};
41use time::PrimitiveDateTime;
42use tokio::sync::{Mutex, OwnedMutexGuard};
43
44#[cfg(test)]
45pub use tests::TestDb;
46
47pub use ids::*;
48pub use queries::billing_customers::CreateBillingCustomerParams;
49pub use queries::billing_subscriptions::CreateBillingSubscriptionParams;
50pub use queries::contributors::ContributorSelector;
51pub use sea_orm::ConnectOptions;
52pub use tables::user::Model as User;
53pub use tables::*;
54
55/// Database gives you a handle that lets you access the database.
56/// It handles pooling internally.
57pub struct Database {
58 options: ConnectOptions,
59 pool: DatabaseConnection,
60 rooms: DashMap<RoomId, Arc<Mutex<()>>>,
61 projects: DashMap<ProjectId, Arc<Mutex<()>>>,
62 rng: Mutex<StdRng>,
63 executor: Executor,
64 notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
65 notification_kinds_by_name: HashMap<String, NotificationKindId>,
66 #[cfg(test)]
67 runtime: Option<tokio::runtime::Runtime>,
68}
69
70// The `Database` type has so many methods that its impl blocks are split into
71// separate files in the `queries` folder.
72impl Database {
73 /// Connects to the database with the given options
74 pub async fn new(options: ConnectOptions, executor: Executor) -> Result<Self> {
75 sqlx::any::install_default_drivers();
76 Ok(Self {
77 options: options.clone(),
78 pool: sea_orm::Database::connect(options).await?,
79 rooms: DashMap::with_capacity(16384),
80 projects: DashMap::with_capacity(16384),
81 rng: Mutex::new(StdRng::seed_from_u64(0)),
82 notification_kinds_by_id: HashMap::default(),
83 notification_kinds_by_name: HashMap::default(),
84 executor,
85 #[cfg(test)]
86 runtime: None,
87 })
88 }
89
90 #[cfg(test)]
91 pub fn reset(&self) {
92 self.rooms.clear();
93 self.projects.clear();
94 }
95
96 /// Runs the database migrations.
97 pub async fn migrate(
98 &self,
99 migrations_path: &Path,
100 ignore_checksum_mismatch: bool,
101 ) -> anyhow::Result<Vec<(Migration, Duration)>> {
102 let migrations = MigrationSource::resolve(migrations_path)
103 .await
104 .map_err(|err| anyhow!("failed to load migrations: {err:?}"))?;
105
106 let mut connection = sqlx::AnyConnection::connect(self.options.get_url()).await?;
107
108 connection.ensure_migrations_table().await?;
109 let applied_migrations: HashMap<_, _> = connection
110 .list_applied_migrations()
111 .await?
112 .into_iter()
113 .map(|m| (m.version, m))
114 .collect();
115
116 let mut new_migrations = Vec::new();
117 for migration in migrations {
118 match applied_migrations.get(&migration.version) {
119 Some(applied_migration) => {
120 if migration.checksum != applied_migration.checksum && !ignore_checksum_mismatch
121 {
122 Err(anyhow!(
123 "checksum mismatch for applied migration {}",
124 migration.description
125 ))?;
126 }
127 }
128 None => {
129 let elapsed = connection.apply(&migration).await?;
130 new_migrations.push((migration, elapsed));
131 }
132 }
133 }
134
135 Ok(new_migrations)
136 }
137
138 /// Transaction runs things in a transaction. If you want to call other methods
139 /// and pass the transaction around you need to reborrow the transaction at each
140 /// call site with: `&*tx`.
141 pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
142 where
143 F: Send + Fn(TransactionHandle) -> Fut,
144 Fut: Send + Future<Output = Result<T>>,
145 {
146 let body = async {
147 let mut i = 0;
148 loop {
149 let (tx, result) = self.with_transaction(&f).await?;
150 match result {
151 Ok(result) => match tx.commit().await.map_err(Into::into) {
152 Ok(()) => return Ok(result),
153 Err(error) => {
154 if !self.retry_on_serialization_error(&error, i).await {
155 return Err(error);
156 }
157 }
158 },
159 Err(error) => {
160 tx.rollback().await?;
161 if !self.retry_on_serialization_error(&error, i).await {
162 return Err(error);
163 }
164 }
165 }
166 i += 1;
167 }
168 };
169
170 self.run(body).await
171 }
172
173 pub async fn weak_transaction<F, Fut, T>(&self, f: F) -> Result<T>
174 where
175 F: Send + Fn(TransactionHandle) -> Fut,
176 Fut: Send + Future<Output = Result<T>>,
177 {
178 let body = async {
179 let (tx, result) = self.with_weak_transaction(&f).await?;
180 match result {
181 Ok(result) => match tx.commit().await.map_err(Into::into) {
182 Ok(()) => return Ok(result),
183 Err(error) => {
184 return Err(error);
185 }
186 },
187 Err(error) => {
188 tx.rollback().await?;
189 return Err(error);
190 }
191 }
192 };
193
194 self.run(body).await
195 }
196
197 /// The same as room_transaction, but if you need to only optionally return a Room.
198 async fn optional_room_transaction<F, Fut, T>(
199 &self,
200 f: F,
201 ) -> Result<Option<TransactionGuard<T>>>
202 where
203 F: Send + Fn(TransactionHandle) -> Fut,
204 Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
205 {
206 let body = async {
207 let mut i = 0;
208 loop {
209 let (tx, result) = self.with_transaction(&f).await?;
210 match result {
211 Ok(Some((room_id, data))) => {
212 let lock = self.rooms.entry(room_id).or_default().clone();
213 let _guard = lock.lock_owned().await;
214 match tx.commit().await.map_err(Into::into) {
215 Ok(()) => {
216 return Ok(Some(TransactionGuard {
217 data,
218 _guard,
219 _not_send: PhantomData,
220 }));
221 }
222 Err(error) => {
223 if !self.retry_on_serialization_error(&error, i).await {
224 return Err(error);
225 }
226 }
227 }
228 }
229 Ok(None) => match tx.commit().await.map_err(Into::into) {
230 Ok(()) => return Ok(None),
231 Err(error) => {
232 if !self.retry_on_serialization_error(&error, i).await {
233 return Err(error);
234 }
235 }
236 },
237 Err(error) => {
238 tx.rollback().await?;
239 if !self.retry_on_serialization_error(&error, i).await {
240 return Err(error);
241 }
242 }
243 }
244 i += 1;
245 }
246 };
247
248 self.run(body).await
249 }
250
251 async fn project_transaction<F, Fut, T>(
252 &self,
253 project_id: ProjectId,
254 f: F,
255 ) -> Result<TransactionGuard<T>>
256 where
257 F: Send + Fn(TransactionHandle) -> Fut,
258 Fut: Send + Future<Output = Result<T>>,
259 {
260 let room_id = Database::room_id_for_project(&self, project_id).await?;
261 let body = async {
262 let mut i = 0;
263 loop {
264 let lock = if let Some(room_id) = room_id {
265 self.rooms.entry(room_id).or_default().clone()
266 } else {
267 self.projects.entry(project_id).or_default().clone()
268 };
269 let _guard = lock.lock_owned().await;
270 let (tx, result) = self.with_transaction(&f).await?;
271 match result {
272 Ok(data) => match tx.commit().await.map_err(Into::into) {
273 Ok(()) => {
274 return Ok(TransactionGuard {
275 data,
276 _guard,
277 _not_send: PhantomData,
278 });
279 }
280 Err(error) => {
281 if !self.retry_on_serialization_error(&error, i).await {
282 return Err(error);
283 }
284 }
285 },
286 Err(error) => {
287 tx.rollback().await?;
288 if !self.retry_on_serialization_error(&error, i).await {
289 return Err(error);
290 }
291 }
292 }
293 i += 1;
294 }
295 };
296
297 self.run(body).await
298 }
299
300 /// room_transaction runs the block in a transaction. It returns a RoomGuard, that keeps
301 /// the database locked until it is dropped. This ensures that updates sent to clients are
302 /// properly serialized with respect to database changes.
303 async fn room_transaction<F, Fut, T>(
304 &self,
305 room_id: RoomId,
306 f: F,
307 ) -> Result<TransactionGuard<T>>
308 where
309 F: Send + Fn(TransactionHandle) -> Fut,
310 Fut: Send + Future<Output = Result<T>>,
311 {
312 let body = async {
313 let mut i = 0;
314 loop {
315 let lock = self.rooms.entry(room_id).or_default().clone();
316 let _guard = lock.lock_owned().await;
317 let (tx, result) = self.with_transaction(&f).await?;
318 match result {
319 Ok(data) => match tx.commit().await.map_err(Into::into) {
320 Ok(()) => {
321 return Ok(TransactionGuard {
322 data,
323 _guard,
324 _not_send: PhantomData,
325 });
326 }
327 Err(error) => {
328 if !self.retry_on_serialization_error(&error, i).await {
329 return Err(error);
330 }
331 }
332 },
333 Err(error) => {
334 tx.rollback().await?;
335 if !self.retry_on_serialization_error(&error, i).await {
336 return Err(error);
337 }
338 }
339 }
340 i += 1;
341 }
342 };
343
344 self.run(body).await
345 }
346
347 async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
348 where
349 F: Send + Fn(TransactionHandle) -> Fut,
350 Fut: Send + Future<Output = Result<T>>,
351 {
352 let tx = self
353 .pool
354 .begin_with_config(Some(IsolationLevel::Serializable), None)
355 .await?;
356
357 let mut tx = Arc::new(Some(tx));
358 let result = f(TransactionHandle(tx.clone())).await;
359 let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
360 return Err(anyhow!(
361 "couldn't complete transaction because it's still in use"
362 ))?;
363 };
364
365 Ok((tx, result))
366 }
367
368 async fn with_weak_transaction<F, Fut, T>(
369 &self,
370 f: &F,
371 ) -> Result<(DatabaseTransaction, Result<T>)>
372 where
373 F: Send + Fn(TransactionHandle) -> Fut,
374 Fut: Send + Future<Output = Result<T>>,
375 {
376 let tx = self
377 .pool
378 .begin_with_config(Some(IsolationLevel::ReadCommitted), None)
379 .await?;
380
381 let mut tx = Arc::new(Some(tx));
382 let result = f(TransactionHandle(tx.clone())).await;
383 let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
384 return Err(anyhow!(
385 "couldn't complete transaction because it's still in use"
386 ))?;
387 };
388
389 Ok((tx, result))
390 }
391
392 async fn run<F, T>(&self, future: F) -> Result<T>
393 where
394 F: Future<Output = Result<T>>,
395 {
396 #[cfg(test)]
397 {
398 if let Executor::Deterministic(executor) = &self.executor {
399 executor.simulate_random_delay().await;
400 }
401
402 self.runtime.as_ref().unwrap().block_on(future)
403 }
404
405 #[cfg(not(test))]
406 {
407 future.await
408 }
409 }
410
411 async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: usize) -> bool {
412 // If the error is due to a failure to serialize concurrent transactions, then retry
413 // this transaction after a delay. With each subsequent retry, double the delay duration.
414 // Also vary the delay randomly in order to ensure different database connections retry
415 // at different times.
416 const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.];
417 if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() {
418 let base_delay = SLEEPS[prev_attempt_count];
419 let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0);
420 log::warn!(
421 "retrying transaction after serialization error. delay: {} ms.",
422 randomized_delay
423 );
424 self.executor
425 .sleep(Duration::from_millis(randomized_delay as u64))
426 .await;
427 true
428 } else {
429 false
430 }
431 }
432}
433
434fn is_serialization_error(error: &Error) -> bool {
435 const SERIALIZATION_FAILURE_CODE: &str = "40001";
436 match error {
437 Error::Database(
438 DbErr::Exec(sea_orm::RuntimeErr::SqlxError(error))
439 | DbErr::Query(sea_orm::RuntimeErr::SqlxError(error)),
440 ) if error
441 .as_database_error()
442 .and_then(|error| error.code())
443 .as_deref()
444 == Some(SERIALIZATION_FAILURE_CODE) =>
445 {
446 true
447 }
448 _ => false,
449 }
450}
451
452/// A handle to a [`DatabaseTransaction`].
453pub struct TransactionHandle(Arc<Option<DatabaseTransaction>>);
454
455impl Deref for TransactionHandle {
456 type Target = DatabaseTransaction;
457
458 fn deref(&self) -> &Self::Target {
459 self.0.as_ref().as_ref().unwrap()
460 }
461}
462
463/// [`TransactionGuard`] keeps a database transaction alive until it is dropped.
464/// It wraps data that depends on the state of the database and prevents an additional
465/// transaction from starting that would invalidate that data.
466pub struct TransactionGuard<T> {
467 data: T,
468 _guard: OwnedMutexGuard<()>,
469 _not_send: PhantomData<Rc<()>>,
470}
471
472impl<T> Deref for TransactionGuard<T> {
473 type Target = T;
474
475 fn deref(&self) -> &T {
476 &self.data
477 }
478}
479
480impl<T> DerefMut for TransactionGuard<T> {
481 fn deref_mut(&mut self) -> &mut T {
482 &mut self.data
483 }
484}
485
486impl<T> TransactionGuard<T> {
487 /// Returns the inner value of the guard.
488 pub fn into_inner(self) -> T {
489 self.data
490 }
491}
492
493#[derive(Clone, Debug, PartialEq, Eq)]
494pub enum Contact {
495 Accepted { user_id: UserId, busy: bool },
496 Outgoing { user_id: UserId },
497 Incoming { user_id: UserId },
498}
499
500impl Contact {
501 pub fn user_id(&self) -> UserId {
502 match self {
503 Contact::Accepted { user_id, .. } => *user_id,
504 Contact::Outgoing { user_id } => *user_id,
505 Contact::Incoming { user_id, .. } => *user_id,
506 }
507 }
508}
509
510pub type NotificationBatch = Vec<(UserId, proto::Notification)>;
511
512pub struct CreatedChannelMessage {
513 pub message_id: MessageId,
514 pub participant_connection_ids: HashSet<ConnectionId>,
515 pub notifications: NotificationBatch,
516}
517
518pub struct UpdatedChannelMessage {
519 pub message_id: MessageId,
520 pub participant_connection_ids: Vec<ConnectionId>,
521 pub notifications: NotificationBatch,
522 pub reply_to_message_id: Option<MessageId>,
523 pub timestamp: PrimitiveDateTime,
524 pub deleted_mention_notification_ids: Vec<NotificationId>,
525 pub updated_mention_notifications: Vec<rpc::proto::Notification>,
526}
527
528#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
529pub struct Invite {
530 pub email_address: String,
531 pub email_confirmation_code: String,
532}
533
534#[derive(Clone, Debug, Deserialize)]
535pub struct NewSignup {
536 pub email_address: String,
537 pub platform_mac: bool,
538 pub platform_windows: bool,
539 pub platform_linux: bool,
540 pub editor_features: Vec<String>,
541 pub programming_languages: Vec<String>,
542 pub device_id: Option<String>,
543 pub added_to_mailing_list: bool,
544 pub created_at: Option<DateTime>,
545}
546
547#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromQueryResult)]
548pub struct WaitlistSummary {
549 pub count: i64,
550 pub linux_count: i64,
551 pub mac_count: i64,
552 pub windows_count: i64,
553 pub unknown_count: i64,
554}
555
556/// The parameters to create a new user.
557#[derive(Debug, Serialize, Deserialize)]
558pub struct NewUserParams {
559 pub github_login: String,
560 pub github_user_id: i32,
561}
562
563/// The result of creating a new user.
564#[derive(Debug)]
565pub struct NewUserResult {
566 pub user_id: UserId,
567 pub metrics_id: String,
568 pub inviting_user_id: Option<UserId>,
569 pub signup_device_id: Option<String>,
570}
571
572/// The result of updating a channel membership.
573#[derive(Debug)]
574pub struct MembershipUpdated {
575 pub channel_id: ChannelId,
576 pub new_channels: ChannelsForUser,
577 pub removed_channels: Vec<ChannelId>,
578}
579
580/// The result of setting a member's role.
581#[derive(Debug)]
582#[allow(clippy::large_enum_variant)]
583pub enum SetMemberRoleResult {
584 InviteUpdated(Channel),
585 MembershipUpdated(MembershipUpdated),
586}
587
588/// The result of inviting a member to a channel.
589#[derive(Debug)]
590pub struct InviteMemberResult {
591 pub channel: Channel,
592 pub notifications: NotificationBatch,
593}
594
595#[derive(Debug)]
596pub struct RespondToChannelInvite {
597 pub membership_update: Option<MembershipUpdated>,
598 pub notifications: NotificationBatch,
599}
600
601#[derive(Debug)]
602pub struct RemoveChannelMemberResult {
603 pub membership_update: MembershipUpdated,
604 pub notification_id: Option<NotificationId>,
605}
606
607#[derive(Debug, PartialEq, Eq, Hash)]
608pub struct Channel {
609 pub id: ChannelId,
610 pub name: String,
611 pub visibility: ChannelVisibility,
612 /// parent_path is the channel ids from the root to this one (not including this one)
613 pub parent_path: Vec<ChannelId>,
614}
615
616impl Channel {
617 pub fn from_model(value: channel::Model) -> Self {
618 Channel {
619 id: value.id,
620 visibility: value.visibility,
621 name: value.clone().name,
622 parent_path: value.ancestors().collect(),
623 }
624 }
625
626 pub fn to_proto(&self) -> proto::Channel {
627 proto::Channel {
628 id: self.id.to_proto(),
629 name: self.name.clone(),
630 visibility: self.visibility.into(),
631 parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
632 }
633 }
634}
635
636#[derive(Debug, PartialEq, Eq, Hash)]
637pub struct ChannelMember {
638 pub role: ChannelRole,
639 pub user_id: UserId,
640 pub kind: proto::channel_member::Kind,
641}
642
643impl ChannelMember {
644 pub fn to_proto(&self) -> proto::ChannelMember {
645 proto::ChannelMember {
646 role: self.role.into(),
647 user_id: self.user_id.to_proto(),
648 kind: self.kind.into(),
649 }
650 }
651}
652
653#[derive(Debug, PartialEq)]
654pub struct ChannelsForUser {
655 pub channels: Vec<Channel>,
656 pub channel_memberships: Vec<channel_member::Model>,
657 pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
658 pub hosted_projects: Vec<proto::HostedProject>,
659 pub invited_channels: Vec<Channel>,
660
661 pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
662 pub observed_channel_messages: Vec<proto::ChannelMessageId>,
663 pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
664 pub latest_channel_messages: Vec<proto::ChannelMessageId>,
665}
666
667#[derive(Debug)]
668pub struct RejoinedChannelBuffer {
669 pub buffer: proto::RejoinedChannelBuffer,
670 pub old_connection_id: ConnectionId,
671}
672
673#[derive(Clone)]
674pub struct JoinRoom {
675 pub room: proto::Room,
676 pub channel: Option<channel::Model>,
677}
678
679pub struct RejoinedRoom {
680 pub room: proto::Room,
681 pub rejoined_projects: Vec<RejoinedProject>,
682 pub reshared_projects: Vec<ResharedProject>,
683 pub channel: Option<channel::Model>,
684}
685
686pub struct ResharedProject {
687 pub id: ProjectId,
688 pub old_connection_id: ConnectionId,
689 pub collaborators: Vec<ProjectCollaborator>,
690 pub worktrees: Vec<proto::WorktreeMetadata>,
691}
692
693pub struct RejoinedProject {
694 pub id: ProjectId,
695 pub old_connection_id: ConnectionId,
696 pub collaborators: Vec<ProjectCollaborator>,
697 pub worktrees: Vec<RejoinedWorktree>,
698 pub language_servers: Vec<proto::LanguageServer>,
699}
700
701impl RejoinedProject {
702 pub fn to_proto(&self) -> proto::RejoinedProject {
703 proto::RejoinedProject {
704 id: self.id.to_proto(),
705 worktrees: self
706 .worktrees
707 .iter()
708 .map(|worktree| proto::WorktreeMetadata {
709 id: worktree.id,
710 root_name: worktree.root_name.clone(),
711 visible: worktree.visible,
712 abs_path: worktree.abs_path.clone(),
713 })
714 .collect(),
715 collaborators: self
716 .collaborators
717 .iter()
718 .map(|collaborator| collaborator.to_proto())
719 .collect(),
720 language_servers: self.language_servers.clone(),
721 }
722 }
723}
724
725#[derive(Debug)]
726pub struct RejoinedWorktree {
727 pub id: u64,
728 pub abs_path: String,
729 pub root_name: String,
730 pub visible: bool,
731 pub updated_entries: Vec<proto::Entry>,
732 pub removed_entries: Vec<u64>,
733 pub updated_repositories: Vec<proto::RepositoryEntry>,
734 pub removed_repositories: Vec<u64>,
735 pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
736 pub settings_files: Vec<WorktreeSettingsFile>,
737 pub scan_id: u64,
738 pub completed_scan_id: u64,
739}
740
741pub struct LeftRoom {
742 pub room: proto::Room,
743 pub channel: Option<channel::Model>,
744 pub left_projects: HashMap<ProjectId, LeftProject>,
745 pub canceled_calls_to_user_ids: Vec<UserId>,
746 pub deleted: bool,
747}
748
749pub struct RefreshedRoom {
750 pub room: proto::Room,
751 pub channel: Option<channel::Model>,
752 pub stale_participant_user_ids: Vec<UserId>,
753 pub canceled_calls_to_user_ids: Vec<UserId>,
754}
755
756pub struct RefreshedChannelBuffer {
757 pub connection_ids: Vec<ConnectionId>,
758 pub collaborators: Vec<proto::Collaborator>,
759}
760
761pub struct Project {
762 pub id: ProjectId,
763 pub role: ChannelRole,
764 pub collaborators: Vec<ProjectCollaborator>,
765 pub worktrees: BTreeMap<u64, Worktree>,
766 pub language_servers: Vec<proto::LanguageServer>,
767 pub dev_server_project_id: Option<DevServerProjectId>,
768}
769
770pub struct ProjectCollaborator {
771 pub connection_id: ConnectionId,
772 pub user_id: UserId,
773 pub replica_id: ReplicaId,
774 pub is_host: bool,
775}
776
777impl ProjectCollaborator {
778 pub fn to_proto(&self) -> proto::Collaborator {
779 proto::Collaborator {
780 peer_id: Some(self.connection_id.into()),
781 replica_id: self.replica_id.0 as u32,
782 user_id: self.user_id.to_proto(),
783 }
784 }
785}
786
787#[derive(Debug)]
788pub struct LeftProject {
789 pub id: ProjectId,
790 pub should_unshare: bool,
791 pub connection_ids: Vec<ConnectionId>,
792}
793
794pub struct Worktree {
795 pub id: u64,
796 pub abs_path: String,
797 pub root_name: String,
798 pub visible: bool,
799 pub entries: Vec<proto::Entry>,
800 pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
801 pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
802 pub settings_files: Vec<WorktreeSettingsFile>,
803 pub scan_id: u64,
804 pub completed_scan_id: u64,
805}
806
807#[derive(Debug)]
808pub struct WorktreeSettingsFile {
809 pub path: String,
810 pub content: String,
811}
812
813pub struct NewExtensionVersion {
814 pub name: String,
815 pub version: semver::Version,
816 pub description: String,
817 pub authors: Vec<String>,
818 pub repository: String,
819 pub schema_version: i32,
820 pub wasm_api_version: Option<String>,
821 pub published_at: PrimitiveDateTime,
822}
823
824pub struct ExtensionVersionConstraints {
825 pub schema_versions: RangeInclusive<i32>,
826 pub wasm_api_versions: RangeInclusive<SemanticVersion>,
827}