@imbingox/acex 0.4.0-beta.16 → 0.4.0-beta.18
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.
- package/CHANGELOG.md +12 -0
- package/docs/api.md +105 -7
- package/package.json +1 -1
- package/src/adapters/binance/adapter.ts +4 -1
- package/src/adapters/binance/market-catalog.ts +25 -20
- package/src/adapters/binance/private-adapter.ts +68 -53
- package/src/adapters/binance/rate-limit-topology.ts +257 -0
- package/src/adapters/binance/server-time.ts +20 -18
- package/src/client/runtime.ts +47 -6
- package/src/errors.ts +1 -0
- package/src/internal/async-event-bus.ts +75 -3
- package/src/internal/rate-limiter/snapshot.ts +67 -0
- package/src/internal/rate-limiter/state.ts +98 -0
- package/src/internal/rate-limiter/topology.ts +123 -0
- package/src/internal/rate-limiter/types.ts +49 -0
- package/src/internal/rate-limiter/usage.ts +48 -0
- package/src/internal/rate-limiter.ts +792 -74
- package/src/managers/account-manager.ts +43 -16
- package/src/managers/market-manager.ts +111 -7
- package/src/managers/order-manager.ts +42 -16
- package/src/types/account.ts +9 -2
- package/src/types/client.ts +8 -2
- package/src/types/market.ts +19 -4
- package/src/types/order.ts +9 -2
- package/src/types/shared.ts +209 -1
package/src/types/shared.ts
CHANGED
|
@@ -42,14 +42,178 @@ export interface RateLimitUsage {
|
|
|
42
42
|
orderCount?: Record<string, number>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Request priority used by rate-limit plans and per-request contexts.
|
|
47
|
+
*
|
|
48
|
+
* `"normal"` is the default path. `"cancel"` is intended for cancellation or
|
|
49
|
+
* other unwind traffic that may use reserved bucket headroom. `"risk"` is for
|
|
50
|
+
* risk/account maintenance traffic. Custom strings are allowed so venue
|
|
51
|
+
* adapters can add narrower priorities without changing the public union.
|
|
52
|
+
*/
|
|
53
|
+
export type RateLimitPriority = "normal" | "cancel" | "risk" | (string & {});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Logical bucket family reported by an exchange.
|
|
57
|
+
*
|
|
58
|
+
* `"request_weight"` tracks REST weight-style budgets. `"orders"` tracks order
|
|
59
|
+
* count budgets separately. Custom strings are allowed for venue-specific
|
|
60
|
+
* bucket families.
|
|
61
|
+
*/
|
|
62
|
+
export type RateLimitBucketKind = "request_weight" | "orders" | (string & {});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Scope dimensions that define how a bucket state is keyed.
|
|
66
|
+
*
|
|
67
|
+
* `"venue"` shares one bucket per venue, `"account"` adds accountId isolation,
|
|
68
|
+
* and `"endpoint"` adds endpointKey isolation.
|
|
69
|
+
*/
|
|
70
|
+
export type RateLimitScopeDimension = "venue" | "account" | "endpoint";
|
|
71
|
+
|
|
72
|
+
/** Priority-specific bucket headroom held back from other priorities. */
|
|
73
|
+
export interface RateLimitBucketReserve {
|
|
74
|
+
/** Priority that may consume the full published bucket limit. */
|
|
75
|
+
priority: RateLimitPriority;
|
|
76
|
+
/** Number of bucket units reserved from non-matching priorities. */
|
|
77
|
+
units: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Fixed-window budget bucket owned by a venue topology. */
|
|
81
|
+
export interface RateLimitBucketDescriptor {
|
|
82
|
+
/** Stable bucket id referenced by RateLimitCost.bucketId. */
|
|
83
|
+
id: string;
|
|
84
|
+
/** Exchange budget family, such as request weight or order count. */
|
|
85
|
+
kind: RateLimitBucketKind;
|
|
86
|
+
/** Published bucket capacity in the same units as RateLimitCost.cost. */
|
|
87
|
+
limit: number;
|
|
88
|
+
/** Fixed-window interval length in milliseconds. */
|
|
89
|
+
intervalMs: number;
|
|
90
|
+
/** Dimensions included when deriving the bucket state key. */
|
|
91
|
+
scope: readonly RateLimitScopeDimension[];
|
|
92
|
+
/**
|
|
93
|
+
* Fraction of limit the default limiter should normally target before
|
|
94
|
+
* reserve handling. Omitted means the limiter-wide default.
|
|
95
|
+
*/
|
|
96
|
+
utilizationTarget?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Optional priority reserve. Non-matching priorities use the target limit
|
|
99
|
+
* minus reserve.units, clamped to zero; the matching priority may use limit.
|
|
100
|
+
*/
|
|
101
|
+
reserve?: RateLimitBucketReserve;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Cost of one plan against one bucket. */
|
|
105
|
+
export interface RateLimitCost {
|
|
106
|
+
/** Bucket id declared in the same topology. */
|
|
107
|
+
bucketId: string;
|
|
108
|
+
/** Units consumed in that bucket when this plan is admitted. */
|
|
109
|
+
cost: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Request admission plan selected by an adapter for a semantic operation. */
|
|
113
|
+
export interface RateLimitPlan {
|
|
114
|
+
/** Stable semantic plan id, not necessarily identical to endpointKey. */
|
|
115
|
+
id: string;
|
|
116
|
+
/** Bucket costs that must all be admitted atomically. */
|
|
117
|
+
costs: readonly RateLimitCost[];
|
|
118
|
+
/** Default priority for requests using this plan. */
|
|
119
|
+
priority?: RateLimitPriority;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Venue-owned rate-limit topology.
|
|
124
|
+
*
|
|
125
|
+
* Buckets describe fixed-window budgets. Plans map adapter operations to the
|
|
126
|
+
* bucket costs consumed by one request.
|
|
127
|
+
*/
|
|
128
|
+
export interface RateLimitTopology {
|
|
129
|
+
/** Stable topology id, typically venue-scoped. */
|
|
130
|
+
id: string;
|
|
131
|
+
/** Bucket descriptors referenced by plans. */
|
|
132
|
+
buckets: readonly RateLimitBucketDescriptor[];
|
|
133
|
+
/** Operation plans that map requests to bucket costs. */
|
|
134
|
+
plans: readonly RateLimitPlan[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Opaque admission token returned by RateLimiter.beforeRequest().
|
|
139
|
+
*
|
|
140
|
+
* Adapters must not inspect or construct this token. They pass it back through
|
|
141
|
+
* RateLimitResponseContext.reservation or RateLimitTransportErrorContext.
|
|
142
|
+
*/
|
|
143
|
+
export interface RateLimitReservation {
|
|
144
|
+
readonly __opaqueRateLimitReservation?: never;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface RateLimitTopologyRegistry {
|
|
148
|
+
/**
|
|
149
|
+
* Registers a venue topology with the limiter.
|
|
150
|
+
*
|
|
151
|
+
* Re-registering an identical descriptor is idempotent. A conflicting bucket
|
|
152
|
+
* or plan descriptor should be rejected instead of overwritten.
|
|
153
|
+
*/
|
|
154
|
+
registerRateLimitTopology(topology: RateLimitTopology): void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Explicit no-token result for custom limiters that only wait or observe. */
|
|
158
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: Existing custom limiters return void; the SPI must keep that source-compatible.
|
|
159
|
+
export type RateLimitNoReservation = void;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Return type for RateLimiter.beforeRequest().
|
|
163
|
+
*
|
|
164
|
+
* Return void/Promise<void> when no token is needed. Return a reservation when
|
|
165
|
+
* later response/error hooks need to reconcile the admitted request.
|
|
166
|
+
*/
|
|
167
|
+
export type RateLimitBeforeRequestResult =
|
|
168
|
+
| Promise<RateLimitReservation | RateLimitNoReservation>
|
|
169
|
+
| RateLimitReservation
|
|
170
|
+
| RateLimitNoReservation;
|
|
171
|
+
|
|
172
|
+
/** Diagnostic snapshot for one registered bucket at the current scope. */
|
|
173
|
+
export interface RateLimitBucketSnapshot {
|
|
174
|
+
/** Bucket id from the registered descriptor. */
|
|
175
|
+
bucketId: string;
|
|
176
|
+
/** Bucket family from the registered descriptor. */
|
|
177
|
+
kind: RateLimitBucketKind;
|
|
178
|
+
/** Published bucket capacity. */
|
|
179
|
+
limit: number;
|
|
180
|
+
/** Fixed-window interval length in milliseconds. */
|
|
181
|
+
intervalMs: number;
|
|
182
|
+
/** Effective utilization target used for normal admission. */
|
|
183
|
+
utilizationTarget?: number;
|
|
184
|
+
/** Priority reserve copied from the descriptor, when configured. */
|
|
185
|
+
reserve?: RateLimitBucketReserve;
|
|
186
|
+
/** Units observed or pre-reserved in the active window. */
|
|
187
|
+
used?: number;
|
|
188
|
+
/** Active fixed-window start time as epoch milliseconds. */
|
|
189
|
+
windowStartMs?: number;
|
|
190
|
+
/** Active fixed-window end time as epoch milliseconds. */
|
|
191
|
+
windowEndMs?: number;
|
|
192
|
+
/** Epoch milliseconds until which this bucket is blocked. */
|
|
193
|
+
blockedUntil?: number;
|
|
194
|
+
/** Most recent Retry-After duration in milliseconds, when available. */
|
|
195
|
+
retryAfterMs?: number;
|
|
196
|
+
/** `"ok"` admits normally, `"rate_limited"` waits, `"banned"` is a ban. */
|
|
197
|
+
state: "ok" | "rate_limited" | "banned";
|
|
198
|
+
/** Last update time as epoch milliseconds. */
|
|
199
|
+
updatedAt?: number;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface RateLimitOptions {
|
|
203
|
+
utilizationTarget?: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
45
206
|
export interface RateLimitRequestContext {
|
|
46
207
|
scope: RateLimitScope;
|
|
208
|
+
planId?: string;
|
|
209
|
+
priority?: RateLimitPriority;
|
|
47
210
|
}
|
|
48
211
|
|
|
49
212
|
export interface RateLimitResponseContext {
|
|
50
213
|
status: number;
|
|
51
214
|
headers?: Headers;
|
|
52
215
|
usage?: RateLimitUsage;
|
|
216
|
+
reservation?: RateLimitReservation;
|
|
53
217
|
}
|
|
54
218
|
|
|
55
219
|
export interface RateLimitTransportErrorContext {
|
|
@@ -57,6 +221,8 @@ export interface RateLimitTransportErrorContext {
|
|
|
57
221
|
headers?: Headers;
|
|
58
222
|
retryAfterMs?: number;
|
|
59
223
|
usage?: RateLimitUsage;
|
|
224
|
+
reservation?: RateLimitReservation;
|
|
225
|
+
requestNotSent?: boolean;
|
|
60
226
|
}
|
|
61
227
|
|
|
62
228
|
export interface RateLimitSnapshot {
|
|
@@ -66,10 +232,38 @@ export interface RateLimitSnapshot {
|
|
|
66
232
|
retryAfterMs?: number;
|
|
67
233
|
state: "ok" | "rate_limited" | "banned";
|
|
68
234
|
updatedAt?: number;
|
|
235
|
+
buckets?: RateLimitBucketSnapshot[];
|
|
69
236
|
}
|
|
70
237
|
|
|
71
238
|
export interface RateLimiter {
|
|
72
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Waits for request admission and optionally returns an opaque reservation.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* const reservations = new WeakMap<RateLimitReservation, { cost: number }>();
|
|
245
|
+
*
|
|
246
|
+
* const limiter: RateLimiter = {
|
|
247
|
+
* async beforeRequest() {
|
|
248
|
+
* const reservation: RateLimitReservation = {};
|
|
249
|
+
* reservations.set(reservation, { cost: 1 });
|
|
250
|
+
* return reservation;
|
|
251
|
+
* },
|
|
252
|
+
* afterResponse(_ctx, response) {
|
|
253
|
+
* if (response.reservation) reservations.delete(response.reservation);
|
|
254
|
+
* },
|
|
255
|
+
* onTransportError(_ctx, error) {
|
|
256
|
+
* if (error.reservation && error.requestNotSent) {
|
|
257
|
+
* reservations.delete(error.reservation);
|
|
258
|
+
* }
|
|
259
|
+
* },
|
|
260
|
+
* getSnapshot(scope) {
|
|
261
|
+
* return { scope, state: "ok" };
|
|
262
|
+
* },
|
|
263
|
+
* };
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
beforeRequest(ctx: RateLimitRequestContext): RateLimitBeforeRequestResult;
|
|
73
267
|
afterResponse(
|
|
74
268
|
ctx: RateLimitRequestContext,
|
|
75
269
|
response: RateLimitResponseContext,
|
|
@@ -116,6 +310,7 @@ export interface CreateClientOptions {
|
|
|
116
310
|
/** Request/signing clock; local receivedAt/freshness clocks stay independent. */
|
|
117
311
|
clock?: TimeProvider;
|
|
118
312
|
rateLimiter?: RateLimiter;
|
|
313
|
+
rateLimit?: RateLimitOptions;
|
|
119
314
|
logger?: Logger;
|
|
120
315
|
logLevel?: LogLevel;
|
|
121
316
|
market?: MarketRuntimeOptions;
|
|
@@ -175,11 +370,24 @@ export interface StopOptions {
|
|
|
175
370
|
timeoutMs?: number;
|
|
176
371
|
}
|
|
177
372
|
|
|
373
|
+
export type EventStreamMode = "buffer" | "conflate";
|
|
374
|
+
|
|
375
|
+
export interface EventStreamOptions {
|
|
376
|
+
mode?: EventStreamMode;
|
|
377
|
+
maxBuffer?: number;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface BufferedEventStreamOptions {
|
|
381
|
+
maxBuffer?: number;
|
|
382
|
+
}
|
|
383
|
+
|
|
178
384
|
export interface AcexInternalError {
|
|
179
385
|
source: "client" | "market" | "account" | "order" | "adapter" | "runtime";
|
|
180
386
|
venue?: Venue;
|
|
181
387
|
accountId?: string;
|
|
182
388
|
symbol?: string;
|
|
389
|
+
stream?: string;
|
|
390
|
+
maxBuffer?: number;
|
|
183
391
|
error: Error;
|
|
184
392
|
ts: number;
|
|
185
393
|
}
|