@gibs/quotes 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +45 -0
  3. package/dist/cache.d.ts +73 -0
  4. package/dist/cache.js +137 -0
  5. package/dist/carriers/calibration.d.ts +115 -0
  6. package/dist/carriers/calibration.js +83 -0
  7. package/dist/carriers/lifi.d.ts +63 -0
  8. package/dist/carriers/lifi.js +217 -0
  9. package/dist/carriers/near-intents.d.ts +91 -0
  10. package/dist/carriers/near-intents.js +249 -0
  11. package/dist/carriers/price-catalogue.d.ts +186 -0
  12. package/dist/carriers/price-catalogue.js +191 -0
  13. package/dist/carriers/relay.d.ts +97 -0
  14. package/dist/carriers/relay.js +242 -0
  15. package/dist/chain-lists.d.ts +144 -0
  16. package/dist/chain-lists.js +169 -0
  17. package/dist/client.d.ts +102 -0
  18. package/dist/client.js +402 -0
  19. package/dist/index.d.ts +19 -0
  20. package/dist/index.js +18 -0
  21. package/dist/quote-guards.d.ts +128 -0
  22. package/dist/quote-guards.js +240 -0
  23. package/dist/quote-refusals.d.ts +180 -0
  24. package/dist/quote-refusals.js +284 -0
  25. package/dist/quote-service-contract.d.ts +2240 -0
  26. package/dist/quote-service-contract.js +247 -0
  27. package/dist/search/capabilities.d.ts +68 -0
  28. package/dist/search/capabilities.js +28 -0
  29. package/dist/search/enumerate.d.ts +162 -0
  30. package/dist/search/enumerate.js +313 -0
  31. package/dist/search/index.d.ts +17 -0
  32. package/dist/search/index.js +17 -0
  33. package/dist/search/serve.d.ts +178 -0
  34. package/dist/search/serve.js +142 -0
  35. package/dist/search/waves.d.ts +481 -0
  36. package/dist/search/waves.js +939 -0
  37. package/dist/single-flight.d.ts +90 -0
  38. package/dist/single-flight.js +142 -0
  39. package/dist/types.d.ts +502 -0
  40. package/dist/types.js +1 -0
  41. package/dist/upstream.d.ts +126 -0
  42. package/dist/upstream.js +343 -0
  43. package/llms.txt +217 -0
  44. package/package.json +142 -0
@@ -0,0 +1,90 @@
1
+ import { type DisplayProviderSlot } from './quote-service-contract.js';
2
+ /**
3
+ * The bucket-and-amount-aware in-flight coordinator `docs/quote-service.md`'s
4
+ * "Cache" section describes: "identical concurrent requests wait on one
5
+ * upstream call. An `estimate-ok` request joins an in-flight call for another
6
+ * amount in the same bucket and gets a scaled answer."
7
+ *
8
+ * WHY THIS IS SEPARATE FROM `cache.ts`'S OWN `joinOrRun`. `AnswerCache.joinOrRun`
9
+ * only ever dedups CALLERS OF THE IDENTICAL KEY, and a key already collapses
10
+ * every amount in a two-significant-figure bucket (`displayQuoteCacheKey`) —
11
+ * so it already gives an `estimate-ok` ask sharing behaviour for free. What it
12
+ * cannot do is the ONE THING this module exists for: telling an `exact` ask
13
+ * apart from an `estimate-ok` one sharing the same key, so that an `exact`
14
+ * ask NEVER receives another asker's answer for a different literal amount —
15
+ * `docs/quote-service.md`'s rule, "`precision: 'exact'` never takes a scaled
16
+ * answer" — while an `estimate-ok` ask still gets to share, scaled to its own
17
+ * amount. This module keys its own two in-flight maps directly on
18
+ * `(bucketKey, precision, askedAmount)` rather than reusing `AnswerCache`'s
19
+ * single, coarser key space.
20
+ */
21
+ /** Whether an ask may join an in-flight call for a different literal amount in the same bucket. */
22
+ export type SingleFlightPrecision = 'estimate-ok' | 'exact';
23
+ /** One caller's own request entering single-flight de-duplication. */
24
+ export type SingleFlightRequest = {
25
+ /** The bucket-level key this ask maps to — a {@link displayQuoteCacheKey} result. */
26
+ readonly bucketKey: string;
27
+ /** This caller's own literal amount, in the asked token's base units. */
28
+ readonly askedAmount: bigint;
29
+ /** `'exact'` only ever shares a call with an identical literal amount; `'estimate-ok'` shares any call in the bucket. */
30
+ readonly precision: SingleFlightPrecision;
31
+ };
32
+ /** What running the shared upstream call produces: the raw answer, and the literal amount it was actually asked for. */
33
+ export type SingleFlightOutcome = {
34
+ readonly answer: DisplayProviderSlot;
35
+ /** The amount `answer` is a genuine, un-scaled reply to — the FIRST caller's own amount, since only the first caller's `run` is ever invoked. */
36
+ readonly askedAmount: bigint;
37
+ };
38
+ /** What {@link SingleFlightGroup.ask} hands back to one caller. */
39
+ export type SingleFlightResult = {
40
+ /** This caller's own answer — scaled to its own amount when it joined a call for a different one. */
41
+ readonly answer: DisplayProviderSlot;
42
+ /** Whether this caller joined another's in-flight call rather than starting its own. */
43
+ readonly joined: boolean;
44
+ };
45
+ /** Runs the shared upstream call for a key, if nobody else is already running one. */
46
+ export type SingleFlightRun = () => Promise<SingleFlightOutcome>;
47
+ /**
48
+ * Thrown when `run` does not settle before {@link SingleFlightConfig.timeoutMs}.
49
+ * Every caller sharing that call — the one that started it and every joiner —
50
+ * rejects with this.
51
+ *
52
+ * A CALLER'S OWN ABORT NEVER REACHES THIS FAR. `SingleFlightGroup.ask` takes
53
+ * no `AbortSignal`, by design: `docs/quote-service.md`'s rule, "a client
54
+ * disconnect never cancels the shared call," is upheld structurally, by never
55
+ * giving a caller a channel to cancel it, rather than by remembering to
56
+ * ignore one. Only this module's OWN timeout — never a caller — ends a call
57
+ * that runs too long.
58
+ */
59
+ export declare class SingleFlightTimeoutError extends Error {
60
+ readonly key: string;
61
+ readonly timeoutMs: number;
62
+ constructor(key: string, timeoutMs: number);
63
+ }
64
+ /** The bucket-and-amount-aware in-flight coordinator. */
65
+ export type SingleFlightGroup = {
66
+ /**
67
+ * Asks for `request`, sharing an already in-flight call when this
68
+ * request's own precision allows it, or starting `run` when it does not.
69
+ *
70
+ * @param request - this caller's own bucket key, amount and precision
71
+ * @param run - the upstream call to make, if nobody joinable is already running one
72
+ * @returns this caller's own answer, and whether it joined another's call
73
+ * @throws {SingleFlightTimeoutError} when the shared call does not settle within the timeout
74
+ */
75
+ readonly ask: (request: SingleFlightRequest, run: SingleFlightRun) => Promise<SingleFlightResult>;
76
+ };
77
+ /** How a host builds a {@link SingleFlightGroup}. */
78
+ export type SingleFlightConfig = {
79
+ /** How long a shared call may run before every caller sharing it rejects with {@link SingleFlightTimeoutError}. Defaults to {@link DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS}. */
80
+ readonly timeoutMs?: number;
81
+ };
82
+ /** The 15 s default timeout `docs/quote-service.md`'s "Cache" section names: "own 15 s timeout." */
83
+ export declare const DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 15000;
84
+ /**
85
+ * Builds a fresh, empty {@link SingleFlightGroup}.
86
+ *
87
+ * @param config - see {@link SingleFlightConfig}
88
+ * @returns the group
89
+ */
90
+ export declare const createSingleFlightGroup: (config?: SingleFlightConfig) => SingleFlightGroup;
@@ -0,0 +1,142 @@
1
+ import { scaleToAskedAmount } from './quote-guards.js';
2
+ import { decodeBaseUnitsAmount, encodeBaseUnitsAmount, toEstimatedDelivery, } from './quote-service-contract.js';
3
+ /**
4
+ * Thrown when `run` does not settle before {@link SingleFlightConfig.timeoutMs}.
5
+ * Every caller sharing that call — the one that started it and every joiner —
6
+ * rejects with this.
7
+ *
8
+ * A CALLER'S OWN ABORT NEVER REACHES THIS FAR. `SingleFlightGroup.ask` takes
9
+ * no `AbortSignal`, by design: `docs/quote-service.md`'s rule, "a client
10
+ * disconnect never cancels the shared call," is upheld structurally, by never
11
+ * giving a caller a channel to cancel it, rather than by remembering to
12
+ * ignore one. Only this module's OWN timeout — never a caller — ends a call
13
+ * that runs too long.
14
+ */
15
+ export class SingleFlightTimeoutError extends Error {
16
+ key;
17
+ timeoutMs;
18
+ constructor(key, timeoutMs) {
19
+ super(`no answer for "${key}" within ${timeoutMs}ms`);
20
+ this.key = key;
21
+ this.timeoutMs = timeoutMs;
22
+ this.name = 'SingleFlightTimeoutError';
23
+ }
24
+ }
25
+ /** The 15 s default timeout `docs/quote-service.md`'s "Cache" section names: "own 15 s timeout." */
26
+ export const DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 15_000;
27
+ const scheduleTimer = (callback, delayMs) => {
28
+ const handle = setTimeout(callback, Math.max(0, delayMs));
29
+ if (typeof handle === 'object' && handle !== null && 'unref' in handle) {
30
+ handle.unref();
31
+ }
32
+ return () => clearTimeout(handle);
33
+ };
34
+ /**
35
+ * Turns the shared call's own outcome into ONE joiner's own answer: unchanged
36
+ * when the joiner's amount matches the amount actually asked upstream (a
37
+ * genuine, un-scaled reply for that joiner too), or scaled via
38
+ * {@link scaleToAskedAmount} and re-labelled `basis: 'scaled-from-nearby-amount'`
39
+ * otherwise. A refusal or a `pending` slot carries no figure to scale, so it
40
+ * passes through unchanged either way — only `cache` is set to `'joined'` on
41
+ * a `quoted` answer, since only that status carries the field.
42
+ *
43
+ * @param outcome - the shared call's own outcome
44
+ * @param joinerAskedAmount - this joiner's own literal amount
45
+ * @returns this joiner's own answer
46
+ */
47
+ const answerForJoiner = (outcome, joinerAskedAmount) => {
48
+ const { answer } = outcome;
49
+ if (answer.status !== 'quoted')
50
+ return answer;
51
+ if (outcome.askedAmount === joinerAskedAmount) {
52
+ return { ...answer, cache: 'joined' };
53
+ }
54
+ const rawExpected = decodeBaseUnitsAmount(answer.expectedAmount);
55
+ const scaledExpected = scaleToAskedAmount(rawExpected, joinerAskedAmount, outcome.askedAmount);
56
+ const scaledMinimum = answer.minimumAmount === undefined
57
+ ? undefined
58
+ : scaleToAskedAmount(decodeBaseUnitsAmount(answer.minimumAmount), joinerAskedAmount, outcome.askedAmount);
59
+ return {
60
+ status: 'quoted',
61
+ basis: 'scaled-from-nearby-amount',
62
+ askedAmount: encodeBaseUnitsAmount(joinerAskedAmount),
63
+ quotedAt: answer.quotedAt,
64
+ ageMs: answer.ageMs,
65
+ cache: 'joined',
66
+ expectedAmount: toEstimatedDelivery(scaledExpected),
67
+ ...(scaledMinimum === undefined ? {} : { minimumAmount: toEstimatedDelivery(scaledMinimum) }),
68
+ scaledFrom: { askedAmount: answer.askedAmount, expectedAmount: answer.expectedAmount },
69
+ };
70
+ };
71
+ /**
72
+ * Builds a fresh, empty {@link SingleFlightGroup}.
73
+ *
74
+ * @param config - see {@link SingleFlightConfig}
75
+ * @returns the group
76
+ */
77
+ export const createSingleFlightGroup = (config = {}) => {
78
+ const timeoutMs = config.timeoutMs ?? DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS;
79
+ // `estimate-ok` asks share ONE call per bucket, regardless of literal
80
+ // amount — matching "joins an in-flight call for another amount in the
81
+ // same bucket." `exact` asks share only with an identical literal amount,
82
+ // in their OWN map, so they never inherit an estimate-ok call's amount.
83
+ const estimateInFlightByBucket = new Map();
84
+ const exactInFlightByAmountKey = new Map();
85
+ const runWithTimeout = (key, run) => new Promise((resolve, reject) => {
86
+ let settled = false;
87
+ const cancelTimer = scheduleTimer(() => {
88
+ if (settled)
89
+ return;
90
+ settled = true;
91
+ reject(new SingleFlightTimeoutError(key, timeoutMs));
92
+ }, timeoutMs);
93
+ run().then((outcome) => {
94
+ if (settled)
95
+ return;
96
+ settled = true;
97
+ cancelTimer();
98
+ resolve(outcome);
99
+ }, (error) => {
100
+ if (settled)
101
+ return;
102
+ settled = true;
103
+ cancelTimer();
104
+ reject(error);
105
+ });
106
+ });
107
+ return {
108
+ ask: async (request, run) => {
109
+ if (request.precision === 'exact') {
110
+ const amountKey = `${request.bucketKey}#exact#${request.askedAmount.toString()}`;
111
+ const existing = exactInFlightByAmountKey.get(amountKey);
112
+ if (existing !== undefined) {
113
+ const outcome = await existing;
114
+ return { answer: answerForJoiner(outcome, request.askedAmount), joined: true };
115
+ }
116
+ const promise = runWithTimeout(amountKey, run);
117
+ exactInFlightByAmountKey.set(amountKey, promise);
118
+ try {
119
+ const outcome = await promise;
120
+ return { answer: outcome.answer, joined: false };
121
+ }
122
+ finally {
123
+ exactInFlightByAmountKey.delete(amountKey);
124
+ }
125
+ }
126
+ const existing = estimateInFlightByBucket.get(request.bucketKey);
127
+ if (existing !== undefined) {
128
+ const outcome = await existing;
129
+ return { answer: answerForJoiner(outcome, request.askedAmount), joined: true };
130
+ }
131
+ const promise = runWithTimeout(request.bucketKey, run);
132
+ estimateInFlightByBucket.set(request.bucketKey, promise);
133
+ try {
134
+ const outcome = await promise;
135
+ return { answer: outcome.answer, joined: false };
136
+ }
137
+ finally {
138
+ estimateInFlightByBucket.delete(request.bucketKey);
139
+ }
140
+ },
141
+ };
142
+ };