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