@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.
@@ -14,6 +14,8 @@ import type {
14
14
  PrivateAccountDataConsumer,
15
15
  PrivateSubscriptionState,
16
16
  } from "../client/context.ts";
17
+ import { AcexError } from "../errors.ts";
18
+ import type { AsyncEventBusOverflowInfo } from "../internal/async-event-bus.ts";
17
19
  import { AsyncEventBus } from "../internal/async-event-bus.ts";
18
20
  import { toCanonical } from "../internal/decimal.ts";
19
21
  import { matchesAccountFilter } from "../internal/filters.ts";
@@ -125,23 +127,33 @@ export class AccountManagerImpl
125
127
  this.context = context;
126
128
 
127
129
  this.events = {
128
- status: (filter) =>
129
- this.accountStatusBus.stream((event) =>
130
- matchesAccountFilter(
131
- { accountId: event.accountId, venue: event.venue },
132
- filter,
133
- ),
130
+ status: (filter, options) =>
131
+ this.accountStatusBus.stream(
132
+ (event) =>
133
+ matchesAccountFilter(
134
+ { accountId: event.accountId, venue: event.venue },
135
+ filter,
136
+ ),
137
+ {
138
+ maxBuffer: options?.maxBuffer,
139
+ onOverflow: this.createOverflowHandler("account.status"),
140
+ },
134
141
  ),
135
- updates: (filter) =>
136
- this.accountBus.stream((event) =>
137
- matchesAccountFilter(
138
- {
139
- accountId: event.accountId,
140
- venue: event.venue,
141
- symbol: "symbol" in event ? event.symbol : undefined,
142
- },
143
- filter,
144
- ),
142
+ updates: (filter, options) =>
143
+ this.accountBus.stream(
144
+ (event) =>
145
+ matchesAccountFilter(
146
+ {
147
+ accountId: event.accountId,
148
+ venue: event.venue,
149
+ symbol: "symbol" in event ? event.symbol : undefined,
150
+ },
151
+ filter,
152
+ ),
153
+ {
154
+ maxBuffer: options?.maxBuffer,
155
+ onOverflow: this.createOverflowHandler("account.updates"),
156
+ },
145
157
  ),
146
158
  };
147
159
  }
@@ -872,4 +884,19 @@ export class AccountManagerImpl
872
884
  this.accountStatusBus.publish(event);
873
885
  this.context.publishHealthEvent(event);
874
886
  }
887
+
888
+ private createOverflowHandler(
889
+ stream: string,
890
+ ): (info: AsyncEventBusOverflowInfo) => void {
891
+ return ({ maxBuffer }) => {
892
+ const error = new AcexError(
893
+ "EVENT_BUFFER_OVERFLOW",
894
+ `Event stream buffer overflow: ${stream}`,
895
+ );
896
+ this.context.publishRuntimeError("account", error, {
897
+ stream,
898
+ maxBuffer,
899
+ });
900
+ };
901
+ }
875
902
  }
@@ -19,10 +19,15 @@ import {
19
19
  buildAcexErrorDetails,
20
20
  formatAcexErrorMessage,
21
21
  } from "../errors.ts";
22
+ import type {
23
+ AsyncEventBusOverflowInfo,
24
+ AsyncEventBusStreamOptions,
25
+ } from "../internal/async-event-bus.ts";
22
26
  import { AsyncEventBus } from "../internal/async-event-bus.ts";
23
27
  import { toCanonical } from "../internal/decimal.ts";
24
28
  import { matchesMarketFilter } from "../internal/filters.ts";
25
29
  import type {
30
+ EventStreamOptions,
26
31
  FundingRateSnapshot,
27
32
  FundingRateUpdatedEvent,
28
33
  L1Book,
@@ -67,6 +72,14 @@ interface MarketRecord {
67
72
  status: MarketDataStatus;
68
73
  l1BookStream?: StreamHandle;
69
74
  fundingRateStream?: StreamHandle;
75
+ lastPublishedStatusKey?: MarketStatusPublicationKey;
76
+ }
77
+
78
+ interface MarketStatusPublicationKey {
79
+ activity: MarketDataStatus["activity"];
80
+ ready: MarketDataStatus["ready"];
81
+ freshness: MarketDataStatus["freshness"];
82
+ reason: MarketDataStatus["reason"];
70
83
  }
71
84
 
72
85
  interface CatalogFetchResult {
@@ -85,10 +98,38 @@ function marketKey(input: MarketKeyInput): string {
85
98
  return `${input.venue}:${input.symbol}`;
86
99
  }
87
100
 
101
+ function marketEventConflateKey(event: MarketEvent): string {
102
+ return `${event.type}:${marketKey(event)}`;
103
+ }
104
+
88
105
  function cloneMarketStatus(status: MarketDataStatus): MarketDataStatus {
89
106
  return { ...status };
90
107
  }
91
108
 
109
+ function statusPublicationKey(
110
+ status: MarketDataStatus,
111
+ ): MarketStatusPublicationKey {
112
+ return {
113
+ activity: status.activity,
114
+ ready: status.ready,
115
+ freshness: status.freshness,
116
+ reason: status.reason,
117
+ };
118
+ }
119
+
120
+ function sameStatusPublicationKey(
121
+ current: MarketStatusPublicationKey,
122
+ previous: MarketStatusPublicationKey | undefined,
123
+ ): boolean {
124
+ return (
125
+ previous !== undefined &&
126
+ current.activity === previous.activity &&
127
+ current.ready === previous.ready &&
128
+ current.freshness === previous.freshness &&
129
+ current.reason === previous.reason
130
+ );
131
+ }
132
+
92
133
  function cloneStreamStatus(
93
134
  status: MarketDataStreamStatus,
94
135
  ): MarketDataStreamStatus {
@@ -153,23 +194,49 @@ export class MarketManagerImpl
153
194
  options.l1ReconnectMaxDelayMs ?? DEFAULT_L1_RECONNECT_MAX_DELAY_MS;
154
195
 
155
196
  this.events = {
156
- all: (filter) =>
157
- this.marketBus.stream((event) => matchesMarketFilter(event, filter)),
158
- fundingRateUpdates: (filter) =>
197
+ all: (filter, options) =>
198
+ this.marketBus.stream(
199
+ (event) => matchesMarketFilter(event, filter),
200
+ this.createStreamOptions(
201
+ "market.all",
202
+ options,
203
+ "buffer",
204
+ marketEventConflateKey,
205
+ ),
206
+ ),
207
+ fundingRateUpdates: (filter, options) =>
159
208
  this.marketBus.stream(
160
209
  (event): event is FundingRateUpdatedEvent =>
161
210
  event.type === "funding_rate.updated" &&
162
211
  matchesMarketFilter(event, filter),
212
+ this.createStreamOptions(
213
+ "market.fundingRateUpdates",
214
+ options,
215
+ "conflate",
216
+ marketKey,
217
+ ),
163
218
  ),
164
- l1BookUpdates: (filter) =>
219
+ l1BookUpdates: (filter, options) =>
165
220
  this.marketBus.stream(
166
221
  (event): event is L1BookUpdatedEvent =>
167
222
  event.type === "l1_book.updated" &&
168
223
  matchesMarketFilter(event, filter),
224
+ this.createStreamOptions(
225
+ "market.l1BookUpdates",
226
+ options,
227
+ "conflate",
228
+ marketKey,
229
+ ),
169
230
  ),
170
- status: (filter) =>
171
- this.marketStatusBus.stream((event) =>
172
- matchesMarketFilter(event, filter),
231
+ status: (filter, options) =>
232
+ this.marketStatusBus.stream(
233
+ (event) => matchesMarketFilter(event, filter),
234
+ this.createStreamOptions(
235
+ "market.status",
236
+ options,
237
+ "buffer",
238
+ marketKey,
239
+ ),
173
240
  ),
174
241
  };
175
242
  }
@@ -1064,6 +1131,14 @@ export class MarketManagerImpl
1064
1131
  record.status.lastReadyAt = this.resolveLastReadyAt(record);
1065
1132
  }
1066
1133
 
1134
+ const publicationKey = statusPublicationKey(record.status);
1135
+ if (
1136
+ sameStatusPublicationKey(publicationKey, record.lastPublishedStatusKey)
1137
+ ) {
1138
+ return;
1139
+ }
1140
+
1141
+ record.lastPublishedStatusKey = publicationKey;
1067
1142
  this.publishStatus(record);
1068
1143
  }
1069
1144
 
@@ -1184,6 +1259,35 @@ export class MarketManagerImpl
1184
1259
  this.context.publishHealthEvent(event);
1185
1260
  }
1186
1261
 
1262
+ private createStreamOptions<U extends { venue: Venue; symbol: string }>(
1263
+ stream: string,
1264
+ options: EventStreamOptions | undefined,
1265
+ defaultMode: "buffer" | "conflate",
1266
+ conflateKey: (event: U) => string,
1267
+ ): AsyncEventBusStreamOptions<U> {
1268
+ return {
1269
+ mode: options?.mode ?? defaultMode,
1270
+ maxBuffer: options?.maxBuffer,
1271
+ conflateKey,
1272
+ onOverflow: this.createOverflowHandler(stream),
1273
+ };
1274
+ }
1275
+
1276
+ private createOverflowHandler(
1277
+ stream: string,
1278
+ ): (info: AsyncEventBusOverflowInfo) => void {
1279
+ return ({ maxBuffer }) => {
1280
+ const error = new AcexError(
1281
+ "EVENT_BUFFER_OVERFLOW",
1282
+ `Event stream buffer overflow: ${stream}`,
1283
+ );
1284
+ this.context.publishRuntimeError("market", error, {
1285
+ stream,
1286
+ maxBuffer,
1287
+ });
1288
+ };
1289
+ }
1290
+
1187
1291
  private async resumeStreams(): Promise<void> {
1188
1292
  for (const record of this.records.values()) {
1189
1293
  const market = record.market;
@@ -17,6 +17,7 @@ import {
17
17
  buildAcexErrorDetails,
18
18
  formatAcexErrorMessage,
19
19
  } from "../errors.ts";
20
+ import type { AsyncEventBusOverflowInfo } from "../internal/async-event-bus.ts";
20
21
  import { AsyncEventBus } from "../internal/async-event-bus.ts";
21
22
  import { matchesOrderFilter } from "../internal/filters.ts";
22
23
  import { isTransportError } from "../internal/http-client.ts";
@@ -116,23 +117,33 @@ export class OrderManagerImpl
116
117
  );
117
118
 
118
119
  this.events = {
119
- status: (filter) =>
120
- this.orderStatusBus.stream((event) =>
121
- matchesOrderFilter(
122
- { accountId: event.accountId, venue: event.venue },
123
- filter,
124
- ),
120
+ status: (filter, options) =>
121
+ this.orderStatusBus.stream(
122
+ (event) =>
123
+ matchesOrderFilter(
124
+ { accountId: event.accountId, venue: event.venue },
125
+ filter,
126
+ ),
127
+ {
128
+ maxBuffer: options?.maxBuffer,
129
+ onOverflow: this.createOverflowHandler("order.status"),
130
+ },
125
131
  ),
126
- updates: (filter) =>
127
- this.orderBus.stream((event) =>
128
- matchesOrderFilter(
129
- {
130
- accountId: event.accountId,
131
- venue: event.venue,
132
- symbol: "symbol" in event ? event.symbol : undefined,
133
- },
134
- filter,
135
- ),
132
+ updates: (filter, options) =>
133
+ this.orderBus.stream(
134
+ (event) =>
135
+ matchesOrderFilter(
136
+ {
137
+ accountId: event.accountId,
138
+ venue: event.venue,
139
+ symbol: "symbol" in event ? event.symbol : undefined,
140
+ },
141
+ filter,
142
+ ),
143
+ {
144
+ maxBuffer: options?.maxBuffer,
145
+ onOverflow: this.createOverflowHandler("order.updates"),
146
+ },
136
147
  ),
137
148
  };
138
149
  }
@@ -1270,6 +1281,21 @@ export class OrderManagerImpl
1270
1281
  },
1271
1282
  };
1272
1283
  }
1284
+
1285
+ private createOverflowHandler(
1286
+ stream: string,
1287
+ ): (info: AsyncEventBusOverflowInfo) => void {
1288
+ return ({ maxBuffer }) => {
1289
+ const error = new AcexError(
1290
+ "EVENT_BUFFER_OVERFLOW",
1291
+ `Event stream buffer overflow: ${stream}`,
1292
+ );
1293
+ this.context.publishRuntimeError("order", error, {
1294
+ stream,
1295
+ maxBuffer,
1296
+ });
1297
+ };
1298
+ }
1273
1299
  }
1274
1300
 
1275
1301
  function isOrderErrorCode(
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ BufferedEventStreamOptions,
2
3
  PrivateRuntimeReason,
3
4
  PrivateRuntimeStatus,
4
5
  SubscriptionActivity,
@@ -158,8 +159,14 @@ export type AccountEvent =
158
159
  | AccountSnapshotReplacedEvent;
159
160
 
160
161
  export interface AccountEventStreams {
161
- updates(filter?: AccountEventFilter): AsyncIterable<AccountEvent>;
162
- status(filter?: AccountEventFilter): AsyncIterable<AccountStatusChangedEvent>;
162
+ updates(
163
+ filter?: AccountEventFilter,
164
+ options?: BufferedEventStreamOptions,
165
+ ): AsyncIterable<AccountEvent>;
166
+ status(
167
+ filter?: AccountEventFilter,
168
+ options?: BufferedEventStreamOptions,
169
+ ): AsyncIterable<AccountStatusChangedEvent>;
163
170
  }
164
171
 
165
172
  export interface AccountManager {
@@ -18,6 +18,7 @@ import type {
18
18
  import type {
19
19
  AccountCredentials,
20
20
  AcexInternalError,
21
+ BufferedEventStreamOptions,
21
22
  ClientStatus,
22
23
  CreateClientOptions,
23
24
  RegisterAccountInput,
@@ -54,8 +55,13 @@ export interface HealthEventFilter {
54
55
  }
55
56
 
56
57
  export interface ClientEventStreams {
57
- health(filter?: HealthEventFilter): AsyncIterable<HealthEvent>;
58
- errors(): AsyncIterable<AcexInternalError>;
58
+ health(
59
+ filter?: HealthEventFilter,
60
+ options?: BufferedEventStreamOptions,
61
+ ): AsyncIterable<HealthEvent>;
62
+ errors(
63
+ options?: BufferedEventStreamOptions,
64
+ ): AsyncIterable<AcexInternalError>;
59
65
  }
60
66
 
61
67
  export type VenueRuntimeStatus = "available" | "type_only" | "reserved";
@@ -1,6 +1,11 @@
1
1
  import type BigNumber from "bignumber.js";
2
2
  import type { AcexError } from "../errors.ts";
3
- import type { MarketFreshness, SubscriptionActivity, Venue } from "./shared.ts";
3
+ import type {
4
+ EventStreamOptions,
5
+ MarketFreshness,
6
+ SubscriptionActivity,
7
+ Venue,
8
+ } from "./shared.ts";
4
9
 
5
10
  export type MarketType = "spot" | "swap" | "future";
6
11
 
@@ -170,12 +175,22 @@ export type MarketEvent =
170
175
  | MarketStatusChangedEvent;
171
176
 
172
177
  export interface MarketEventStreams {
173
- l1BookUpdates(filter?: MarketEventFilter): AsyncIterable<L1BookUpdatedEvent>;
178
+ l1BookUpdates(
179
+ filter?: MarketEventFilter,
180
+ options?: EventStreamOptions,
181
+ ): AsyncIterable<L1BookUpdatedEvent>;
174
182
  fundingRateUpdates(
175
183
  filter?: MarketEventFilter,
184
+ options?: EventStreamOptions,
176
185
  ): AsyncIterable<FundingRateUpdatedEvent>;
177
- status(filter?: MarketEventFilter): AsyncIterable<MarketStatusChangedEvent>;
178
- all(filter?: MarketEventFilter): AsyncIterable<MarketEvent>;
186
+ status(
187
+ filter?: MarketEventFilter,
188
+ options?: EventStreamOptions,
189
+ ): AsyncIterable<MarketStatusChangedEvent>;
190
+ all(
191
+ filter?: MarketEventFilter,
192
+ options?: EventStreamOptions,
193
+ ): AsyncIterable<MarketEvent>;
179
194
  }
180
195
 
181
196
  export interface MarketManager {
@@ -1,5 +1,6 @@
1
1
  import type { PositionSide } from "./account.ts";
2
2
  import type {
3
+ BufferedEventStreamOptions,
3
4
  PrivateRuntimeReason,
4
5
  PrivateRuntimeStatus,
5
6
  SubscriptionActivity,
@@ -160,8 +161,14 @@ export type OrderEvent =
160
161
  | OrderSnapshotReplacedEvent;
161
162
 
162
163
  export interface OrderEventStreams {
163
- updates(filter?: OrderEventFilter): AsyncIterable<OrderEvent>;
164
- status(filter?: OrderEventFilter): AsyncIterable<OrderStatusChangedEvent>;
164
+ updates(
165
+ filter?: OrderEventFilter,
166
+ options?: BufferedEventStreamOptions,
167
+ ): AsyncIterable<OrderEvent>;
168
+ status(
169
+ filter?: OrderEventFilter,
170
+ options?: BufferedEventStreamOptions,
171
+ ): AsyncIterable<OrderStatusChangedEvent>;
165
172
  }
166
173
 
167
174
  export interface OrderManager {