@agg-build/sdk 4.25.0 → 4.27.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 (42) hide show
  1. package/dist/agg-websocket-BstVWtLA.d.mts +6004 -0
  2. package/dist/agg-websocket-BstVWtLA.d.mts.map +1 -0
  3. package/dist/agg-websocket-BstVWtLA.d.ts +6004 -0
  4. package/dist/agg-websocket-BstVWtLA.d.ts.map +1 -0
  5. package/dist/agg-websocket-CK2iXfti.mjs +1427 -0
  6. package/dist/agg-websocket-CK2iXfti.mjs.map +1 -0
  7. package/dist/agg-websocket-DJbIfxKb.js +1478 -0
  8. package/dist/browser.d.mts +67 -66
  9. package/dist/browser.d.mts.map +1 -0
  10. package/dist/browser.d.ts +67 -66
  11. package/dist/browser.d.ts.map +1 -0
  12. package/dist/browser.js +720 -2047
  13. package/dist/browser.mjs +423 -799
  14. package/dist/browser.mjs.map +1 -0
  15. package/dist/candle-builder-BNewIe9K.mjs +148 -0
  16. package/dist/candle-builder-BNewIe9K.mjs.map +1 -0
  17. package/dist/candle-builder-BULYdxB4.js +151 -0
  18. package/dist/index.d.mts +2 -1342
  19. package/dist/index.d.ts +2 -1342
  20. package/dist/index.js +2794 -4656
  21. package/dist/index.mjs +2788 -2486
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/market-data.d.mts +6 -6
  24. package/dist/market-data.d.mts.map +1 -0
  25. package/dist/market-data.d.ts +6 -6
  26. package/dist/market-data.d.ts.map +1 -0
  27. package/dist/market-data.js +314 -1774
  28. package/dist/market-data.mjs +314 -350
  29. package/dist/market-data.mjs.map +1 -0
  30. package/dist/server.d.mts +235 -234
  31. package/dist/server.d.mts.map +1 -0
  32. package/dist/server.d.ts +235 -234
  33. package/dist/server.d.ts.map +1 -0
  34. package/dist/server.js +168 -296
  35. package/dist/server.mjs +163 -234
  36. package/dist/server.mjs.map +1 -0
  37. package/package.json +4 -4
  38. package/dist/agg-websocket-Bxt9PzTz.d.mts +0 -4479
  39. package/dist/agg-websocket-Bxt9PzTz.d.ts +0 -4479
  40. package/dist/chunk-QXBVA5TW.mjs +0 -2073
  41. package/dist/chunk-VD5UTAVD.mjs +0 -158
  42. package/dist/chunk-WNQUEZJF.mjs +0 -58
@@ -1,352 +1,316 @@
1
- import {
2
- CandleBuilder
3
- } from "./chunk-VD5UTAVD.mjs";
4
- import {
5
- AggWebSocket,
6
- decodeChartHistory,
7
- loadImmutableHistory,
8
- readChartHistoryText
9
- } from "./chunk-QXBVA5TW.mjs";
10
- import {
11
- __async,
12
- __spreadProps,
13
- __spreadValues
14
- } from "./chunk-WNQUEZJF.mjs";
15
-
16
- // src/market-data/engine.ts
17
- var intervals = ["1m", "5m", "1h", "1d"];
1
+ import { a as loadImmutableHistory, o as decodeChartHistory, s as readChartHistoryText, t as AggWebSocket } from "./agg-websocket-CK2iXfti.mjs";
2
+ import { t as CandleBuilder } from "./candle-builder-BNewIe9K.mjs";
3
+ //#region src/market-data/engine.ts
4
+ const intervals = [
5
+ "1m",
6
+ "5m",
7
+ "1h",
8
+ "1d"
9
+ ];
10
+ /** Framework-independent processing. The native worker and replay tests run this exact engine. */
18
11
  var MarketDataEngine = class {
19
- constructor() {
20
- this.socket = null;
21
- this.generation = 0;
22
- this.receivedAtMs = 0;
23
- this.revision = 0;
24
- this.deliveredClosed = /* @__PURE__ */ new Map();
25
- this.subscriptions = /* @__PURE__ */ new Map();
26
- this.builders = /* @__PURE__ */ new Map();
27
- this.dirty = /* @__PURE__ */ new Map();
28
- this.inFlight = false;
29
- this.deliveryToken = null;
30
- this.disposed = false;
31
- this.history = /* @__PURE__ */ new Map();
32
- this.measurements = {
33
- received: 0,
34
- published: 0,
35
- coalesced: 0,
36
- maxPendingOutcomes: 0,
37
- maxInFlight: 0
38
- };
39
- }
40
- start(options, event) {
41
- this.dispose();
42
- this.disposed = false;
43
- this.generation = 0;
44
- this.revision = 0;
45
- const callbacks = {};
46
- const names = [
47
- "onConnected",
48
- "onAuthenticated",
49
- "onHeartbeat",
50
- "onSubscribed",
51
- "onUnsubscribed",
52
- "onMarketResolved",
53
- "onError",
54
- "onOrderSubmitted",
55
- "onBalanceUpdate",
56
- "onOrderEvent",
57
- "onRedeemEvent",
58
- "onWithdrawalLifecycle",
59
- "onArbMarket",
60
- "onArbFeed",
61
- "onDiagnostics",
62
- "onConnectionStateChange"
63
- ];
64
- for (const name of names) {
65
- Object.assign(callbacks, {
66
- [name]: (...args) => {
67
- var _a;
68
- if (name === "onConnectionStateChange" && args[0] === false) {
69
- for (const builder of this.builders.values()) builder.reset();
70
- this.dirty.clear();
71
- this.deliveredClosed.clear();
72
- }
73
- if (name === "onMarketResolved") {
74
- const id = args[0].outcomeId;
75
- (_a = this.builders.get(id)) == null ? void 0 : _a.dispose();
76
- this.builders.delete(id);
77
- this.dirty.delete(id);
78
- }
79
- if (name === "onDiagnostics") {
80
- const diagnostic = args[0];
81
- if (diagnostic.outcomeId && ["seq_gap", "checksum_mismatch"].includes(diagnostic.type))
82
- this.mark(diagnostic.outcomeId, false);
83
- }
84
- event({ name, args, revision: this.revision });
85
- }
86
- });
87
- }
88
- callbacks.onFrameReceived = (receivedAtMs) => {
89
- this.receivedAtMs = receivedAtMs;
90
- this.revision++;
91
- };
92
- callbacks.onBookTick = (id, tick) => {
93
- if (tick.midpoint != null) this.builder(id).addMidpoint(tick.midpoint, tick.timestamp);
94
- this.mark(id, tick.source === "snapshot");
95
- };
96
- callbacks.onTrade = (trade) => {
97
- this.builder(trade.outcomeId).addTrade(trade.price, trade.size, trade.timestamp);
98
- this.mark(trade.outcomeId, false, trade);
99
- };
100
- this.socket = new AggWebSocket(__spreadProps(__spreadValues({}, options), { callbacks, deferBookViews: true }));
101
- }
102
- observeViews(observer) {
103
- this.observer = observer;
104
- }
105
- setSubscriptions(subscriptions, generation) {
106
- var _a;
107
- if (generation < this.generation) return;
108
- this.generation = generation;
109
- const desired = new Map(subscriptions.map((sub) => [`${sub.channel}:${sub.id}`, sub]));
110
- for (const [key, unsubscribe] of this.subscriptions) {
111
- if (!desired.has(key)) {
112
- unsubscribe();
113
- this.subscriptions.delete(key);
114
- }
115
- }
116
- for (const [key, sub] of desired) {
117
- if (this.subscriptions.has(key) || !this.socket) continue;
118
- const unsubscribe = sub.channel === "arb" ? this.socket.subscribeArb(sub.id, () => {
119
- }) : sub.channel === "arb-feed" ? this.socket.subscribeArbFeed(() => {
120
- }) : this.socket.subscribe(sub.id, sub.channel);
121
- this.subscriptions.set(key, unsubscribe);
122
- }
123
- for (const sub of subscriptions)
124
- if (sub.channel === "orderbook" && ((_a = this.socket) == null ? void 0 : _a.getBook(sub.id))) this.mark(sub.id, false);
125
- const outcomes = new Set(
126
- subscriptions.filter((sub) => sub.channel === "orderbook" || sub.channel === "trades").map((sub) => sub.id)
127
- );
128
- for (const id of this.dirty.keys()) if (!outcomes.has(id)) this.dirty.delete(id);
129
- for (const id of this.deliveredClosed.keys())
130
- if (!outcomes.has(id)) this.deliveredClosed.delete(id);
131
- for (const [id, builder] of this.builders)
132
- if (!outcomes.has(id)) {
133
- builder.dispose();
134
- this.builders.delete(id);
135
- this.dirty.delete(id);
136
- this.deliveredClosed.delete(id);
137
- }
138
- }
139
- authenticate(token) {
140
- var _a;
141
- (_a = this.socket) == null ? void 0 : _a.authenticate(token);
142
- }
143
- resnapshot(ids) {
144
- var _a;
145
- (_a = this.socket) == null ? void 0 : _a.resnapshot(ids);
146
- }
147
- connect() {
148
- var _a;
149
- (_a = this.socket) == null ? void 0 : _a.connect();
150
- }
151
- disconnect() {
152
- var _a;
153
- (_a = this.socket) == null ? void 0 : _a.disconnect();
154
- }
155
- resume() {
156
- var _a;
157
- (_a = this.socket) == null ? void 0 : _a.resnapshot([...this.builders.keys()]);
158
- }
159
- getMeasurements() {
160
- var _a, _b, _c, _d, _e;
161
- const socket = (_b = (_a = this.socket) == null ? void 0 : _a.getDiagnostics()) != null ? _b : null;
162
- return __spreadProps(__spreadValues({}, this.measurements), {
163
- activeSubscriptions: this.subscriptions.size,
164
- activeOutcomes: (/* @__PURE__ */ new Set([
165
- ...(_c = socket == null ? void 0 : socket.orderbookOutcomeIds) != null ? _c : [],
166
- ...(_d = socket == null ? void 0 : socket.tradeOutcomeIds) != null ? _d : []
167
- ])).size,
168
- cachedBooks: (_e = socket == null ? void 0 : socket.cachedBooks) != null ? _e : 0,
169
- builders: this.builders.size,
170
- historyTasks: this.history.size,
171
- pendingOutcomes: this.dirty.size,
172
- inFlightDeliveries: this.inFlight ? 1 : 0,
173
- retainedCandleOutcomes: this.deliveredClosed.size,
174
- socket
175
- });
176
- }
177
- decodeHistory(payload) {
178
- return decodeChartHistory(payload);
179
- }
180
- /** Fetch/decode on the processing side; newer range generations abort obsolete fetches. */
181
- loadHistoryRange(request) {
182
- return __async(this, null, function* () {
183
- const previous = this.history.get(request.key);
184
- if (previous && previous.generation > request.generation) return null;
185
- previous == null ? void 0 : previous.controller.abort();
186
- const controller = new AbortController();
187
- const entry = { generation: request.generation, controller };
188
- this.history.set(request.key, entry);
189
- const timeout = setTimeout(() => controller.abort(), 1e4);
190
- try {
191
- const response = yield fetch(request.url, {
192
- headers: request.headers,
193
- signal: controller.signal
194
- });
195
- if (!response.ok) throw new Error(`History request failed (${response.status})`);
196
- const body = decodeChartHistory(yield readChartHistoryText(response));
197
- if (body.data.length > 4096) throw new Error("Invalid or unbounded history response");
198
- if (this.history.get(request.key) !== entry || this.disposed) return null;
199
- return __spreadProps(__spreadValues({}, body), { data: [...body.data].sort((a, b) => a.t - b.t) });
200
- } finally {
201
- clearTimeout(timeout);
202
- if (this.history.get(request.key) === entry) this.history.delete(request.key);
203
- }
204
- });
205
- }
206
- loadImmutableRange(key, generation, manifest) {
207
- return __async(this, null, function* () {
208
- const previous = this.history.get(key);
209
- if (previous && previous.generation > generation) throw new Error("Obsolete history range");
210
- previous == null ? void 0 : previous.controller.abort();
211
- const entry = { generation, controller: new AbortController() };
212
- this.history.set(key, entry);
213
- const timer = setTimeout(() => entry.controller.abort(), 1e4);
214
- try {
215
- const data = yield loadImmutableHistory({ manifest, signal: entry.controller.signal });
216
- if (this.history.get(key) !== entry || this.disposed)
217
- throw new Error("Obsolete history range");
218
- return data;
219
- } finally {
220
- clearTimeout(timer);
221
- if (this.history.get(key) === entry) this.history.delete(key);
222
- }
223
- });
224
- }
225
- cancelHistoryRange(key, generation) {
226
- const entry = this.history.get(key);
227
- if (!entry || generation !== void 0 && entry.generation !== generation) return;
228
- entry.controller.abort();
229
- this.history.delete(key);
230
- }
231
- dispose() {
232
- var _a, _b;
233
- this.disposed = true;
234
- (_a = this.cancelDelivery) == null ? void 0 : _a.call(this);
235
- this.cancelDelivery = void 0;
236
- this.deliveryToken = null;
237
- this.inFlight = false;
238
- this.observer = void 0;
239
- (_b = this.socket) == null ? void 0 : _b.destroy();
240
- this.socket = null;
241
- if (this.timer) clearTimeout(this.timer);
242
- this.timer = void 0;
243
- this.subscriptions.clear();
244
- this.dirty.clear();
245
- this.deliveredClosed.clear();
246
- for (const builder of this.builders.values()) builder.dispose();
247
- this.builders.clear();
248
- for (const request of this.history.values()) request.controller.abort();
249
- this.history.clear();
250
- }
251
- builder(id) {
252
- let builder = this.builders.get(id);
253
- if (!builder) {
254
- builder = new CandleBuilder();
255
- this.builders.set(id, builder);
256
- }
257
- return builder;
258
- }
259
- mark(id, snapshot, trade) {
260
- var _a, _b, _c;
261
- const previous = this.dirty.get(id);
262
- this.measurements.received++;
263
- if (previous) this.measurements.coalesced++;
264
- this.dirty.set(id, {
265
- snapshot: snapshot || !!(previous == null ? void 0 : previous.snapshot),
266
- receivedAtMs: (_a = previous == null ? void 0 : previous.receivedAtMs) != null ? _a : this.receivedAtMs || Date.now(),
267
- trades: trade ? [trade, ...(_b = previous == null ? void 0 : previous.trades) != null ? _b : []].slice(0, 50) : (_c = previous == null ? void 0 : previous.trades) != null ? _c : []
268
- });
269
- this.measurements.maxPendingOutcomes = Math.max(
270
- this.measurements.maxPendingOutcomes,
271
- this.dirty.size
272
- );
273
- this.schedule();
274
- }
275
- schedule() {
276
- if (this.timer || this.inFlight || this.disposed || !this.observer) return;
277
- this.timer = setTimeout(() => {
278
- this.timer = void 0;
279
- void this.publish();
280
- }, 50);
281
- }
282
- publish() {
283
- return __async(this, null, function* () {
284
- var _a, _b;
285
- if (this.inFlight || this.disposed || !this.observer || !this.dirty.size) return;
286
- const views = [...this.dirty].map(([outcomeId, dirty]) => {
287
- var _a2, _b2, _c;
288
- const builder = this.builder(outcomeId);
289
- return __spreadProps(__spreadValues({
290
- outcomeId,
291
- revision: this.revision,
292
- integrity: ((_a2 = this.socket) == null ? void 0 : _a2.isResyncing(outcomeId)) ? "resyncing" : "ok",
293
- book: (_c = (_b2 = this.socket) == null ? void 0 : _b2.getBook(outcomeId)) != null ? _c : null
294
- }, dirty), {
295
- candles: Object.fromEntries(
296
- intervals.map((interval) => [
297
- interval,
298
- {
299
- forming: builder.getForming(interval) ? __spreadValues({}, builder.getForming(interval)) : null,
300
- // Bounded recovery tail. Full historical ranges never travel per tick.
301
- closed: builder.getClosed(interval).filter(
302
- (candle) => {
303
- var _a3, _b3;
304
- return candle.openTime > ((_b3 = (_a3 = this.deliveredClosed.get(outcomeId)) == null ? void 0 : _a3[interval]) != null ? _b3 : -Infinity);
305
- }
306
- ).map((candle) => __spreadValues({}, candle))
307
- }
308
- ])
309
- )
310
- });
311
- });
312
- const generation = this.generation;
313
- this.dirty.clear();
314
- this.inFlight = true;
315
- const token = {};
316
- this.deliveryToken = token;
317
- const cancelled = new Promise((resolve) => {
318
- this.cancelDelivery = resolve;
319
- });
320
- this.measurements.maxInFlight = 1;
321
- try {
322
- yield Promise.race([
323
- this.observer({ generation, views, publishedAtMs: Date.now() }),
324
- cancelled
325
- ]);
326
- if (this.disposed || this.deliveryToken !== token) return;
327
- for (const view of generation === this.generation ? views : []) {
328
- const delivered = (_a = this.deliveredClosed.get(view.outcomeId)) != null ? _a : {};
329
- for (const interval of intervals) {
330
- const last = view.candles[interval].closed.at(-1);
331
- if (last) delivered[interval] = last.openTime;
332
- }
333
- this.deliveredClosed.set(view.outcomeId, delivered);
334
- }
335
- this.measurements.published++;
336
- } catch (e) {
337
- if (!this.disposed && this.deliveryToken === token) (_b = this.socket) == null ? void 0 : _b.disconnect();
338
- } finally {
339
- if (this.deliveryToken === token) {
340
- this.deliveryToken = null;
341
- this.cancelDelivery = void 0;
342
- this.inFlight = false;
343
- if (this.dirty.size) this.schedule();
344
- }
345
- }
346
- });
347
- }
348
- };
349
- export {
350
- MarketDataEngine,
351
- decodeChartHistory
12
+ constructor() {
13
+ this.socket = null;
14
+ this.generation = 0;
15
+ this.receivedAtMs = 0;
16
+ this.revision = 0;
17
+ this.deliveredClosed = /* @__PURE__ */ new Map();
18
+ this.subscriptions = /* @__PURE__ */ new Map();
19
+ this.builders = /* @__PURE__ */ new Map();
20
+ this.dirty = /* @__PURE__ */ new Map();
21
+ this.inFlight = false;
22
+ this.deliveryToken = null;
23
+ this.disposed = false;
24
+ this.history = /* @__PURE__ */ new Map();
25
+ this.measurements = {
26
+ received: 0,
27
+ published: 0,
28
+ coalesced: 0,
29
+ maxPendingOutcomes: 0,
30
+ maxInFlight: 0
31
+ };
32
+ }
33
+ start(options, event) {
34
+ this.dispose();
35
+ this.disposed = false;
36
+ this.generation = 0;
37
+ this.revision = 0;
38
+ const callbacks = {};
39
+ for (const name of [
40
+ "onConnected",
41
+ "onAuthenticated",
42
+ "onHeartbeat",
43
+ "onSubscribed",
44
+ "onUnsubscribed",
45
+ "onMarketResolved",
46
+ "onError",
47
+ "onOrderSubmitted",
48
+ "onBalanceUpdate",
49
+ "onOrderEvent",
50
+ "onRedeemEvent",
51
+ "onWithdrawalLifecycle",
52
+ "onArbMarket",
53
+ "onArbFeed",
54
+ "onDiagnostics",
55
+ "onConnectionStateChange"
56
+ ]) Object.assign(callbacks, { [name]: (...args) => {
57
+ if (name === "onConnectionStateChange" && args[0] === false) {
58
+ for (const builder of this.builders.values()) builder.reset();
59
+ this.dirty.clear();
60
+ this.deliveredClosed.clear();
61
+ }
62
+ if (name === "onMarketResolved") {
63
+ const id = args[0].outcomeId;
64
+ this.builders.get(id)?.dispose();
65
+ this.builders.delete(id);
66
+ this.dirty.delete(id);
67
+ }
68
+ if (name === "onDiagnostics") {
69
+ const diagnostic = args[0];
70
+ if (diagnostic.outcomeId && ["seq_gap", "checksum_mismatch"].includes(diagnostic.type)) this.mark(diagnostic.outcomeId, false);
71
+ }
72
+ event({
73
+ name,
74
+ args,
75
+ revision: this.revision
76
+ });
77
+ } });
78
+ callbacks.onFrameReceived = (receivedAtMs) => {
79
+ this.receivedAtMs = receivedAtMs;
80
+ this.revision++;
81
+ };
82
+ callbacks.onBookTick = (id, tick) => {
83
+ if (tick.midpoint != null) this.builder(id).addMidpoint(tick.midpoint, tick.timestamp);
84
+ this.mark(id, tick.source === "snapshot");
85
+ };
86
+ callbacks.onTrade = (trade) => {
87
+ this.builder(trade.outcomeId).addTrade(trade.price, trade.size, trade.timestamp);
88
+ this.mark(trade.outcomeId, false, trade);
89
+ };
90
+ this.socket = new AggWebSocket({
91
+ ...options,
92
+ callbacks,
93
+ deferBookViews: true
94
+ });
95
+ }
96
+ observeViews(observer) {
97
+ this.observer = observer;
98
+ }
99
+ setSubscriptions(subscriptions, generation) {
100
+ if (generation < this.generation) return;
101
+ this.generation = generation;
102
+ const desired = new Map(subscriptions.map((sub) => [`${sub.channel}:${sub.id}`, sub]));
103
+ for (const [key, unsubscribe] of this.subscriptions) if (!desired.has(key)) {
104
+ unsubscribe();
105
+ this.subscriptions.delete(key);
106
+ }
107
+ for (const [key, sub] of desired) {
108
+ if (this.subscriptions.has(key) || !this.socket) continue;
109
+ const unsubscribe = sub.channel === "arb" ? this.socket.subscribeArb(sub.id, () => {}) : sub.channel === "arb-feed" ? this.socket.subscribeArbFeed(() => {}) : this.socket.subscribe(sub.id, sub.channel);
110
+ this.subscriptions.set(key, unsubscribe);
111
+ }
112
+ for (const sub of subscriptions) if (sub.channel === "orderbook" && this.socket?.getBook(sub.id)) this.mark(sub.id, false);
113
+ const outcomes = new Set(subscriptions.filter((sub) => sub.channel === "orderbook" || sub.channel === "trades").map((sub) => sub.id));
114
+ for (const id of this.dirty.keys()) if (!outcomes.has(id)) this.dirty.delete(id);
115
+ for (const id of this.deliveredClosed.keys()) if (!outcomes.has(id)) this.deliveredClosed.delete(id);
116
+ for (const [id, builder] of this.builders) if (!outcomes.has(id)) {
117
+ builder.dispose();
118
+ this.builders.delete(id);
119
+ this.dirty.delete(id);
120
+ this.deliveredClosed.delete(id);
121
+ }
122
+ }
123
+ authenticate(token) {
124
+ this.socket?.authenticate(token);
125
+ }
126
+ resnapshot(ids) {
127
+ this.socket?.resnapshot(ids);
128
+ }
129
+ connect() {
130
+ this.socket?.connect();
131
+ }
132
+ disconnect() {
133
+ this.socket?.disconnect();
134
+ }
135
+ resume() {
136
+ this.socket?.resnapshot([...this.builders.keys()]);
137
+ }
138
+ getMeasurements() {
139
+ const socket = this.socket?.getDiagnostics() ?? null;
140
+ return {
141
+ ...this.measurements,
142
+ activeSubscriptions: this.subscriptions.size,
143
+ activeOutcomes: (/* @__PURE__ */ new Set([...socket?.orderbookOutcomeIds ?? [], ...socket?.tradeOutcomeIds ?? []])).size,
144
+ cachedBooks: socket?.cachedBooks ?? 0,
145
+ builders: this.builders.size,
146
+ historyTasks: this.history.size,
147
+ pendingOutcomes: this.dirty.size,
148
+ inFlightDeliveries: this.inFlight ? 1 : 0,
149
+ retainedCandleOutcomes: this.deliveredClosed.size,
150
+ socket
151
+ };
152
+ }
153
+ decodeHistory(payload) {
154
+ return decodeChartHistory(payload);
155
+ }
156
+ /** Fetch/decode on the processing side; newer range generations abort obsolete fetches. */
157
+ async loadHistoryRange(request) {
158
+ const previous = this.history.get(request.key);
159
+ if (previous && previous.generation > request.generation) return null;
160
+ previous?.controller.abort();
161
+ const controller = new AbortController();
162
+ const entry = {
163
+ generation: request.generation,
164
+ controller
165
+ };
166
+ this.history.set(request.key, entry);
167
+ const timeout = setTimeout(() => controller.abort(), 1e4);
168
+ try {
169
+ const response = await fetch(request.url, {
170
+ headers: request.headers,
171
+ signal: controller.signal
172
+ });
173
+ if (!response.ok) throw new Error(`History request failed (${response.status})`);
174
+ const body = decodeChartHistory(await readChartHistoryText(response));
175
+ if (body.data.length > 4096) throw new Error("Invalid or unbounded history response");
176
+ if (this.history.get(request.key) !== entry || this.disposed) return null;
177
+ return {
178
+ ...body,
179
+ data: [...body.data].sort((a, b) => a.t - b.t)
180
+ };
181
+ } finally {
182
+ clearTimeout(timeout);
183
+ if (this.history.get(request.key) === entry) this.history.delete(request.key);
184
+ }
185
+ }
186
+ async loadImmutableRange(key, generation, manifest) {
187
+ const previous = this.history.get(key);
188
+ if (previous && previous.generation > generation) throw new Error("Obsolete history range");
189
+ previous?.controller.abort();
190
+ const entry = {
191
+ generation,
192
+ controller: new AbortController()
193
+ };
194
+ this.history.set(key, entry);
195
+ const timer = setTimeout(() => entry.controller.abort(), 1e4);
196
+ try {
197
+ const data = await loadImmutableHistory({
198
+ manifest,
199
+ signal: entry.controller.signal
200
+ });
201
+ if (this.history.get(key) !== entry || this.disposed) throw new Error("Obsolete history range");
202
+ return data;
203
+ } finally {
204
+ clearTimeout(timer);
205
+ if (this.history.get(key) === entry) this.history.delete(key);
206
+ }
207
+ }
208
+ cancelHistoryRange(key, generation) {
209
+ const entry = this.history.get(key);
210
+ if (!entry || generation !== void 0 && entry.generation !== generation) return;
211
+ entry.controller.abort();
212
+ this.history.delete(key);
213
+ }
214
+ dispose() {
215
+ this.disposed = true;
216
+ this.cancelDelivery?.();
217
+ this.cancelDelivery = void 0;
218
+ this.deliveryToken = null;
219
+ this.inFlight = false;
220
+ this.observer = void 0;
221
+ this.socket?.destroy();
222
+ this.socket = null;
223
+ if (this.timer) clearTimeout(this.timer);
224
+ this.timer = void 0;
225
+ this.subscriptions.clear();
226
+ this.dirty.clear();
227
+ this.deliveredClosed.clear();
228
+ for (const builder of this.builders.values()) builder.dispose();
229
+ this.builders.clear();
230
+ for (const request of this.history.values()) request.controller.abort();
231
+ this.history.clear();
232
+ }
233
+ builder(id) {
234
+ let builder = this.builders.get(id);
235
+ if (!builder) {
236
+ builder = new CandleBuilder();
237
+ this.builders.set(id, builder);
238
+ }
239
+ return builder;
240
+ }
241
+ mark(id, snapshot, trade) {
242
+ const previous = this.dirty.get(id);
243
+ this.measurements.received++;
244
+ if (previous) this.measurements.coalesced++;
245
+ this.dirty.set(id, {
246
+ snapshot: snapshot || !!previous?.snapshot,
247
+ receivedAtMs: previous?.receivedAtMs ?? (this.receivedAtMs || Date.now()),
248
+ trades: trade ? [trade, ...previous?.trades ?? []].slice(0, 50) : previous?.trades ?? []
249
+ });
250
+ this.measurements.maxPendingOutcomes = Math.max(this.measurements.maxPendingOutcomes, this.dirty.size);
251
+ this.schedule();
252
+ }
253
+ schedule() {
254
+ if (this.timer || this.inFlight || this.disposed || !this.observer) return;
255
+ this.timer = setTimeout(() => {
256
+ this.timer = void 0;
257
+ this.publish();
258
+ }, 50);
259
+ }
260
+ async publish() {
261
+ if (this.inFlight || this.disposed || !this.observer || !this.dirty.size) return;
262
+ const views = [...this.dirty].map(([outcomeId, dirty]) => {
263
+ const builder = this.builder(outcomeId);
264
+ return {
265
+ outcomeId,
266
+ revision: this.revision,
267
+ integrity: this.socket?.isResyncing(outcomeId) ? "resyncing" : "ok",
268
+ book: this.socket?.getBook(outcomeId) ?? null,
269
+ ...dirty,
270
+ candles: Object.fromEntries(intervals.map((interval) => [interval, {
271
+ forming: builder.getForming(interval) ? { ...builder.getForming(interval) } : null,
272
+ closed: builder.getClosed(interval).filter((candle) => candle.openTime > (this.deliveredClosed.get(outcomeId)?.[interval] ?? -Infinity)).map((candle) => ({ ...candle }))
273
+ }]))
274
+ };
275
+ });
276
+ const generation = this.generation;
277
+ this.dirty.clear();
278
+ this.inFlight = true;
279
+ const token = {};
280
+ this.deliveryToken = token;
281
+ const cancelled = new Promise((resolve) => {
282
+ this.cancelDelivery = resolve;
283
+ });
284
+ this.measurements.maxInFlight = 1;
285
+ try {
286
+ await Promise.race([this.observer({
287
+ generation,
288
+ views,
289
+ publishedAtMs: Date.now()
290
+ }), cancelled]);
291
+ if (this.disposed || this.deliveryToken !== token) return;
292
+ for (const view of generation === this.generation ? views : []) {
293
+ const delivered = this.deliveredClosed.get(view.outcomeId) ?? {};
294
+ for (const interval of intervals) {
295
+ const last = view.candles[interval].closed.at(-1);
296
+ if (last) delivered[interval] = last.openTime;
297
+ }
298
+ this.deliveredClosed.set(view.outcomeId, delivered);
299
+ }
300
+ this.measurements.published++;
301
+ } catch {
302
+ if (!this.disposed && this.deliveryToken === token) this.socket?.disconnect();
303
+ } finally {
304
+ if (this.deliveryToken === token) {
305
+ this.deliveryToken = null;
306
+ this.cancelDelivery = void 0;
307
+ this.inFlight = false;
308
+ if (this.dirty.size) this.schedule();
309
+ }
310
+ }
311
+ }
352
312
  };
313
+ //#endregion
314
+ export { MarketDataEngine, decodeChartHistory };
315
+
316
+ //# sourceMappingURL=market-data.mjs.map