From 902b8a317a0874a8f12a63fb364bc676f8f45dfd Mon Sep 17 00:00:00 2001 From: dawn Date: Thu, 6 Aug 2026 11:26:23 +0300 Subject: [PATCH] bobbin/xrpc: offset param and total output on list endpoints Signed-off-by: dawn --- bobbin/crates/xrpc/src/feed.rs | 2 +- bobbin/crates/xrpc/src/filter.rs | 97 ++--- bobbin/crates/xrpc/src/lib.rs | 125 ++++-- bobbin/crates/xrpc/tests/aggregation.rs | 363 ++++++++++++++++++ bobbin/crates/xrpc/tests/mirror_proxy.rs | 1 + bobbin/crates/xrpc/tests/search.rs | 70 ++++ lexicons/feed/listComments.json | 11 +- lexicons/feed/listCommentsBy.json | 11 +- lexicons/feed/listReactions.json | 11 +- lexicons/feed/listReactionsBy.json | 11 +- lexicons/feed/listStars.json | 11 +- lexicons/feed/listStarsBy.json | 11 +- lexicons/git/listRefUpdates.json | 11 +- lexicons/git/listRefUpdatesBy.json | 11 +- lexicons/graph/listFollows.json | 11 +- lexicons/graph/listFollowsBy.json | 11 +- lexicons/graph/listVouches.json | 11 +- lexicons/graph/listVouchesBy.json | 11 +- lexicons/knot/listKnots.json | 11 +- lexicons/knot/listMembers.json | 11 +- lexicons/knot/listMembersBy.json | 11 +- lexicons/label/listDefinitions.json | 11 +- lexicons/label/listOps.json | 11 +- lexicons/label/listOpsBy.json | 11 +- lexicons/pipeline/listPipelines.json | 11 +- lexicons/pipeline/listPipelinesBy.json | 11 +- lexicons/pipeline/listStatuses.json | 11 +- lexicons/pipeline/listStatusesBy.json | 11 +- lexicons/publicKey/listKeys.json | 11 +- lexicons/repo/listArtifacts.json | 11 +- lexicons/repo/listArtifactsBy.json | 11 +- lexicons/repo/listCollaborators.json | 11 +- lexicons/repo/listCollaboratorsBy.json | 11 +- lexicons/repo/listIssues.json | 9 + lexicons/repo/listIssuesBy.json | 9 + lexicons/repo/listPulls.json | 9 + lexicons/repo/listPullsBy.json | 9 + lexicons/repo/listRepos.json | 11 +- lexicons/search/query.json | 5 + lexicons/spindle/listMembers.json | 11 +- lexicons/spindle/listMembersBy.json | 11 +- lexicons/spindle/listSpindles.json | 11 +- lexicons/string/listStrings.json | 11 +- .../types/sh/tangled/feed/listComments.ts | 9 + .../types/sh/tangled/feed/listCommentsBy.ts | 9 + .../types/sh/tangled/feed/listReactions.ts | 9 + .../types/sh/tangled/feed/listReactionsBy.ts | 9 + .../types/sh/tangled/feed/listStars.ts | 9 + .../types/sh/tangled/feed/listStarsBy.ts | 9 + .../types/sh/tangled/git/listRefUpdates.ts | 9 + .../types/sh/tangled/git/listRefUpdatesBy.ts | 9 + .../types/sh/tangled/graph/listFollows.ts | 9 + .../types/sh/tangled/graph/listFollowsBy.ts | 9 + .../types/sh/tangled/graph/listVouches.ts | 9 + .../types/sh/tangled/graph/listVouchesBy.ts | 9 + .../types/sh/tangled/knot/listKnots.ts | 9 + .../types/sh/tangled/knot/listMembers.ts | 9 + .../types/sh/tangled/knot/listMembersBy.ts | 9 + .../types/sh/tangled/label/listDefinitions.ts | 9 + .../types/sh/tangled/label/listOps.ts | 9 + .../types/sh/tangled/label/listOpsBy.ts | 9 + .../sh/tangled/pipeline/listPipelines.ts | 9 + .../sh/tangled/pipeline/listPipelinesBy.ts | 9 + .../types/sh/tangled/pipeline/listStatuses.ts | 9 + .../sh/tangled/pipeline/listStatusesBy.ts | 9 + .../types/sh/tangled/publicKey/listKeys.ts | 9 + .../types/sh/tangled/repo/listArtifacts.ts | 9 + .../types/sh/tangled/repo/listArtifactsBy.ts | 9 + .../sh/tangled/repo/listCollaborators.ts | 9 + .../sh/tangled/repo/listCollaboratorsBy.ts | 9 + .../types/sh/tangled/repo/listIssues.ts | 9 + .../types/sh/tangled/repo/listIssuesBy.ts | 9 + .../types/sh/tangled/repo/listPulls.ts | 9 + .../types/sh/tangled/repo/listPullsBy.ts | 9 + .../types/sh/tangled/repo/listRepos.ts | 9 + .../lexicons/types/sh/tangled/search/query.ts | 5 + .../types/sh/tangled/spindle/listMembers.ts | 9 + .../types/sh/tangled/spindle/listMembersBy.ts | 9 + .../types/sh/tangled/spindle/listSpindles.ts | 9 + .../types/sh/tangled/string/listStrings.ts | 9 + 80 files changed, 1249 insertions(+), 131 deletions(-) diff --git a/bobbin/crates/xrpc/src/feed.rs b/bobbin/crates/xrpc/src/feed.rs index 76929ab55..782e742dd 100644 --- a/bobbin/crates/xrpc/src/feed.rs +++ b/bobbin/crates/xrpc/src/feed.rs @@ -84,7 +84,7 @@ pub(crate) async fn get_timeline( Ok(json_stream::, _>( "feed", items, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } diff --git a/bobbin/crates/xrpc/src/filter.rs b/bobbin/crates/xrpc/src/filter.rs index 9ead9cb1c..c8d0529be 100644 --- a/bobbin/crates/xrpc/src/filter.rs +++ b/bobbin/crates/xrpc/src/filter.rs @@ -1,20 +1,29 @@ -use std::sync::Arc; - -use bobbin_edge_index::{FilteredCount, IssueStateKind, PullStatusKind, StateIndex}; -use bobbin_types::ids::{EdgeKey, SubjectRef}; +use bobbin_edge_index::{FilteredCount, IssueStateKind, PullStatusKind}; +use bobbin_types::ids::EdgeKey; use jacquard_common::DefaultStr; use jacquard_common::types::did::Did; -use jacquard_common::types::string::AtUri; use serde::Deserialize; use crate::AppState; -use crate::{accept_state_source, at_uri_owned_by, source_authority_did}; -pub type FilterPredicate = Box) -> bool + Send + Sync + 'static>; +// kind is None for author-only queries which hit the any-kind view +#[derive(Clone, Debug)] +pub enum StateViewQuery { + Issue { + kind: Option, + author: Option>, + }, + Pull { + kind: Option, + author: Option>, + }, +} pub trait ListFilter: serde::de::DeserializeOwned + Send + Sync + 'static { - fn predicate(&self, state: &AppState, subject: &SubjectRef) -> FilterPredicate; fn is_identity(&self) -> bool; + + // non-identity filters must resolve to a view or xrpc returns an error + fn state_view(&self) -> Option; } pub trait CountFilter: ListFilter { @@ -25,13 +34,13 @@ pub trait CountFilter: ListFilter { pub struct NoFilter; impl ListFilter for NoFilter { - fn predicate(&self, _: &AppState, _: &SubjectRef) -> FilterPredicate { - Box::new(|_| true) - } - fn is_identity(&self) -> bool { true } + + fn state_view(&self) -> Option { + None + } } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq)] @@ -57,18 +66,16 @@ pub struct IssueFilter { } impl ListFilter for IssueFilter { - fn predicate(&self, state: &AppState, subject: &SubjectRef) -> FilterPredicate { - compose_state_filter::( - self.author.clone(), - self.state.map(Into::into), - state.issue_states.clone(), - subject.as_did().cloned(), - ) - } - fn is_identity(&self) -> bool { self.author.is_none() && self.state.is_none() } + + fn state_view(&self) -> Option { + (!self.is_identity()).then(|| StateViewQuery::Issue { + kind: self.state.map(Into::into), + author: self.author.clone(), + }) + } } impl CountFilter for IssueFilter { @@ -107,18 +114,16 @@ pub struct PullFilter { } impl ListFilter for PullFilter { - fn predicate(&self, state: &AppState, subject: &SubjectRef) -> FilterPredicate { - compose_state_filter::( - self.author.clone(), - self.status.map(Into::into), - state.pull_statuses.clone(), - subject.as_did().cloned(), - ) - } - fn is_identity(&self) -> bool { self.author.is_none() && self.status.is_none() } + + fn state_view(&self) -> Option { + (!self.is_identity()).then(|| StateViewQuery::Pull { + kind: self.status.map(Into::into), + author: self.author.clone(), + }) + } } impl CountFilter for PullFilter { @@ -148,35 +153,3 @@ fn count_by_author( ), } } - -fn compose_state_filter( - author: Option>, - want: Option, - index: Arc>, - repo_owner: Option>, -) -> FilterPredicate -where - K: bobbin_edge_index::StateKind + Default + 'static, -{ - Box::new(move |uri| { - if let Some(a) = &author - && !at_uri_owned_by(uri, a) - { - return false; - } - let Some(want) = want else { - return true; - }; - let Some(repo_owner) = repo_owner.as_ref() else { - return false; - }; - let entity_author = source_authority_did(uri); - let accept = - |src: &AtUri| accept_state_source(src, entity_author.as_ref(), repo_owner); - let effective = index - .latest_by(uri, accept) - .map(|(k, _)| k) - .unwrap_or_default(); - effective == want - }) -} diff --git a/bobbin/crates/xrpc/src/lib.rs b/bobbin/crates/xrpc/src/lib.rs index 162998fd7..bcd591bca 100644 --- a/bobbin/crates/xrpc/src/lib.rs +++ b/bobbin/crates/xrpc/src/lib.rs @@ -24,7 +24,8 @@ use axum::{ }; use bobbin_edge_index::{ Coverage, CoverageWatch, CursorParseError, EdgeItem, EdgePage, EdgeStore, IssueStateKind, - PageCursor, PageLimit, PageToken, PullStatusKind, SortDir, StateIndex, StateKind, + PageCursor, PageLimit, PageOffset, PageStart, PageToken, PullStatusKind, SortDir, StateIndex, + StateKind, TotalCount, }; use bobbin_knot_proxy::{ KnotHost, KnotProxy, KnotProxyError, MirrorNsid, MirrorProxy, ProxyResponse, RepoSlug, @@ -113,7 +114,7 @@ pub use backpressure::{ }; use client_address::X_FORWARDED_FOR; pub use client_address::{ClientAddress, SocketPeer}; -use filter::{CountFilter, IssueFilter, ListFilter, NoFilter, PullFilter}; +use filter::{CountFilter, IssueFilter, ListFilter, NoFilter, PullFilter, StateViewQuery}; use trusted_proxies::TrustedProxies; const DEFAULT_LIMIT: u32 = 50; @@ -714,6 +715,7 @@ impl From for SortDir { struct TypedListQuery { subject: SubjectQuery, cursor: Option, + offset: Option, limit: Option, #[serde(default)] order: Order, @@ -754,6 +756,7 @@ struct SearchQueryParams { since: Option, until: Option, cursor: Option, + offset: Option, limit: Option, } @@ -1157,18 +1160,26 @@ pub(crate) fn parse_cursor(raw: Option<&str>) -> Result { .map_err(|e: CursorParseError| XrpcError::InvalidParams(format!("cursor: {e}"))) } +// cursor and offset are mutually exclusive, offset pages return a cursor for follow-up +pub(crate) fn parse_start( + cursor: Option<&str>, + offset: Option, +) -> Result { + match (cursor, offset) { + (Some(_), Some(_)) => Err(XrpcError::InvalidParams( + "cursor and offset are mutually exclusive".into(), + )), + (Some(c), None) => Ok(PageStart::Cursor(parse_cursor(Some(c))?)), + (None, Some(o)) => Ok(PageStart::Offset(o)), + (None, None) => Ok(PageStart::Cursor(PageCursor::Start)), + } +} + pub(crate) fn parse_limit(raw: Option) -> Result { PageLimit::new(raw.unwrap_or(DEFAULT_LIMIT)) .map_err(|e| XrpcError::InvalidParams(format!("limit: {e}"))) } -pub(crate) fn at_uri_owned_by(uri: &AtUri, author: &Did) -> bool { - match uri.authority() { - AtIdentifier::Did(d) => d.as_ref() == author.as_ref(), - AtIdentifier::Handle(_) => false, - } -} - async fn resolve_for_view( state: &AppState, expected_nsid: &Nsid, @@ -1722,9 +1733,12 @@ struct PageState { permit: Option, } -pub(crate) fn paged_tail(cursor: Option) -> Vec { +pub(crate) fn paged_tail(cursor: Option, total: Option) -> Vec { let encoded = serde_json::to_string(&cursor).unwrap_or_else(|_| "null".to_owned()); - format!("],\"cursor\":{encoded}}}").into_bytes() + match total { + Some(t) => format!("],\"cursor\":{encoded},\"total\":{}}}", t.get()).into_bytes(), + None => format!("],\"cursor\":{encoded}}}").into_bytes(), + } } fn unpaged_tail() -> Vec { @@ -1862,17 +1876,32 @@ where F: ListFilter, { let subject = parse_subject(&q.subject, R::SHAPE)?; - let cursor = parse_cursor(q.cursor.as_deref())?; + let start = parse_start(q.cursor.as_deref(), q.offset)?; let limit = parse_limit(q.limit)?; let dir = q.dir(); let nsid = nsid_static(R::NSID); - let page = if q.filter.is_identity() { - let key = EdgeKey::new(nsid.clone(), subject); - state.edges.list(&key, cursor, limit, dir) - } else { - let pred = q.filter.predicate(state, &subject); - let key = EdgeKey::new(nsid.clone(), subject); - state.edges.list_filtered(&key, cursor, limit, dir, pred) + let page = match (q.filter.is_identity(), q.filter.state_view()) { + (true, _) => { + let key = EdgeKey::new(nsid.clone(), subject); + state.edges.list(&key, start, limit, dir) + } + (_, Some(StateViewQuery::Issue { kind, author })) => { + let key = EdgeKey::new(nsid.clone(), subject); + state + .edges + .page_issue_state(&key, kind, start, limit, dir, author.as_ref()) + } + (_, Some(StateViewQuery::Pull { kind, author })) => { + let key = EdgeKey::new(nsid.clone(), subject); + state + .edges + .page_pull_status(&key, kind, start, limit, dir, author.as_ref()) + } + (_, None) => { + return Err(XrpcError::Internal( + "non-identity filter did not resolve to a state view".into(), + )); + } }; Ok((page, nsid)) } @@ -1892,7 +1921,7 @@ where Ok(json_stream::, _>( "items", views, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } @@ -1949,17 +1978,32 @@ where F: ListFilter, { let subject = parse_subject(&q.subject, M::SHAPE)?; - let cursor = parse_cursor(q.cursor.as_deref())?; + let start = parse_start(q.cursor.as_deref(), q.offset)?; let limit = parse_limit(q.limit)?; let dir = q.dir(); let edge_nsid = nsid_static(M::EDGE_KIND); - let page = if q.filter.is_identity() { - let key = EdgeKey::new(edge_nsid, subject); - state.edges.list(&key, cursor, limit, dir) - } else { - let pred = q.filter.predicate(state, &subject); - let key = EdgeKey::new(edge_nsid, subject); - state.edges.list_filtered(&key, cursor, limit, dir, pred) + let page = match (q.filter.is_identity(), q.filter.state_view()) { + (true, _) => { + let key = EdgeKey::new(edge_nsid, subject); + state.edges.list(&key, start, limit, dir) + } + (_, Some(StateViewQuery::Issue { kind, author })) => { + let key = EdgeKey::new(edge_nsid, subject); + state + .edges + .page_issue_state(&key, kind, start, limit, dir, author.as_ref()) + } + (_, Some(StateViewQuery::Pull { kind, author })) => { + let key = EdgeKey::new(edge_nsid, subject); + state + .edges + .page_pull_status(&key, kind, start, limit, dir, author.as_ref()) + } + (_, None) => { + return Err(XrpcError::Internal( + "non-identity filter did not resolve to a state view".into(), + )); + } }; let record_nsid = nsid_static(::NSID); Ok((page, record_nsid)) @@ -1977,7 +2021,7 @@ where Ok(json_stream::, _>( "items", views, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } @@ -2050,7 +2094,7 @@ async fn list_issues( Ok(json_stream::>, _>( "items", items, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } @@ -2075,7 +2119,7 @@ async fn list_pulls( Ok(json_stream::>, _>( "items", items, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } @@ -2415,7 +2459,7 @@ async fn list_issues_by( Ok(json_stream::>, _>( "items", items, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } @@ -2465,7 +2509,7 @@ async fn list_pulls_by( Ok(json_stream::>, _>( "items", items, - paged_tail(page.next.map(PageToken::encode_token)), + paged_tail(page.next.map(PageToken::encode_token), page.total), permit, )) } @@ -2693,8 +2737,17 @@ async fn search_query( if q.q.trim().is_empty() { return Err(XrpcError::InvalidParams("q must not be empty".into())); } - let cursor = SearchCursor::from_token(q.cursor.as_deref()) - .map_err(|e| XrpcError::InvalidParams(format!("cursor: {e}")))?; + let cursor = match (q.cursor.as_deref(), q.offset) { + (Some(_), Some(_)) => { + return Err(XrpcError::InvalidParams( + "cursor and offset are mutually exclusive".into(), + )); + } + (Some(c), None) => SearchCursor::from_token(Some(c)) + .map_err(|e| XrpcError::InvalidParams(format!("cursor: {e}")))?, + (None, Some(o)) => SearchCursor::At(SearchOffset::new(o.get() as u32)), + (None, None) => SearchCursor::Start, + }; let limit = parse_limit(q.limit)?; let filters = build_search_filters(&q)?; let permit = state.heavy_permit()?; @@ -2717,7 +2770,7 @@ async fn search_query( Ok(json_stream::( "hits", hits, - paged_tail(next), + paged_tail(next, None), permit, )) } diff --git a/bobbin/crates/xrpc/tests/aggregation.rs b/bobbin/crates/xrpc/tests/aggregation.rs index c4f6a7fe1..b3b3ae044 100644 --- a/bobbin/crates/xrpc/tests/aggregation.rs +++ b/bobbin/crates/xrpc/tests/aggregation.rs @@ -598,6 +598,369 @@ async fn list_paginates_via_cursor() { assert_eq!(sorted.len(), owners.len(), "no duplicates across pages"); } +#[tokio::test] +async fn list_paginates_via_offset() { + let h = Harness::new().await; + let subject = at("at://did:plc:abalone"); + let repo = did("did:plc:abalone"); + let owners = [ + ("did:plc:nel", "i1"), + ("did:plc:olaren", "i2"), + ("did:plc:teq", "i3"), + ("did:plc:lyna", "i4"), + ("did:plc:bailey", "i5"), + ]; + stream::iter(owners) + .for_each(|(d, r)| { + let h = &h; + let subject = subject.clone(); + let repo = repo.clone(); + async move { + let d_did = did(d); + let rk = rkey(r); + h.add_edge( + &nsid("sh.tangled.repo.issue"), + &subject, + &at(&format!( + "at://{}/sh.tangled.repo.issue/{}", + d_did.as_ref(), + rk.as_ref() + )), + ); + h.mount( + &d_did, + &nsid("sh.tangled.repo.issue"), + &rk, + issue_body(&repo, &format!("issue-{}", rk.as_ref())), + ) + .await; + } + }) + .await; + + let app = router(h.state.clone()); + + // offset walk covers the same union as a cursor walk + let mut offset_items: Vec = Vec::new(); + for o in [0u64, 2, 4] { + let (_, page) = json_response( + app.clone() + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[("limit", "2"), ("offset", &o.to_string())], + )) + .await + .unwrap(), + ) + .await; + assert_eq!(page["total"], json!(5), "total rides every offset page"); + offset_items.extend(page["items"].as_array().unwrap().clone()); + } + let mut uris: Vec<&str> = offset_items + .iter() + .map(|item| item["uri"].as_str().unwrap()) + .collect(); + uris.sort(); + uris.dedup(); + assert_eq!(uris.len(), owners.len(), "offset walk covers every issue"); + + // an offset page's cursor continues keyset-style from the same position + let (_, page) = json_response( + app.clone() + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[("limit", "2"), ("offset", "2")], + )) + .await + .unwrap(), + ) + .await; + let cursor = page["cursor"] + .as_str() + .expect("offset page must yield a keyset cursor") + .to_owned(); + let (_, tail) = json_response( + app.clone() + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[("limit", "10"), ("cursor", &cursor)], + )) + .await + .unwrap(), + ) + .await; + let tail_uris: Vec<&str> = tail["items"] + .as_array() + .unwrap() + .iter() + .map(|item| item["uri"].as_str().unwrap()) + .collect(); + let offset_uris: Vec<&str> = offset_items[4..] + .iter() + .map(|item| item["uri"].as_str().unwrap()) + .collect(); + assert_eq!(tail_uris, offset_uris, "cursor continues after offset page"); + + // past-the-end offset is empty but keeps the total + let (_, page) = json_response( + app.oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[("offset", "5")], + )) + .await + .unwrap(), + ) + .await; + assert_eq!(page["items"].as_array().unwrap().len(), 0); + assert_eq!(page["total"], json!(5)); + assert!(page["cursor"].is_null()); +} + +#[tokio::test] +async fn list_filtered_paginates_via_offset_with_total() { + let h = Harness::new().await; + let subject = at("at://did:plc:abalone"); + let repo = did("did:plc:abalone"); + let owners = [ + ("did:plc:nel", "i1", IssueStateKind::Open), + ("did:plc:olaren", "i2", IssueStateKind::Closed), + ("did:plc:teq", "i3", IssueStateKind::Open), + ("did:plc:lyna", "i4", IssueStateKind::Closed), + ("did:plc:bailey", "i5", IssueStateKind::Open), + ]; + stream::iter(owners) + .for_each(|(d, r, state)| { + let h = &h; + let subject = subject.clone(); + let repo = repo.clone(); + async move { + let d_did = did(d); + let rk = rkey(r); + let uri = at(&format!( + "at://{}/sh.tangled.repo.issue/{}", + d_did.as_ref(), + rk.as_ref() + )); + h.add_edge(&nsid("sh.tangled.repo.issue"), &subject, &uri); + h.mount( + &d_did, + &nsid("sh.tangled.repo.issue"), + &rk, + issue_body(&repo, &format!("issue-{}", rk.as_ref())), + ) + .await; + if state == IssueStateKind::Closed { + h.upsert_issue_state( + at(&format!( + "at://{}/sh.tangled.repo.issue.state/s-{}", + repo.as_ref(), + rk.as_ref() + )), + uri, + next_sort_micros(), + IssueStateKind::Closed, + ); + } + } + }) + .await; + + let app = router(h.state.clone()); + + // the default-open view pages by rank and knows its total + let (_, page) = json_response( + app.clone() + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[("state", "open"), ("limit", "2"), ("offset", "2")], + )) + .await + .unwrap(), + ) + .await; + assert_eq!( + page["total"], + json!(3), + "open view counts state-less issues" + ); + let open_uris: Vec<&str> = page["items"] + .as_array() + .unwrap() + .iter() + .map(|item| item["uri"].as_str().unwrap()) + .collect(); + assert_eq!(open_uris.len(), 1, "third open issue is alone on its page"); + + let mut closed_uris: Vec = Vec::new(); + for o in [0u64, 1] { + let (_, page) = json_response( + app.clone() + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[ + ("state", "closed"), + ("limit", "1"), + ("offset", &o.to_string()), + ], + )) + .await + .unwrap(), + ) + .await; + assert_eq!( + page["total"], + json!(2), + ); + closed_uris.extend( + page["items"] + .as_array() + .unwrap() + .iter() + .map(|item| item["uri"].as_str().unwrap().to_owned()), + ); + } + assert_eq!( + closed_uris, + vec![ + "at://did:plc:lyna/sh.tangled.repo.issue/i4".to_owned(), + "at://did:plc:olaren/sh.tangled.repo.issue/i2".to_owned(), + ], + ); + + let (_, page) = json_response( + app.oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[ + ("state", "open"), + ("author", "did:plc:nel"), + ("limit", "5"), + ("offset", "0"), + ], + )) + .await + .unwrap(), + ) + .await; + assert_eq!(page["total"], json!(1)); + assert_eq!( + page["items"][0]["uri"], + json!("at://did:plc:nel/sh.tangled.repo.issue/i1"), + ); +} + +#[tokio::test] +async fn list_author_only_paginates_via_any_kind_view() { + let h = Harness::new().await; + let subject = at("at://did:plc:abalone"); + let repo = did("did:plc:abalone"); + let specs = [ + ("did:plc:nel", "i1", IssueStateKind::Open), + ("did:plc:nel", "i2", IssueStateKind::Closed), + ("did:plc:wren", "i3", IssueStateKind::Open), + ("did:plc:nel", "i4", IssueStateKind::Open), + ]; + stream::iter(specs) + .for_each(|(d, r, state)| { + let h = &h; + let subject = subject.clone(); + let repo = repo.clone(); + async move { + let d_did = did(d); + let rk = rkey(r); + let uri = at(&format!( + "at://{}/sh.tangled.repo.issue/{}", + d_did.as_ref(), + rk.as_ref() + )); + h.add_edge(&nsid("sh.tangled.repo.issue"), &subject, &uri); + h.mount( + &d_did, + &nsid("sh.tangled.repo.issue"), + &rk, + issue_body(&repo, &format!("issue-{}", rk.as_ref())), + ) + .await; + if state == IssueStateKind::Closed { + h.upsert_issue_state( + at(&format!( + "at://{}/sh.tangled.repo.issue.state/s-{}", + repo.as_ref(), + rk.as_ref() + )), + uri, + next_sort_micros(), + IssueStateKind::Closed, + ); + } + } + }) + .await; + + let app = router(h.state.clone()); + + // author-only: no state param, closed issue still shows, offset is exact + let mut uris: Vec = Vec::new(); + for o in [0u64, 2] { + let (_, page) = json_response( + app.clone() + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[ + ("author", "did:plc:nel"), + ("limit", "2"), + ("offset", &o.to_string()), + ], + )) + .await + .unwrap(), + ) + .await; + assert_eq!(page["total"], json!(3), "any-kind author view total"); + uris.extend( + page["items"] + .as_array() + .unwrap() + .iter() + .map(|item| item["uri"].as_str().unwrap().to_owned()), + ); + } + assert_eq!( + uris, + vec![ + "at://did:plc:nel/sh.tangled.repo.issue/i4".to_owned(), + "at://did:plc:nel/sh.tangled.repo.issue/i2".to_owned(), + "at://did:plc:nel/sh.tangled.repo.issue/i1".to_owned(), + ], + "nel's issues page newest-first across states", + ); +} + +#[tokio::test] +async fn list_rejects_cursor_and_offset_together() { + let h = Harness::new().await; + let subject = at("at://did:plc:abalone"); + let app = router(h.state.clone()); + let resp = app + .oneshot(list_request( + "sh.tangled.repo.listIssues", + subject.as_ref(), + &[("cursor", "000000000000000000000000"), ("offset", "2")], + )) + .await + .unwrap(); + let (status, body) = json_response(resp).await; + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(body["error"], "InvalidRequest"); +} + #[tokio::test] async fn pagination_unaffected_by_coverage_promotion() { let h = Harness::new().await; diff --git a/bobbin/crates/xrpc/tests/mirror_proxy.rs b/bobbin/crates/xrpc/tests/mirror_proxy.rs index 2a8e417a4..fd21ea614 100644 --- a/bobbin/crates/xrpc/tests/mirror_proxy.rs +++ b/bobbin/crates/xrpc/tests/mirror_proxy.rs @@ -115,6 +115,7 @@ impl Harness { Arc::new(SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, clock).unwrap()) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ) .with_mirror(mirror_proxy); diff --git a/bobbin/crates/xrpc/tests/search.rs b/bobbin/crates/xrpc/tests/search.rs index 513c8caa8..84487112b 100644 --- a/bobbin/crates/xrpc/tests/search.rs +++ b/bobbin/crates/xrpc/tests/search.rs @@ -392,6 +392,76 @@ async fn hit_set_stable_across_coverage_promotion() { assert_eq!(after_hits[0]["uri"], before_hits[0]["uri"]); } +#[tokio::test] +async fn pagination_offset_matches_cursor_walk() { + let h = Harness::new().await; + let names = ["nel", "olaren", "teq", "lyna", "bailey"]; + for (i, owner) in names.iter().enumerate() { + h.index_issue( + &did(&format!("did:plc:{owner}")), + &rkey(&format!("r{i}")), + "anemone tides", + "shell", + ) + .await; + } + let app = router(h.state.clone()); + + let mut walked: Vec = Vec::new(); + let mut cursor: Option = None; + loop { + let mut params = vec![("q", "anemone"), ("limit", "2")]; + if let Some(c) = &cursor { + params.push(("cursor", c)); + } + let (_, page) = json_response( + app.clone() + .oneshot(search_request(¶ms)) + .await + .unwrap(), + ) + .await; + walked.extend( + page["hits"] + .as_array() + .unwrap() + .iter() + .map(|hit| hit["uri"].as_str().unwrap().to_owned()), + ); + cursor = page["cursor"].as_str().map(str::to_owned); + if cursor.is_none() { + break; + } + } + assert_eq!(walked.len(), names.len()); + + let (_, page) = json_response( + app.clone() + .oneshot(search_request(&[("q", "anemone"), ("limit", "2"), ("offset", "2")])) + .await + .unwrap(), + ) + .await; + let slice: Vec<&str> = page["hits"] + .as_array() + .unwrap() + .iter() + .map(|hit| hit["uri"].as_str().unwrap()) + .collect(); + assert_eq!(slice, walked[2..4].to_vec(), "offset page matches walk"); + + let resp = app + .oneshot(search_request(&[ + ("q", "anemone"), + ("cursor", "deadbeef0"), + ("offset", "1"), + ])) + .await + .unwrap(); + let (status, _) = json_response(resp).await; + assert_eq!(status, StatusCode::BAD_REQUEST); +} + #[tokio::test] async fn pagination_round_trips_via_cursor() { let h = Harness::new().await; diff --git a/lexicons/feed/listComments.json b/lexicons/feed/listComments.json index bb3eaa248..ce09641cc 100644 --- a/lexicons/feed/listComments.json +++ b/lexicons/feed/listComments.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/feed/listCommentsBy.json b/lexicons/feed/listCommentsBy.json index 33600fb42..e4b6fe67e 100644 --- a/lexicons/feed/listCommentsBy.json +++ b/lexicons/feed/listCommentsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.feed.listComments#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/feed/listReactions.json b/lexicons/feed/listReactions.json index 27da434b8..de415895f 100644 --- a/lexicons/feed/listReactions.json +++ b/lexicons/feed/listReactions.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/feed/listReactionsBy.json b/lexicons/feed/listReactionsBy.json index 17ae614cb..6873e84bb 100644 --- a/lexicons/feed/listReactionsBy.json +++ b/lexicons/feed/listReactionsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.feed.listReactions#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/feed/listStars.json b/lexicons/feed/listStars.json index faac23b01..e35bdceb7 100644 --- a/lexicons/feed/listStars.json +++ b/lexicons/feed/listStars.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/feed/listStarsBy.json b/lexicons/feed/listStarsBy.json index 5db55b375..bd189b6af 100644 --- a/lexicons/feed/listStarsBy.json +++ b/lexicons/feed/listStarsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.feed.listStars#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/git/listRefUpdates.json b/lexicons/git/listRefUpdates.json index e2529f84c..c5e1337f0 100644 --- a/lexicons/git/listRefUpdates.json +++ b/lexicons/git/listRefUpdates.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/git/listRefUpdatesBy.json b/lexicons/git/listRefUpdatesBy.json index 6d86769a4..8f6dedb2e 100644 --- a/lexicons/git/listRefUpdatesBy.json +++ b/lexicons/git/listRefUpdatesBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.git.listRefUpdates#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/graph/listFollows.json b/lexicons/graph/listFollows.json index e4ce84fad..017ae4695 100644 --- a/lexicons/graph/listFollows.json +++ b/lexicons/graph/listFollows.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/graph/listFollowsBy.json b/lexicons/graph/listFollowsBy.json index 4725050d4..37b06cd3c 100644 --- a/lexicons/graph/listFollowsBy.json +++ b/lexicons/graph/listFollowsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.graph.listFollows#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/graph/listVouches.json b/lexicons/graph/listVouches.json index 649cb261d..e10991e86 100644 --- a/lexicons/graph/listVouches.json +++ b/lexicons/graph/listVouches.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/graph/listVouchesBy.json b/lexicons/graph/listVouchesBy.json index 3be6468e9..8711edc29 100644 --- a/lexicons/graph/listVouchesBy.json +++ b/lexicons/graph/listVouchesBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.graph.listVouches#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/knot/listKnots.json b/lexicons/knot/listKnots.json index eb10781bf..2dcce036c 100644 --- a/lexicons/knot/listKnots.json +++ b/lexicons/knot/listKnots.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/knot/listMembers.json b/lexicons/knot/listMembers.json index 317f691e8..ab354fda5 100644 --- a/lexicons/knot/listMembers.json +++ b/lexicons/knot/listMembers.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/knot/listMembersBy.json b/lexicons/knot/listMembersBy.json index bbb026c6b..fe236ff56 100644 --- a/lexicons/knot/listMembersBy.json +++ b/lexicons/knot/listMembersBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.knot.listMembers#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/label/listDefinitions.json b/lexicons/label/listDefinitions.json index 1aef094f5..2756faa42 100644 --- a/lexicons/label/listDefinitions.json +++ b/lexicons/label/listDefinitions.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/label/listOps.json b/lexicons/label/listOps.json index 6dee50f4d..45ffd777d 100644 --- a/lexicons/label/listOps.json +++ b/lexicons/label/listOps.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/label/listOpsBy.json b/lexicons/label/listOpsBy.json index 2a0e68423..3f71f6283 100644 --- a/lexicons/label/listOpsBy.json +++ b/lexicons/label/listOpsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.label.listOps#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/pipeline/listPipelines.json b/lexicons/pipeline/listPipelines.json index 4d67fdfb5..4ab889d02 100644 --- a/lexicons/pipeline/listPipelines.json +++ b/lexicons/pipeline/listPipelines.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/pipeline/listPipelinesBy.json b/lexicons/pipeline/listPipelinesBy.json index 628b24dba..a0aa3a21c 100644 --- a/lexicons/pipeline/listPipelinesBy.json +++ b/lexicons/pipeline/listPipelinesBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.pipeline.listPipelines#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/pipeline/listStatuses.json b/lexicons/pipeline/listStatuses.json index 2b77c3de9..bbee31482 100644 --- a/lexicons/pipeline/listStatuses.json +++ b/lexicons/pipeline/listStatuses.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/pipeline/listStatusesBy.json b/lexicons/pipeline/listStatusesBy.json index 7d1ccf199..524d034df 100644 --- a/lexicons/pipeline/listStatusesBy.json +++ b/lexicons/pipeline/listStatusesBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.pipeline.listStatuses#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/publicKey/listKeys.json b/lexicons/publicKey/listKeys.json index bca587713..d42ebc47e 100644 --- a/lexicons/publicKey/listKeys.json +++ b/lexicons/publicKey/listKeys.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/repo/listArtifacts.json b/lexicons/repo/listArtifacts.json index 58ca18ba0..d94ba10ba 100644 --- a/lexicons/repo/listArtifacts.json +++ b/lexicons/repo/listArtifacts.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/repo/listArtifactsBy.json b/lexicons/repo/listArtifactsBy.json index 81c325313..183a900f0 100644 --- a/lexicons/repo/listArtifactsBy.json +++ b/lexicons/repo/listArtifactsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.repo.listArtifacts#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/repo/listCollaborators.json b/lexicons/repo/listCollaborators.json index 42c4f0282..8e7f2501d 100644 --- a/lexicons/repo/listCollaborators.json +++ b/lexicons/repo/listCollaborators.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/repo/listCollaboratorsBy.json b/lexicons/repo/listCollaboratorsBy.json index a8f6926ef..c9e3d48c9 100644 --- a/lexicons/repo/listCollaboratorsBy.json +++ b/lexicons/repo/listCollaboratorsBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.repo.listCollaborators#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/repo/listIssues.json b/lexicons/repo/listIssues.json index c89bdd0f2..e671d82ca 100644 --- a/lexicons/repo/listIssues.json +++ b/lexicons/repo/listIssues.json @@ -27,6 +27,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -56,6 +61,10 @@ }, "cursor": { "type": "string" + }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" } } } diff --git a/lexicons/repo/listIssuesBy.json b/lexicons/repo/listIssuesBy.json index 445485f1a..46f214603 100644 --- a/lexicons/repo/listIssuesBy.json +++ b/lexicons/repo/listIssuesBy.json @@ -22,6 +22,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -51,6 +56,10 @@ }, "cursor": { "type": "string" + }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" } } } diff --git a/lexicons/repo/listPulls.json b/lexicons/repo/listPulls.json index 626639ee1..76d177164 100644 --- a/lexicons/repo/listPulls.json +++ b/lexicons/repo/listPulls.json @@ -27,6 +27,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -56,6 +61,10 @@ }, "cursor": { "type": "string" + }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" } } } diff --git a/lexicons/repo/listPullsBy.json b/lexicons/repo/listPullsBy.json index aa087a864..3899d349f 100644 --- a/lexicons/repo/listPullsBy.json +++ b/lexicons/repo/listPullsBy.json @@ -22,6 +22,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -51,6 +56,10 @@ }, "cursor": { "type": "string" + }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" } } } diff --git a/lexicons/repo/listRepos.json b/lexicons/repo/listRepos.json index 4897f674b..908b9cf0c 100644 --- a/lexicons/repo/listRepos.json +++ b/lexicons/repo/listRepos.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/search/query.json b/lexicons/search/query.json index a98b52404..20b62669e 100644 --- a/lexicons/search/query.json +++ b/lexicons/search/query.json @@ -42,6 +42,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/spindle/listMembers.json b/lexicons/spindle/listMembers.json index 84e956687..313ef19dc 100644 --- a/lexicons/spindle/listMembers.json +++ b/lexicons/spindle/listMembers.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/spindle/listMembersBy.json b/lexicons/spindle/listMembersBy.json index 7d34c9597..a32172924 100644 --- a/lexicons/spindle/listMembersBy.json +++ b/lexicons/spindle/listMembersBy.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "sh.tangled.spindle.listMembers#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/spindle/listSpindles.json b/lexicons/spindle/listSpindles.json index df5846878..7e17a15f9 100644 --- a/lexicons/spindle/listSpindles.json +++ b/lexicons/spindle/listSpindles.json @@ -17,6 +17,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -41,7 +46,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/lexicons/string/listStrings.json b/lexicons/string/listStrings.json index 4e85e656e..8ab1c1f14 100644 --- a/lexicons/string/listStrings.json +++ b/lexicons/string/listStrings.json @@ -16,6 +16,11 @@ "type": "string", "description": "Pagination cursor" }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor." + }, "limit": { "type": "integer", "minimum": 1, @@ -40,7 +45,11 @@ "type": "array", "items": { "type": "ref", "ref": "#listItem" } }, - "cursor": { "type": "string" } + "cursor": { "type": "string" }, + "total": { + "type": "integer", + "description": "Total items in the full list; omitted for filtered or merged views" + } } } } diff --git a/web/src/lib/api/lexicons/types/sh/tangled/feed/listComments.ts b/web/src/lib/api/lexicons/types/sh/tangled/feed/listComments.ts index 2b11e76a1..fabb76cab 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/feed/listComments.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/feed/listComments.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listComments", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listComments", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/feed/listCommentsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/feed/listCommentsBy.ts index a955fbcff..ce4bc68e8 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/feed/listCommentsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/feed/listCommentsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listCommentsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listCommentsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledFeedListComments.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactions.ts b/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactions.ts index 48da50ccb..c62571fbd 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactions.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactions.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listReactions", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listReactions", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactionsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactionsBy.ts index 138f8e41d..420a2635f 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactionsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/feed/listReactionsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listReactionsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listReactionsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledFeedListReactions.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/feed/listStars.ts b/web/src/lib/api/lexicons/types/sh/tangled/feed/listStars.ts index 89c0539e6..f04cf8cc7 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/feed/listStars.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/feed/listStars.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listStars", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listStars", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/feed/listStarsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/feed/listStarsBy.ts index 69bfbb67f..b28a7c59e 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/feed/listStarsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/feed/listStarsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listStarsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.feed.listStarsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledFeedListStars.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdates.ts b/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdates.ts index 44323d239..dc6590a9c 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdates.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdates.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.git.listRefUpdates", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.git.listRefUpdates", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdatesBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdatesBy.ts index 274cda6b2..c3d71ee9d 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdatesBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/git/listRefUpdatesBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.git.listRefUpdatesBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.git.listRefUpdatesBy", { get items() { return /*#__PURE__*/ v.array(ShTangledGitListRefUpdates.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollows.ts b/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollows.ts index 4399ae7f6..7e9aeabc2 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollows.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollows.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listFollows", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listFollows", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollowsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollowsBy.ts index eaa434515..e1bf94b18 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollowsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/graph/listFollowsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listFollowsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listFollowsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledGraphListFollows.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouches.ts b/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouches.ts index 6cbbf1bc8..dbfbe11ee 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouches.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouches.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listVouches", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listVouches", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouchesBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouchesBy.ts index 27cb5356d..1248392ea 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouchesBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/graph/listVouchesBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listVouchesBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.graph.listVouchesBy", { get items() { return /*#__PURE__*/ v.array(ShTangledGraphListVouches.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/knot/listKnots.ts b/web/src/lib/api/lexicons/types/sh/tangled/knot/listKnots.ts index 1f103aaba..aed81746e 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/knot/listKnots.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/knot/listKnots.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listKnots", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listKnots", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembers.ts b/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembers.ts index 03b463fbb..029df3a3f 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembers.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembers.ts @@ -44,6 +44,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listMembers", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -64,6 +69,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listMembers", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembersBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembersBy.ts index 9ebc61a65..a9c8ea9ac 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembersBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/knot/listMembersBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listMembersBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listMembersBy", { get items() { return /*#__PURE__*/ v.array(ShTangledKnotListMembers.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/label/listDefinitions.ts b/web/src/lib/api/lexicons/types/sh/tangled/label/listDefinitions.ts index c517ddcc6..9c194e873 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/label/listDefinitions.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/label/listDefinitions.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.label.listDefinitions", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.label.listDefinitions", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/label/listOps.ts b/web/src/lib/api/lexicons/types/sh/tangled/label/listOps.ts index 45f989346..8414c1d54 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/label/listOps.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/label/listOps.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.label.listOps", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.label.listOps", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/label/listOpsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/label/listOpsBy.ts index c57091b83..20ccdc3a0 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/label/listOpsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/label/listOpsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.label.listOpsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.label.listOpsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledLabelListOps.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelines.ts b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelines.ts index 4a8ea0a46..4209bebe4 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelines.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelines.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.pipeline.listPipelines", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.pipeline.listPipelines", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelinesBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelinesBy.ts index 6eb88db68..faa250818 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelinesBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listPipelinesBy.ts @@ -22,6 +22,11 @@ const _mainSchema = /*#__PURE__*/ v.query( ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -44,6 +49,10 @@ const _mainSchema = /*#__PURE__*/ v.query( ShTangledPipelineListPipelines.listItemSchema, ); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }, diff --git a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatuses.ts b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatuses.ts index e82854aab..728a7af58 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatuses.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatuses.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.pipeline.listStatuses", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.pipeline.listStatuses", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatusesBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatusesBy.ts index d878d4e00..4fd334255 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatusesBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/pipeline/listStatusesBy.ts @@ -22,6 +22,11 @@ const _mainSchema = /*#__PURE__*/ v.query( ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -44,6 +49,10 @@ const _mainSchema = /*#__PURE__*/ v.query( ShTangledPipelineListStatuses.listItemSchema, ); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }, diff --git a/web/src/lib/api/lexicons/types/sh/tangled/publicKey/listKeys.ts b/web/src/lib/api/lexicons/types/sh/tangled/publicKey/listKeys.ts index 88509817c..d1f5ec932 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/publicKey/listKeys.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/publicKey/listKeys.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.publicKey.listKeys", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.publicKey.listKeys", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifacts.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifacts.ts index a91e03c8f..98a566dba 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifacts.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifacts.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listArtifacts", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listArtifacts", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifactsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifactsBy.ts index 1c5dfb009..180d407ab 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifactsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listArtifactsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listArtifactsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -40,6 +45,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listArtifactsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledRepoListArtifacts.listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaborators.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaborators.ts index 9f222baf2..73709f5bf 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaborators.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaborators.ts @@ -44,6 +44,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listCollaborators", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -64,6 +69,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listCollaborators", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaboratorsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaboratorsBy.ts index ff1938293..6eca848a7 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaboratorsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listCollaboratorsBy.ts @@ -22,6 +22,11 @@ const _mainSchema = /*#__PURE__*/ v.query( ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -44,6 +49,10 @@ const _mainSchema = /*#__PURE__*/ v.query( ShTangledRepoListCollaborators.listItemSchema, ); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }, diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssues.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssues.ts index 446ec80cc..37bfb6fe8 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssues.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssues.ts @@ -47,6 +47,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listIssues", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -73,6 +78,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listIssues", { get items() { return /*#__PURE__*/ v.array(issueListItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssuesBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssuesBy.ts index 42c604eb5..fbfa3fa17 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssuesBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listIssuesBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listIssuesBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -48,6 +53,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listIssuesBy", { ShTangledRepoListIssues.issueListItemSchema, ); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listPulls.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listPulls.ts index 1de5ca281..7b3cf10e8 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listPulls.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listPulls.ts @@ -23,6 +23,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listPulls", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -49,6 +54,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listPulls", { get items() { return /*#__PURE__*/ v.array(pullListItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listPullsBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listPullsBy.ts index 6b5536fde..8473b9750 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listPullsBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listPullsBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listPullsBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -46,6 +51,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listPullsBy", { get items() { return /*#__PURE__*/ v.array(ShTangledRepoListPulls.pullListItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/repo/listRepos.ts b/web/src/lib/api/lexicons/types/sh/tangled/repo/listRepos.ts index a67705ce4..839c16851 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/repo/listRepos.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/repo/listRepos.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listRepos", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.repo.listRepos", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/search/query.ts b/web/src/lib/api/lexicons/types/sh/tangled/search/query.ts index 5cb619e34..299baebaa 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/search/query.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/search/query.ts @@ -46,6 +46,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.search.query", { * Restrict to records of this collection. */ nsid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Full-text search query. * @minLength 1 diff --git a/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembers.ts b/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembers.ts index 5c206b2c9..61799ef69 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembers.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembers.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.spindle.listMembers", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.spindle.listMembers", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembersBy.ts b/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembersBy.ts index 3f714eadc..a7f3ce318 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembersBy.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/spindle/listMembersBy.ts @@ -20,6 +20,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.spindle.listMembersBy", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -42,6 +47,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.spindle.listMembersBy", { ShTangledSpindleListMembers.listItemSchema, ); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/spindle/listSpindles.ts b/web/src/lib/api/lexicons/types/sh/tangled/spindle/listSpindles.ts index a8e052931..5198c5c84 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/spindle/listSpindles.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/spindle/listSpindles.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.spindle.listSpindles", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.spindle.listSpindles", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); diff --git a/web/src/lib/api/lexicons/types/sh/tangled/string/listStrings.ts b/web/src/lib/api/lexicons/types/sh/tangled/string/listStrings.ts index 25a75b660..d904a6083 100644 --- a/web/src/lib/api/lexicons/types/sh/tangled/string/listStrings.ts +++ b/web/src/lib/api/lexicons/types/sh/tangled/string/listStrings.ts @@ -30,6 +30,11 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.string.listStrings", { ]), 50, ), + /** + * Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. + * @minimum 0 + */ + offset: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), /** * Sort direction by createdAt. * @default "desc" @@ -50,6 +55,10 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.string.listStrings", { get items() { return /*#__PURE__*/ v.array(listItemSchema); }, + /** + * Total items in the full list; omitted for filtered or merged views + */ + total: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), }), }, }); -- 2.51.2