db.rs

  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}