@agg-build/sdk 4.17.4 → 4.18.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.
@@ -0,0 +1,352 @@
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"];
18
+ 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
352
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agg-build/sdk",
3
- "version": "4.17.4",
3
+ "version": "4.18.0",
4
4
  "description": "Vanilla TypeScript client for the AGG prediction market aggregator (auth, markets, orderbooks, charts, trading, managed execution, WebSockets). Works in browsers, Node.js, and React Native.",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -28,6 +28,12 @@
28
28
  "*": {
29
29
  "server": [
30
30
  "./dist/server.d.ts"
31
+ ],
32
+ "browser": [
33
+ "./dist/browser.d.ts"
34
+ ],
35
+ "market-data": [
36
+ "./dist/market-data.d.ts"
31
37
  ]
32
38
  }
33
39
  },
@@ -59,6 +65,26 @@
59
65
  "types": "./dist/server.d.ts",
60
66
  "default": "./dist/server.js"
61
67
  }
68
+ },
69
+ "./browser": {
70
+ "import": {
71
+ "types": "./dist/browser.d.mts",
72
+ "default": "./dist/browser.mjs"
73
+ },
74
+ "require": {
75
+ "types": "./dist/browser.d.ts",
76
+ "default": "./dist/browser.js"
77
+ }
78
+ },
79
+ "./market-data": {
80
+ "import": {
81
+ "types": "./dist/market-data.d.mts",
82
+ "default": "./dist/market-data.mjs"
83
+ },
84
+ "require": {
85
+ "types": "./dist/market-data.d.ts",
86
+ "default": "./dist/market-data.js"
87
+ }
62
88
  }
63
89
  },
64
90
  "publishConfig": {