diff --git a/api/tangled/knotlistMembers.go b/api/tangled/knotlistMembers.go index 77f111c3c..7aace791d 100644 --- a/api/tangled/knotlistMembers.go +++ b/api/tangled/knotlistMembers.go @@ -16,30 +16,27 @@ const ( // KnotListMembers_ListItem is a "listItem" in the sh.tangled.knot.listMembers schema. type KnotListMembers_ListItem struct { - // addedBy: DID that added this member - AddedBy string `json:"addedBy" cborgen:"addedBy"` - // cid: Optional record CID for record-backed indexers Cid *string `json:"cid,omitempty" cborgen:"cid,omitempty"` - // createdAt: When the member was added - CreatedAt string `json:"createdAt" cborgen:"createdAt"` - // subject: DID of the member - Subject string `json:"subject" cborgen:"subject"` - // uri: Optional record AT-URI for record-backed indexers - Uri *string `json:"uri,omitempty" cborgen:"uri,omitempty"` + Uri string `json:"uri" cborgen:"uri"` + // value: Embedded sh.tangled.knot.member record + Value *util.LexiconTypeDecoder `json:"value" cborgen:"value"` } // KnotListMembers_Output is the output of a sh.tangled.knot.listMembers call. type KnotListMembers_Output struct { Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"` Items []*KnotListMembers_ListItem `json:"items" cborgen:"items"` + // total: Total items in the full list; omitted for filtered or merged views + Total *int64 `json:"total,omitempty" cborgen:"total,omitempty"` } // KnotListMembers calls the XRPC method "sh.tangled.knot.listMembers". // // cursor: Pagination cursor +// offset: Absolute offset for random-access pagination. Mutually exclusive with cursor; offsets drift under concurrent writes, so follow up with the returned cursor. // order: Sort direction by createdAt. -// subject: Knot identifier whose member records to list. -func KnotListMembers(ctx context.Context, c util.LexClient, cursor string, limit int64, order string, subject string) (*KnotListMembers_Output, error) { +// subject: Actor DID whose knot memberships to list. +func KnotListMembers(ctx context.Context, c util.LexClient, cursor string, limit int64, offset int64, order string, subject string) (*KnotListMembers_Output, error) { var out KnotListMembers_Output params := map[string]interface{}{} @@ -49,6 +46,9 @@ func KnotListMembers(ctx context.Context, c util.LexClient, cursor string, limit if limit != 0 { params["limit"] = limit } + if offset != 0 { + params["offset"] = offset + } if order != "" { params["order"] = order } diff --git a/bobbin/crates/xrpc/src/lib.rs b/bobbin/crates/xrpc/src/lib.rs index 7f7aec9e3..c8ac248fb 100644 --- a/bobbin/crates/xrpc/src/lib.rs +++ b/bobbin/crates/xrpc/src/lib.rs @@ -953,6 +953,7 @@ impl Serialize for Deduped { #[serde(rename_all = "camelCase")] struct RecordView { uri: AtUri, + #[serde(skip_serializing_if = "Option::is_none")] cid: Option>, value: V, } diff --git a/bobbin/crates/xrpc/tests/aggregation.rs b/bobbin/crates/xrpc/tests/aggregation.rs index 6b866cfe3..6810fb681 100644 --- a/bobbin/crates/xrpc/tests/aggregation.rs +++ b/bobbin/crates/xrpc/tests/aggregation.rs @@ -2909,7 +2909,7 @@ async fn knot_owned_member_is_synthesized_without_slingshot() { "synthesized member must hydrate with no slingshot mock mounted" ); assert_eq!(items[0]["uri"], json!(source.as_ref())); - assert!(items[0]["cid"].is_null()); + assert!(items[0].get("cid").is_none()); assert_eq!(items[0]["value"]["domain"], json!("kt.oyster.cafe")); assert_eq!(items[0]["value"]["subject"], json!("did:plc:boltless")); let got = chrono::DateTime::parse_from_rfc3339( @@ -2948,7 +2948,7 @@ async fn knot_owned_member_lists_by_knot_did() { let items = body["items"].as_array().expect("items array"); assert_eq!(items.len(), 1); assert_eq!(items[0]["uri"], json!(source.as_ref())); - assert!(items[0]["cid"].is_null()); + assert!(items[0].get("cid").is_none()); assert_eq!(items[0]["value"]["domain"], json!("kt.oyster.cafe")); assert_eq!(items[0]["value"]["subject"], json!("did:plc:boltless")); } @@ -2981,7 +2981,7 @@ async fn knot_owned_collaborator_is_synthesized_without_slingshot() { let items = body["items"].as_array().expect("items array"); assert_eq!(items.len(), 1); assert_eq!(items[0]["uri"], json!(source.as_ref())); - assert!(items[0]["cid"].is_null()); + assert!(items[0].get("cid").is_none()); assert_eq!(items[0]["value"]["repo"], json!("did:plc:scallop")); assert_eq!(items[0]["value"]["subject"], json!("did:plc:olaren")); } @@ -3014,7 +3014,7 @@ async fn knot_owned_collaborator_lists_by_subject_did() { let items = body["items"].as_array().expect("items array"); assert_eq!(items.len(), 1); assert_eq!(items[0]["uri"], json!(source.as_ref())); - assert!(items[0]["cid"].is_null()); + assert!(items[0].get("cid").is_none()); assert_eq!(items[0]["value"]["repo"], json!("did:plc:scallop")); assert_eq!(items[0]["value"]["subject"], json!("did:plc:olaren")); } diff --git a/lexicons/knot/listMembers.json b/lexicons/knot/listMembers.json index ab354fda5..755e52f55 100644 --- a/lexicons/knot/listMembers.json +++ b/lexicons/knot/listMembers.json @@ -10,7 +10,7 @@ "properties": { "subject": { "type": "string", - "description": "Knot identifier whose member records to list." + "description": "Actor DID whose knot memberships to list." }, "cursor": { "type": "string", @@ -56,32 +56,13 @@ }, "listItem": { "type": "object", - "required": ["subject", "addedBy", "createdAt"], + "required": ["uri", "value"], "properties": { - "subject": { - "type": "string", - "format": "did", - "description": "DID of the member" - }, - "addedBy": { - "type": "string", - "format": "did", - "description": "DID that added this member" - }, - "createdAt": { - "type": "string", - "format": "datetime", - "description": "When the member was added" - }, - "uri": { - "type": "string", - "format": "at-uri", - "description": "Optional record AT-URI for record-backed indexers" - }, - "cid": { - "type": "string", - "format": "cid", - "description": "Optional record CID for record-backed indexers" + "uri": { "type": "string", "format": "at-uri" }, + "cid": { "type": "string", "format": "cid" }, + "value": { + "type": "unknown", + "description": "Embedded sh.tangled.knot.member record" } } } 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 029df3a3f..029703873 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 @@ -6,26 +6,12 @@ const _listItemSchema = /*#__PURE__*/ v.object({ $type: /*#__PURE__*/ v.optional( /*#__PURE__*/ v.literal("sh.tangled.knot.listMembers#listItem"), ), - /** - * DID that added this member - */ - addedBy: /*#__PURE__*/ v.didString(), - /** - * Optional record CID for record-backed indexers - */ cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), + uri: /*#__PURE__*/ v.resourceUriString(), /** - * When the member was added - */ - createdAt: /*#__PURE__*/ v.datetimeString(), - /** - * DID of the member - */ - subject: /*#__PURE__*/ v.didString(), - /** - * Optional record AT-URI for record-backed indexers + * Embedded sh.tangled.knot.member record */ - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + value: /*#__PURE__*/ v.unknown(), }); const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listMembers", { params: /*#__PURE__*/ v.object({ @@ -58,7 +44,7 @@ const _mainSchema = /*#__PURE__*/ v.query("sh.tangled.knot.listMembers", { "desc", ), /** - * Knot identifier whose member records to list. + * Actor DID whose knot memberships to list. */ subject: /*#__PURE__*/ v.string(), }),