feat(queries): filter engine for invoice queries — status, date, amount range - #946
Merged
Kingsman-99 merged 5 commits intoSep 28, 2026
Merged
Conversation
Add client.queryInvoices(filter) backed by a new InvoiceQueryEngine so callers can filter by status, creator, date range, amount range and tags through a single typed query object. - InvoiceFilter composes every predicate with AND semantics; status matches any listed state while tags require every listed tag - Four sort orders (newest/oldest/highest/lowest) with an invoice-ID tie-break so paging is deterministic - Offset-based opaque cursors; nextCursor is omitted on the final page and total always reports the unpaginated match count - Amounts compared as bigint, never narrowed through Number - Dates accept Unix seconds or milliseconds, matching createdAt's existing convention; invoices with an unknown createdAt are excluded from date filters and sort last - InvoiceTagIndex for tag lookups; tags fall back to #hashtags parsed from the memo so existing invoices are taggable without a contract change - queryInvoices requires a creator because the contract exposes no global invoice index, and filters the creator's invoices in memory closes Stellar-split#851
Expose the filter engine on the client. Because the contract has no query endpoint for status/amount/date, this cursor-pages the creator's invoice IDs, fetches each invoice, then applies the filter in memory. Requiring a creator keeps the fetch bounded and turns the missing global index into an explicit error rather than a silent full scan.
Add a README section covering the InvoiceFilter fields, their AND/OR semantics, the InvoicePage shape, cursor paging, and the exported engine for searching an invoice list already held in memory.
|
@maztah1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the issue was
#851 asked for a filter and query engine for listing invoices —
client.queryInvoices(filter)accepting a single typed query object that filters by status, creator, date range, amount range and tags, returning a paginatedPagewith sorting. There was no such API. The SDK hadgetInvoicesByCreator(raw IDs only) and a separatecompileFilter/applyFilter/FilterIndexmodule built around a recursiveand/orFilterCriteriatree, which had no notion of dates, tags, sorting, or paging.Approach
New module
src/invoiceQuery.tsInvoiceFilter—{ creator?, status?, minAmount?, maxAmount?, fromDate?, toDate?, tags?, limit?, cursor?, sort? }. All supplied fields combine with AND; within a field,statusmatches any listed state whiletagsrequires every listed tag.InvoiceQueryEngine— filter → sort → paginate over an in-memory invoice set. Also exported as a standalonequeryInvoices(invoices, filter)for callers who already hold the invoices and want no network calls.InvoiceTagIndex— tag → invoices lookup, exposed viaengine.tagIndex().InvoicePage—{ items, nextCursor?, total }.nextCursoris omitted (notnull) on the final page;totalalways reports the unpaginated match count.client.queryInvoices(filter)— cursor-pages the creator's invoice IDs, fetches each invoice, then applies the filter in memory.Decisions worth flagging:
creatoris required. The contract exposes no global invoice index, so an unscoped query would be an unbounded full scan. This throws aValidationErrorrather than silently enumerating everything — the existinggetInvoicesByCreatorremains available for manual paging.bigint, never narrowed throughNumber, since stroop totals routinely exceed2^53. Covered by a test at2^53 + 1.Invoice.createdAtalready documents. Invoices with nocreatedAtare excluded from date filters and sort last.off:<n>), since sorting can reorder the underlying set; an offset stays deterministic for a given (filter, sort) pair. Ties break on invoice ID so pages never repeat or skip.#hashtagsparsed from the memo when an invoice has no explicittagsfield, so existing invoices are taggable with no contract change. I added an optionaltags?: string[]toInvoicefor the explicit case.How it was tested
57 new tests in
test/invoiceQuery.test.ts, all passing:nextCursorpresence/absence,totalstability across pages, a full multi-page walk asserting no gaps or duplicates, and a cursor past the end.queryInvoicesagainst a stubbed client, including that it rejects a missingcreator.Full suite: 213 passed, 1 skipped, 0 failed.
tsc --noEmitoutput diffed against the base branch to confirm no new errors (the repo has pre-existing type errors unrelated to this change).Note:
package.json'stestscript only runs two specific files; I ran the full suite vianpx vitest run.closes #851