feat(outlinekit): add parentDocumentId filter to listDocuments

documents.list supports it directly (per the vendored spec) — backs
fetching a document's direct children for the reader's sub-documents
section, without a second round-trip through the whole collection's
flat list.
This commit is contained in:
2026-08-14 01:59:24 +01:00
parent 733a5a582c
commit 3ecc463121
3 changed files with 37 additions and 3 deletions
@@ -8,7 +8,7 @@ public protocol OutlineAPIClient: Sendable {
func authInfo() async throws -> OutlineAuthInfo func authInfo() async throws -> OutlineAuthInfo
func documentInfo(id: String) async throws -> OutlineDocument func documentInfo(id: String) async throws -> OutlineDocument
func listDocuments(collectionId: String?, offset: Int, limit: Int) async throws -> [OutlineDocument] func listDocuments(collectionId: String?, parentDocumentId: String?, offset: Int, limit: Int) async throws -> [OutlineDocument]
/// Full-text search with snippets/ranking. Backed by `documents.search`. /// Full-text search with snippets/ranking. Backed by `documents.search`.
func searchDocuments(_ request: DocumentSearchRequest) async throws -> [OutlineDocumentSearchResult] func searchDocuments(_ request: DocumentSearchRequest) async throws -> [OutlineDocumentSearchResult]
/// Title-only search faster, no snippets. Backed by `documents.search_titles`. /// Title-only search faster, no snippets. Backed by `documents.search_titles`.
@@ -34,10 +34,20 @@ public actor LiveOutlineAPIClient: OutlineAPIClient {
try await post("documents.info", body: DocumentInfoParams(id: id)) try await post("documents.info", body: DocumentInfoParams(id: id))
} }
public func listDocuments(collectionId: String?, offset: Int, limit: Int) async throws -> [OutlineDocument] { public func listDocuments(
collectionId: String?,
parentDocumentId: String?,
offset: Int,
limit: Int
) async throws -> [OutlineDocument] {
try await post( try await post(
"documents.list", "documents.list",
body: DocumentListParams(collectionId: collectionId, offset: offset, limit: limit) body: DocumentListParams(
collectionId: collectionId,
parentDocumentId: parentDocumentId,
offset: offset,
limit: limit
)
) )
} }
@@ -188,6 +198,7 @@ private struct DocumentInfoParams: Encodable {
private struct DocumentListParams: Encodable { private struct DocumentListParams: Encodable {
let collectionId: String? let collectionId: String?
let parentDocumentId: String?
let offset: Int let offset: Int
let limit: Int let limit: Int
} }
@@ -257,6 +257,29 @@ final class LiveOutlineAPIClientTests: XCTestCase {
XCTAssertEqual(decodedBody.description, "Updated overview text.") XCTAssertEqual(decodedBody.description, "Updated overview text.")
} }
func testListDocumentsSendsParentDocumentIdFilter() async throws {
let httpClient = MockHTTPClient()
httpClient.responseData = """
{ "data": [] }
""".data(using: .utf8)!
let client = LiveOutlineAPIClient(
configuration: OutlineConfiguration(baseURL: URL(string: "https://outline.example.com")!),
tokenStore: StaticTokenStore(),
httpClient: httpClient
)
_ = try await client.listDocuments(collectionId: nil, parentDocumentId: "doc-1", offset: 0, limit: 100)
struct SentBody: Decodable {
let parentDocumentId: String?
}
let sentBody = try XCTUnwrap(httpClient.lastRequest?.httpBody)
let decodedBody = try JSONDecoder().decode(SentBody.self, from: sentBody)
XCTAssertEqual(decodedBody.parentDocumentId, "doc-1")
}
func testDocumentInfoDecodesEnvelopeAndSetsAuthHeader() async throws { func testDocumentInfoDecodesEnvelopeAndSetsAuthHeader() async throws {
let httpClient = MockHTTPClient() let httpClient = MockHTTPClient()
httpClient.responseData = """ httpClient.responseData = """