feat: deadline helpers (#852), multi-invoice payment aggregator (#853), options-only tenant pool (#846), property tests (#845) - #898
Merged
Kingsman-99 merged 4 commits intoSep 28, 2026
Conversation
…s and multi-invoice payment aggregator Stellar-split#852 — deadline helpers: - `deadlineFromDays(days)` returns a bigint Unix timestamp, rounded up so any positive duration is strictly in the future - `deadlineFromDate(date)` converts a Date to Unix seconds - `isDeadlineValid(deadline)` enforces the 1-hour minimum lead time - `timeUntilDeadline(deadline)` returns { days, hours, minutes, seconds, expired } - `formatDeadline(deadline, locale?)` renders a localized, UTC date string Stellar-split#853 — payment aggregator: - `aggregatePayments(budget, invoiceIds, strategy, options?)` allocates a budget across invoices using the `equal`, `proportional` or `custom` strategy - allocations are capped at each invoice's remaining amount (no overpayment) and rounding dust is redistributed via the largest-remainder method - `proportional` weights by remaining amount, or by fraction-of-target when `targets` are supplied (i.e. how far each invoice is from its target) - `custom` weights are validated to sum to 100 - remaining amounts are resolved from a map/record, a `fetchRemaining` fn, an `invoiceSource` or a registered fallback fetcher; empty lists return [] Both modules are exported from the package root and documented in the README.
…ruction
The pool already offered LRU eviction, TTL expiry, background health checks and
`stats()`. It could however only be constructed with a tenant->config factory
first, while the documented API is `new MultiTenantClient({ maxClients, ttlMs,
healthCheckIntervalMs })`.
The constructor now accepts either form: a factory plus `PoolOptions` (unchanged
behaviour, fully backwards compatible) or options alone. When no factory is
registered the tenant config must be passed to `getClient(tenantId, config)`;
a missing factory *and* config raises a clear `ValidationError` instead of a
TypeError. Tests cover the options-only form, maxClients enforcement without a
factory, the factory form and the missing-config error.
…new modules - new `test/property-payment-allocation.test.ts`: 500 examples per property proving allocations never exceed the budget or an invoice's remaining amount, always distribute min(budget, total remaining), preserve input order and keep percentages in range; custom weights summing to 100 are accepted and invalid weights rejected - `test/property-deadline.test.ts`: 500-example properties for the bigint deadline helpers (future timestamps for any n > 0, the 1-hour validity rule, non-negative countdown units and exact duration reconstruction) - `npm test` now also runs the deadline, payment allocation and multi-tenant suites so these acceptance criteria are exercised by CI
|
@willi-d7 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! 🚀 |
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.
Summary
Closes four open issues in one PR:
src/deadline.tsmodule withbigint-based helpers that match the on-chainu64representation:deadlineFromDays(rounded up so any positive duration is strictly in the future),deadlineFromDate,isDeadlineValid(1-hour minimum lead time),timeUntilDeadline(returns{ days, hours, minutes, seconds, expired }, all zeros once expired) andformatDeadline(locale-aware, rendered in UTC for deterministic output). Exported from the package root and documented in the README. The legacynumber-returningdeadlineFromDayson the@stellar-split/sdk/utilsentry point is left untouched and its README row points to the new helper.src/paymentAllocation.tsmodule implementingaggregatePayments(budget, invoiceIds, strategy, options?)with all three strategies:equal— even split, surplus redistributed;proportional— weighted by remaining amount, or by fraction of target still unfunded whentargetsare supplied (so invoices furthest from their target receive the most);custom— caller weights that are validated to sum to 100 (length, non-negativity and finiteness are also validated).Every allocation is capped at the invoice's remaining amount so overpayment is impossible, and rounding dust is redistributed deterministically with the largest-remainder/water-filling algorithm, guaranteeing the allocations always sum to
min(budget, total remaining). Empty invoice lists return[]; duplicate IDs, negative budgets, unknown strategies and invalid weights throwValidationError. Remaining amounts are resolved from an explicit map/record, afetchRemainingfunction, aninvoiceSource(any object withgetInvoice(id)), or a fetcher registered viaregisterInvoiceRemainingFetcher— withremainingForInvoiceandcreateInvoiceRemainingFetcherhelpers for the on-chainrecipients - fundedderivation.stats(), but it could only be created asnew MultiTenantClient(factory, options)while the documented API isnew MultiTenantClient({ maxClients, ttlMs, healthCheckIntervalMs }). The constructor now accepts either form: factory + options (unchanged, backwards compatible) or options alone, in which casegetClient(tenantId, config)supplies the tenant config. A missing factory and config now raises a descriptiveValidationErrorinstead of an opaqueTypeError.npm testso the criteria are actually exercised by CI: newtest/property-payment-allocation.test.ts(7 properties, 500 examples each: budget never exceeded, no overpayment per invoice,min(budget, total remaining)fully distributed, input order preserved, percentages in[0, 100], valid weights accepted, invalid weights rejected) and 4 new 500-example properties intest/property-deadline.test.tsfor thebiginthelpers (future timestamps for anyn > 0, the 1-hour rule, non-negative countdown units, exact duration reconstruction).Approach
src/deadline.ts,src/paymentAllocation.ts) following the existing conventions: JSDoc on every export,ValidationErrorfromsrc/errors.jsfor input validation, registered-fetcher pattern (registerInvoiceRemainingFetcher) likeregisterInvoiceFetcher/registerChannelStateFetcher, and root re-exports added tosrc/index.tswith the#nnn —section banners used by the rest of the file.bigintarithmetic end-to-end (no floats) for allocations; the water-filling allocator caps shares at each invoice's remaining amount and redistributes the surplus over remaining passes, then resolves sub-stroop rounding with the largest-remainder method.src/multiTenant.tskeeps its existing behaviour and only widens the constructor signature (factory | options) plus the guard ingetClient, so all existing call sites and tests keep working.How it was tested
npm testnpx vitest run test/paymentAllocation.test.tsnpx vitest run test/deadline.test.tsen-USvsde-DElocale formatting)npx vitest run test/multiTenant.test.tsValidationError)npx vitest run test/property-*.test.tsnpx tsc --noEmitmainand unrelated to these files; the four touched/added modules type-check clean under--strict --noUncheckedIndexedAccess)ESLint is not configured in this repository (
npm run lintmaps totsc --noEmit), so that check is covered by the type-check run above.closes #852
closes #853
closes #846
closes #845