@tickerall/sdk 0.1.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,572 @@
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ type BrokerName = 'mt4' | 'mt5';
4
+ type OrderSide = 'BUY' | 'SELL';
5
+ type OrderType = 'market' | 'limit' | 'stop';
6
+ type AccountStatusListing = 'PENDING' | 'ACTIVE' | 'COOLED' | 'REJECTED';
7
+ interface SessionStartParams {
8
+ broker: BrokerName;
9
+ server: string;
10
+ account: number | string;
11
+ password: string;
12
+ }
13
+ interface SessionStartResult {
14
+ accountId: string;
15
+ isDemo: boolean;
16
+ status: 'connected';
17
+ expiresAt: string;
18
+ }
19
+ /**
20
+ * An always-hot account that currently has no live connection (it died — e.g.
21
+ * TickerAll restarted — and needs a credentials refresh). Returned by
22
+ * `sessions.pendingRearm()`.
23
+ */
24
+ interface PendingRearmAccount {
25
+ id: string;
26
+ broker: BrokerName;
27
+ server: string;
28
+ accountNumber: string;
29
+ }
30
+ interface AccountListing {
31
+ id: string;
32
+ broker: BrokerName;
33
+ server: string;
34
+ accountNumber: string;
35
+ isDemo: boolean;
36
+ status: AccountStatusListing;
37
+ lastHotAt: string | null;
38
+ createdAt: string;
39
+ }
40
+ interface AccountInfo {
41
+ name: string;
42
+ accountType: string;
43
+ leverage: number;
44
+ balance: number;
45
+ brokerName?: string;
46
+ brokerWebsite?: string;
47
+ supportEmail?: string;
48
+ metaQuotesId?: string;
49
+ }
50
+ interface Position {
51
+ ticket: number;
52
+ symbol: string;
53
+ side: OrderSide;
54
+ volume: number;
55
+ openTime?: string;
56
+ entryPrice?: number;
57
+ stopLoss: number;
58
+ takeProfit: number;
59
+ currentPrice?: number;
60
+ profit?: number;
61
+ magic: number;
62
+ comment: string;
63
+ swap: number;
64
+ commission: number;
65
+ lastUpdate?: string;
66
+ }
67
+ interface AccountDetailOnline {
68
+ id: string;
69
+ broker: BrokerName;
70
+ server: string;
71
+ accountNumber: string;
72
+ isDemo: boolean;
73
+ status: 'online';
74
+ account: AccountInfo | null;
75
+ positions: Position[];
76
+ }
77
+ interface AccountDetailOffline {
78
+ id: string;
79
+ broker: BrokerName;
80
+ server: string;
81
+ accountNumber: string;
82
+ isDemo: boolean;
83
+ status: 'offline';
84
+ hint: string;
85
+ }
86
+ type AccountDetail = AccountDetailOnline | AccountDetailOffline;
87
+ interface SymbolsResponse {
88
+ symbols: string[];
89
+ }
90
+ /** Per-symbol volume constraints for validating an order size before placing.
91
+ * `specSource` is `'broker'` when the broker pushed all three volume fields
92
+ * (authoritative), else `'derived'` — filled from the SDK's per-category
93
+ * Exness defaults, so treat derived specs as best-effort, not authoritative.
94
+ * `tradeMode` is MT5's ENUM_SYMBOL_TRADE_MODE when available:
95
+ * 0=DISABLED, 1=LONGONLY, 2=SHORTONLY, 3=CLOSEONLY, 4=FULL.
96
+ * Undefined on MT4 (no equivalent) and on truncated/older-SDK responses.
97
+ * `tradeModeSource` is the SDK-authoritative provenance of `tradeMode`,
98
+ * mirrors `specSource` semantics for that field specifically. */
99
+ interface SymbolSpec {
100
+ name: string;
101
+ volumeMin: number;
102
+ volumeMax: number;
103
+ volumeStep: number;
104
+ specSource: 'broker' | 'derived';
105
+ tradeMode?: number;
106
+ tradeModeSource?: 'broker' | 'derived';
107
+ }
108
+ interface SymbolSpecsResponse {
109
+ specs: SymbolSpec[];
110
+ }
111
+ interface PlaceOrderParams {
112
+ type: OrderType;
113
+ symbol: string;
114
+ side: OrderSide;
115
+ volume: number;
116
+ price?: number;
117
+ stopLoss?: number;
118
+ takeProfit?: number;
119
+ comment?: string;
120
+ }
121
+ interface PlaceOrderResult {
122
+ ticket: number;
123
+ symbol: string;
124
+ side: OrderSide;
125
+ type: OrderType;
126
+ volume: number;
127
+ price?: number;
128
+ stopLoss: number | null;
129
+ takeProfit: number | null;
130
+ comment: string | null;
131
+ status: 'open' | 'pending';
132
+ timestamp: string;
133
+ }
134
+ interface ClosePositionParams {
135
+ volume?: number;
136
+ }
137
+ interface ClosePositionResult {
138
+ ticket: number;
139
+ symbol: string;
140
+ side: OrderSide;
141
+ volume: number;
142
+ closed: true;
143
+ timestamp: string;
144
+ }
145
+ interface ModifyPositionParams {
146
+ stopLoss?: number;
147
+ takeProfit?: number;
148
+ }
149
+ interface ModifyPositionResult {
150
+ ticket: number;
151
+ symbol: string;
152
+ side: OrderSide;
153
+ volume: number;
154
+ stopLoss: number;
155
+ takeProfit: number;
156
+ timestamp: string;
157
+ }
158
+ interface RequestOptions {
159
+ signal?: AbortSignal;
160
+ timeout?: number;
161
+ }
162
+ interface IdempotentRequestOptions extends RequestOptions {
163
+ idempotencyKey?: string;
164
+ /**
165
+ * Opt a state-changing call into queue-and-replay. Default `false` — a call
166
+ * that hits a transient connectivity failure (TickerAll momentarily
167
+ * unreachable) fails fast with a `TickerallServiceUnavailableError` so you
168
+ * can re-decide with fresh prices.
169
+ *
170
+ * When `true`, the call is instead held in an in-order queue and replayed —
171
+ * with its stable `Idempotency-Key`, so the server dedupes any attempt that
172
+ * already executed — until it succeeds or `queueMaxMs` elapses. Use this for
173
+ * orders that aren't price-sensitive (e.g. pending orders, SL/TP edits);
174
+ * avoid it for market orders, where a delayed fill at a moved price is
175
+ * usually worse than a fast failure.
176
+ */
177
+ queueIfReconnecting?: boolean;
178
+ /**
179
+ * Maximum time (ms) a `queueIfReconnecting` call waits across replays before
180
+ * giving up and rejecting with the transient error. Default 60_000.
181
+ * Ignored unless `queueIfReconnecting` is `true`.
182
+ */
183
+ queueMaxMs?: number;
184
+ }
185
+ /** Connection state of a {@link TickerallStream}. */
186
+ type StreamState = 'connecting' | 'open' | 'reconnecting' | 'closed';
187
+ interface TickerallClientConfig {
188
+ apiKey: string;
189
+ baseUrl?: string;
190
+ streamUrl?: string;
191
+ timeout?: number;
192
+ fetch?: typeof globalThis.fetch;
193
+ userAgent?: string;
194
+ /**
195
+ * Called when the client transparently re-arms a kept session (re-supplies
196
+ * broker credentials because the account went cold — e.g. after an atlas
197
+ * restart). Observability only; the re-arm + retry happen automatically.
198
+ */
199
+ onRearm?: (accountId: string) => void;
200
+ }
201
+ interface TickEvent {
202
+ type: 'tick';
203
+ accountId: string;
204
+ symbol: string;
205
+ bid: number;
206
+ ask: number;
207
+ timestamp: string;
208
+ }
209
+ type PositionEventType = 'position-opened' | 'position-updated' | 'position-closed';
210
+ interface PositionEvent {
211
+ type: 'position';
212
+ accountId: string;
213
+ event: PositionEventType;
214
+ position: Position;
215
+ }
216
+ interface AccountEvent {
217
+ type: 'account';
218
+ accountId: string;
219
+ snapshot: {
220
+ group: string;
221
+ balance: number;
222
+ };
223
+ }
224
+ interface StreamErrorEvent {
225
+ message: string;
226
+ cause?: unknown;
227
+ }
228
+ interface StreamCloseEvent {
229
+ code: number;
230
+ reason: string;
231
+ }
232
+ interface StreamReconnectEvent {
233
+ attempt: number;
234
+ }
235
+ type Timeframe = 'M1' | 'M5' | 'M15' | 'M30' | 'H1' | 'H4' | 'D1' | 'W1' | 'MN1';
236
+ interface CandlesParams {
237
+ /** Broker-native symbol name, e.g. "BTCUSDm". */
238
+ symbol: string;
239
+ /** How many hours of history to return, counted back from now. */
240
+ hours: number;
241
+ /** Bar interval. Defaults to "M5" server-side. Coarser timeframes reach further back. */
242
+ timeframe?: Timeframe;
243
+ }
244
+ interface Candle {
245
+ /** Bar OPEN time, Unix seconds (UTC). */
246
+ timestamp: number;
247
+ open: number;
248
+ high: number;
249
+ low: number;
250
+ close: number;
251
+ /** Mirrors close. */
252
+ bid: number;
253
+ }
254
+ interface CandlesResponse {
255
+ symbol: string;
256
+ hours: number;
257
+ timeframe: Timeframe;
258
+ candles: Candle[];
259
+ }
260
+ interface HistoryParams {
261
+ /** Narrow to one broker-native symbol, e.g. "BTCUSDm". */
262
+ symbol?: string;
263
+ /** Lower bound on close time — ISO-8601 string, epoch seconds/ms, or a Date. */
264
+ from?: string | number | Date;
265
+ /** Upper bound on close time — ISO-8601 string, epoch seconds/ms, or a Date. */
266
+ to?: string | number | Date;
267
+ /** Max rows to return (server caps at 5000; defaults to 500). */
268
+ limit?: number;
269
+ /**
270
+ * How long (ms) the server may wait for the broker's login deal-log burst to
271
+ * settle on a just-warmed account. Pass 0 on a hot poller to skip the wait.
272
+ */
273
+ waitMs?: number;
274
+ }
275
+ interface HistoryTrade {
276
+ /** Opening deal ticket — the round-trip's identity. */
277
+ ticket: string;
278
+ symbol: string;
279
+ side: 'BUY' | 'SELL';
280
+ volume: number;
281
+ openPrice: number;
282
+ closePrice: number;
283
+ /** ISO-8601 UTC. */
284
+ openTime: string;
285
+ /** ISO-8601 UTC. */
286
+ closeTime: string;
287
+ /** Realised P/L in account currency; null if the broker didn't report it. */
288
+ profit: number | null;
289
+ swap: number;
290
+ commission: number;
291
+ stopLoss: number;
292
+ takeProfit: number;
293
+ /** Closing deal ticket, or null if the close leg wasn't paired. */
294
+ closeTicket: string | null;
295
+ /** True when both legs were paired into a complete round-trip. */
296
+ complete: boolean;
297
+ }
298
+ interface HistoryResponse {
299
+ trades: HistoryTrade[];
300
+ count: number;
301
+ symbol?: string;
302
+ from?: string;
303
+ to?: string;
304
+ limit: number;
305
+ }
306
+
307
+ declare class SessionsNamespace {
308
+ private readonly client;
309
+ constructor(client: Tickerall);
310
+ start(params: SessionStartParams, options?: IdempotentRequestOptions): Promise<SessionStartResult>;
311
+ /**
312
+ * Start a session AND keep it alive: the broker credentials are cached in
313
+ * this process's memory (never persisted) so the client transparently
314
+ * re-arms the account if it later goes cold — e.g. after an atlas restart.
315
+ * Pair with an always-hot account for a connection that stays up with no
316
+ * manual reconnect. Call {@link stopKeepAlive} (or {@link end}) to forget
317
+ * the credentials.
318
+ */
319
+ keepAlive(params: SessionStartParams, options?: IdempotentRequestOptions): Promise<SessionStartResult>;
320
+ /** Stop auto-re-arming an account and drop its cached credentials. */
321
+ stopKeepAlive(accountId: string): void;
322
+ /**
323
+ * Manually re-arm a kept account now (a fresh session.start with the cached
324
+ * credentials). Normally unnecessary — the client re-arms on demand — but
325
+ * useful to proactively recover accounts listed by `GET /v1/always-hot/pending`.
326
+ */
327
+ rearm(accountId: string): Promise<void>;
328
+ /**
329
+ * Always-hot accounts that currently have no live connection — they need a
330
+ * credentials refresh (e.g. after a TickerAll restart). Poll this after a
331
+ * suspected outage; the next call or stream reconnect also re-arms them
332
+ * automatically.
333
+ */
334
+ pendingRearm(options?: RequestOptions): Promise<PendingRearmAccount[]>;
335
+ /**
336
+ * Re-arm every pending account we hold kept credentials for (the ones we
337
+ * actually can recover). Returns the account IDs that were re-armed.
338
+ */
339
+ rearmPending(options?: RequestOptions): Promise<string[]>;
340
+ end(accountId: string, options?: RequestOptions): Promise<void>;
341
+ }
342
+
343
+ declare class AccountsNamespace {
344
+ private readonly client;
345
+ constructor(client: Tickerall);
346
+ list(options?: RequestOptions): Promise<AccountListing[]>;
347
+ get(accountId: string, options?: RequestOptions): Promise<AccountDetail>;
348
+ symbols(accountId: string, options?: RequestOptions): Promise<string[]>;
349
+ /**
350
+ * Per-symbol volume specs (min / max / step) for this account's tradeable
351
+ * symbols — use these to check an order volume before placing it. Each spec
352
+ * carries `specSource`: `'broker'` (the broker pushed it, authoritative) or
353
+ * `'derived'` (filled from category defaults — best-effort). MT5 only; an
354
+ * MT4 account returns an empty list.
355
+ */
356
+ symbolSpecs(accountId: string, options?: RequestOptions): Promise<SymbolSpec[]>;
357
+ }
358
+
359
+ declare class OrdersNamespace {
360
+ private readonly client;
361
+ constructor(client: Tickerall);
362
+ place(accountId: string, params: PlaceOrderParams, options?: IdempotentRequestOptions): Promise<PlaceOrderResult>;
363
+ }
364
+
365
+ declare class PositionsNamespace {
366
+ private readonly client;
367
+ constructor(client: Tickerall);
368
+ close(accountId: string, ticket: number, params?: ClosePositionParams, options?: IdempotentRequestOptions): Promise<ClosePositionResult>;
369
+ modify(accountId: string, ticket: number, params: ModifyPositionParams, options?: IdempotentRequestOptions): Promise<ModifyPositionResult>;
370
+ }
371
+
372
+ interface StreamConfig {
373
+ streamUrl: string;
374
+ apiKey: string;
375
+ userAgent: string;
376
+ }
377
+ interface TickerallStreamEvents {
378
+ tick: (e: TickEvent) => void;
379
+ position: (e: PositionEvent) => void;
380
+ account: (e: AccountEvent) => void;
381
+ error: (e: StreamErrorEvent) => void;
382
+ close: (e: StreamCloseEvent) => void;
383
+ reconnect: (e: StreamReconnectEvent) => void;
384
+ open: () => void;
385
+ }
386
+ declare class TickerallStream extends EventEmitter {
387
+ private ws;
388
+ private closedByUser;
389
+ private state;
390
+ private beforeResubscribe?;
391
+ private reconnectAttempts;
392
+ private reconnectTimer;
393
+ private heartbeatTimer;
394
+ private heartbeatTimeoutTimer;
395
+ private readonly subs;
396
+ private readonly url;
397
+ private readonly headers;
398
+ constructor(url: string, apiKey: string, userAgent: string);
399
+ on<E extends keyof TickerallStreamEvents>(event: E, listener: TickerallStreamEvents[E]): this;
400
+ once<E extends keyof TickerallStreamEvents>(event: E, listener: TickerallStreamEvents[E]): this;
401
+ off<E extends keyof TickerallStreamEvents>(event: E, listener: TickerallStreamEvents[E]): this;
402
+ connect(): Promise<void>;
403
+ /**
404
+ * Internal: callback run after a RECONNECT (not the initial connect), before
405
+ * subscriptions are re-sent. The SDK wires this to re-arm kept sessions so a
406
+ * connection transparently survives an atlas restart.
407
+ */
408
+ setBeforeResubscribe(cb: () => Promise<void>): void;
409
+ /**
410
+ * Current connection state. `'open'` means live; `'reconnecting'` means the
411
+ * SDK is silently re-establishing the link (idle apps need do nothing).
412
+ * Queryable any time — never thrown at you.
413
+ */
414
+ getState(): StreamState;
415
+ /** `true` only when the stream is live and ready to receive. */
416
+ isConnected(): boolean;
417
+ /**
418
+ * Resolve once the stream is live (immediately if already open), or reject
419
+ * after `timeoutMs`. Rejects right away if the stream was closed by the
420
+ * caller. Handy to gate logic on a confirmed connection without polling.
421
+ */
422
+ waitUntilConnected(timeoutMs?: number): Promise<void>;
423
+ private openOnce;
424
+ private handleFrame;
425
+ private startHeartbeat;
426
+ private clearHeartbeatTimeout;
427
+ private stopHeartbeat;
428
+ private forceReconnect;
429
+ private scheduleReconnect;
430
+ private resendSubscriptions;
431
+ private send;
432
+ subscribeTicks(accountId: string, symbols: string[]): Promise<void>;
433
+ unsubscribeTicks(accountId: string, symbols: string[]): Promise<void>;
434
+ subscribePositions(accountId: string): Promise<void>;
435
+ subscribeAccount(accountId: string): Promise<void>;
436
+ close(): Promise<void>;
437
+ }
438
+ declare class StreamNamespace {
439
+ private readonly client;
440
+ private readonly streamUrl;
441
+ private readonly apiKey;
442
+ private readonly userAgent;
443
+ constructor(client: Tickerall, config: StreamConfig);
444
+ connect(): Promise<TickerallStream>;
445
+ }
446
+
447
+ declare class CandlesNamespace {
448
+ private readonly client;
449
+ constructor(client: Tickerall);
450
+ /**
451
+ * Fetch historical OHLC candles for a symbol from one of your broker
452
+ * accounts.
453
+ *
454
+ * Candles are read from the account's own live broker connection, so pass
455
+ * the `accountId` of a connected account. Coarser timeframes reach further
456
+ * back; a single request returns as much history as fits in a few seconds,
457
+ * so pass a large `hours` and take what comes back. Deep look-backs are
458
+ * served on an isolated history connection and won't disturb your live tick
459
+ * stream.
460
+ *
461
+ * @example
462
+ * const bars = await client.candles.get(accountId, { symbol: 'BTCUSDm', hours: 8760, timeframe: 'D1' })
463
+ */
464
+ get(accountId: string, params: CandlesParams, options?: RequestOptions): Promise<Candle[]>;
465
+ }
466
+
467
+ declare class HistoryNamespace {
468
+ private readonly client;
469
+ constructor(client: Tickerall);
470
+ /**
471
+ * Closed-trade history for one of your broker accounts — executed deals
472
+ * paired into round-trips, read from the account's own live broker
473
+ * connection (the equivalent of MT5's history_deals_get for the recent
474
+ * window).
475
+ *
476
+ * `symbol`/`from`/`to`/`limit` FILTER the broker's recent deal log (a few
477
+ * weeks deep, broker-controlled) plus any closes seen live this session —
478
+ * they don't fetch deeper than the broker's window. Pass the `accountId` of
479
+ * a connected account.
480
+ *
481
+ * @example
482
+ * const trades = await client.history.get(accountId, { symbol: 'BTCUSDm', limit: 100 })
483
+ */
484
+ get(accountId: string, params?: HistoryParams, options?: RequestOptions): Promise<HistoryTrade[]>;
485
+ }
486
+
487
+ declare class Tickerall {
488
+ readonly sessions: SessionsNamespace;
489
+ readonly accounts: AccountsNamespace;
490
+ readonly orders: OrdersNamespace;
491
+ readonly positions: PositionsNamespace;
492
+ readonly stream: StreamNamespace;
493
+ readonly candles: CandlesNamespace;
494
+ readonly history: HistoryNamespace;
495
+ private readonly apiKey;
496
+ private readonly baseUrl;
497
+ private readonly streamUrl;
498
+ private readonly timeout;
499
+ private readonly fetchImpl;
500
+ private readonly userAgent;
501
+ private readonly replayQueue;
502
+ private replayDraining;
503
+ private readonly keptCreds;
504
+ private readonly onRearm?;
505
+ constructor(config: TickerallClientConfig);
506
+ private requestWithRearm;
507
+ private requestOnce;
508
+ private enqueueReplay;
509
+ private drainReplayQueue;
510
+ }
511
+
512
+ interface TickerallErrorInit {
513
+ status: number;
514
+ code: string;
515
+ message: string;
516
+ requestId?: string;
517
+ details?: unknown;
518
+ /**
519
+ * Transient = the request failed because TickerAll was momentarily
520
+ * unreachable (network blip, deploy, restart) rather than because the
521
+ * request itself was bad. Safe to retry. Set automatically for
522
+ * `TickerallServiceUnavailableError`; `false` for every other error.
523
+ */
524
+ transient?: boolean;
525
+ }
526
+ declare class TickerallApiError extends Error {
527
+ readonly status: number;
528
+ readonly code: string;
529
+ readonly requestId?: string;
530
+ readonly details?: unknown;
531
+ /**
532
+ * `true` when the failure is a momentary connectivity issue that is safe
533
+ * to retry (see {@link TickerallServiceUnavailableError}). `false` for
534
+ * application errors (auth, validation, broker rejection, …) that won't
535
+ * change on retry. Use it to decide whether to back off and try again.
536
+ */
537
+ readonly transient: boolean;
538
+ constructor(init: TickerallErrorInit);
539
+ }
540
+ declare class TickerallAuthError extends TickerallApiError {
541
+ constructor(init: TickerallErrorInit);
542
+ }
543
+ declare class TickerallForbiddenError extends TickerallApiError {
544
+ constructor(init: TickerallErrorInit);
545
+ }
546
+ declare class TickerallValidationError extends TickerallApiError {
547
+ constructor(init: TickerallErrorInit);
548
+ }
549
+ declare class TickerallNotFoundError extends TickerallApiError {
550
+ constructor(init: TickerallErrorInit);
551
+ }
552
+ declare class TickerallBrokerError extends TickerallApiError {
553
+ constructor(init: TickerallErrorInit);
554
+ }
555
+ /**
556
+ * TickerAll was momentarily unreachable — a network blip, a deploy, or the
557
+ * service restarting. This is NOT your fault and NOT a broker rejection: the
558
+ * request never reached a verdict, so it's safe to retry. `transient` is
559
+ * always `true`.
560
+ *
561
+ * The SDK throws this for network failures, request timeouts, and bare
562
+ * 502/503s (and `POOL_SHUTTING_DOWN`). By default a trade fails fast with
563
+ * this error so you can re-decide with fresh prices; pass
564
+ * `{ queueIfReconnecting: true }` to instead queue the call and replay it
565
+ * (with its stable idempotency key, so it can't double-execute) once
566
+ * connectivity returns.
567
+ */
568
+ declare class TickerallServiceUnavailableError extends TickerallApiError {
569
+ constructor(init: TickerallErrorInit);
570
+ }
571
+
572
+ export { type AccountDetail, type AccountDetailOffline, type AccountDetailOnline, type AccountEvent, type AccountInfo, type AccountListing, type AccountStatusListing, type BrokerName, type Candle, type CandlesParams, type CandlesResponse, type ClosePositionParams, type ClosePositionResult, type HistoryParams, type HistoryResponse, type HistoryTrade, type IdempotentRequestOptions, type ModifyPositionParams, type ModifyPositionResult, type OrderSide, type OrderType, type PendingRearmAccount, type PlaceOrderParams, type PlaceOrderResult, type Position, type PositionEvent, type PositionEventType, type RequestOptions, type SessionStartParams, type SessionStartResult, type StreamCloseEvent, type StreamErrorEvent, type StreamReconnectEvent, type StreamState, type SymbolSpec, type SymbolSpecsResponse, type SymbolsResponse, type TickEvent, Tickerall, TickerallApiError, TickerallAuthError, TickerallBrokerError, type TickerallClientConfig, type TickerallErrorInit, TickerallForbiddenError, TickerallNotFoundError, TickerallServiceUnavailableError, TickerallStream, TickerallValidationError, type Timeframe };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var events=require('events'),p=require('ws');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var p__default=/*#__PURE__*/_interopDefault(p);var a=class extends Error{constructor(e){super(e.message),this.name="TickerallApiError",this.status=e.status,this.code=e.code,this.requestId=e.requestId,this.details=e.details,this.transient=e.transient??false;}},h=class extends a{constructor(e){super(e),this.name="TickerallAuthError";}},y=class extends a{constructor(e){super(e),this.name="TickerallForbiddenError";}},g=class extends a{constructor(e){super(e),this.name="TickerallValidationError";}},T=class extends a{constructor(e){super(e),this.name="TickerallNotFoundError";}},f=class extends a{constructor(e){super(e),this.name="TickerallBrokerError";}},m=class extends a{constructor(e){super({...e,transient:true}),this.name="TickerallServiceUnavailableError";}},K=new Set(["BROKER_REJECTED","BROKER_AUTH_FAILED","BROKER_UNREACHABLE","BROKER_ACCOUNT_NOT_HOT","BROKER_ACCOUNT_ALREADY_LINKED","BROKER_ACCOUNT_NOT_FOUND","TICKET_NOT_FOUND"]);function q(r){return r.code==="POOL_SHUTTING_DOWN"?new m(r):K.has(r.code)?new f(r):r.status===401?new h(r):r.status===403?new y(r):r.status===404?new T(r):r.status===400||r.status===422?new g(r):r.status===503||r.status===502?new m(r):new a(r)}var b=class{constructor(e){this.client=e;}start(e,t){return this.client.requestIdempotent({method:"POST",path:"/v1/sessions",body:e},t)}async keepAlive(e,t){let s=await this.start(e,t);return this.client.registerKeptSession(s.accountId,e),s}stopKeepAlive(e){this.client.unregisterKeptSession(e);}rearm(e){return this.client.rearm(e)}async pendingRearm(e){return (await this.client.requestPlain({method:"GET",path:"/v1/always-hot/pending"},e)).pending}async rearmPending(e){let t=await this.pendingRearm(e),s=new Set(this.client.keptSessionIds()),n=[];for(let i of t)s.has(i.id)&&(await this.client.rearm(i.id).catch(()=>{}),n.push(i.id));return n}end(e,t){return this.client.unregisterKeptSession(e),this.client.requestPlain({method:"DELETE",path:`/v1/sessions/${encodeURIComponent(e)}`,expectNoContent:true},t)}};var R=class{constructor(e){this.client=e;}list(e){return this.client.requestPlain({method:"GET",path:"/v1/accounts"},e)}get(e,t){return this.client.requestPlain({method:"GET",path:`/v1/accounts/${encodeURIComponent(e)}`,accountId:e},t)}async symbols(e,t){return (await this.client.requestPlain({method:"GET",path:`/v1/accounts/${encodeURIComponent(e)}/symbols`,accountId:e},t)).symbols}async symbolSpecs(e,t){return (await this.client.requestPlain({method:"GET",path:`/v1/accounts/${encodeURIComponent(e)}/symbol-specs`,accountId:e},t)).specs}};var v=class{constructor(e){this.client=e;}place(e,t,s){return this.client.requestIdempotent({method:"POST",path:`/v1/accounts/${encodeURIComponent(e)}/orders`,body:t,accountId:e},s)}};var E=class{constructor(e){this.client=e;}close(e,t,s={},n){return this.client.requestIdempotent({method:"DELETE",path:`/v1/accounts/${encodeURIComponent(e)}/positions/${t}`,body:s,accountId:e},n)}modify(e,t,s,n){return this.client.requestIdempotent({method:"PATCH",path:`/v1/accounts/${encodeURIComponent(e)}/positions/${t}`,body:s,accountId:e},n)}};var $=25e3,H=1e4,U=[1e3,2e3,4e3,8e3,16e3,3e4],_=3e4,L={opened:"position-opened",updated:"position-updated",closed:"position-closed"},k=class extends events.EventEmitter{constructor(t,s,n){super();this.ws=null;this.closedByUser=false;this.state="closed";this.reconnectAttempts=0;this.reconnectTimer=null;this.heartbeatTimer=null;this.heartbeatTimeoutTimer=null;this.subs=new Map;this.url=t,this.headers={Authorization:`Bearer ${s}`,"User-Agent":n},this.on("error",()=>{});}on(t,s){return super.on(t,s)}once(t,s){return super.once(t,s)}off(t,s){return super.off(t,s)}async connect(){this.closedByUser=false,this.state="connecting",await this.openOnce();}setBeforeResubscribe(t){this.beforeResubscribe=t;}getState(){return this.state}isConnected(){return this.state==="open"&&this.ws?.readyState===p__default.default.OPEN}waitUntilConnected(t=3e4){return this.isConnected()?Promise.resolve():this.state==="closed"?Promise.reject(new Error("Stream is closed")):new Promise((s,n)=>{let i,o=()=>{clearTimeout(i),s();};i=setTimeout(()=>{this.off("open",o),n(new Error(`waitUntilConnected: timed out after ${t}ms`));},t),this.once("open",o);})}openOnce(){return new Promise((t,s)=>{let n=new p__default.default(this.url,{headers:this.headers});this.ws=n;let i=false,o=async()=>{if(i)return;i=true;let u=this.reconnectAttempts>0;if(this.state="open",this.reconnectAttempts=0,this.startHeartbeat(),u&&this.beforeResubscribe)try{await this.beforeResubscribe();}catch{}this.resendSubscriptions(),this.emit("open"),t();},d=u=>{try{let c=JSON.parse(u.toString());this.handleFrame(c);}catch(c){this.emit("error",{message:"Failed to parse server frame",cause:c});}},l=u=>{i||(i=true,s(u)),this.emit("error",{message:u.message,cause:u});},O=(u,c)=>{this.stopHeartbeat(),this.ws=null;let A=c?.toString()??"";this.emit("close",{code:u,reason:A}),i||(i=true,s(new Error(`WebSocket closed before open (code=${u})`))),this.closedByUser||(this.state="reconnecting",this.scheduleReconnect());};n.on("open",o),n.on("message",d),n.on("error",l),n.on("close",O),n.on("pong",()=>this.clearHeartbeatTimeout());})}handleFrame(t){switch(t.type){case "tick":this.emit("tick",{type:"tick",accountId:t.accountId,symbol:t.symbol,bid:t.bid,ask:t.ask,timestamp:t.timestamp});return;case "position_update":this.emit("position",{type:"position",accountId:t.accountId,event:L[t.event]??"position-updated",position:t.position});return;case "account_update":this.emit("account",{type:"account",accountId:t.accountId,snapshot:t.snapshot});return;case "subscribed":t.rejected&&t.rejected.length>0&&this.emit("error",{message:`Subscribe rejected: ${t.rejected.map(s=>`${s.code} (${s.reason})`).join(", ")}`});return;case "error":this.emit("error",{message:`${t.code}: ${t.message}`});return;case "pong":this.clearHeartbeatTimeout();return}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(!this.ws||this.ws.readyState!==p__default.default.OPEN)){this.send({type:"ping"});try{this.ws.ping();}catch{}this.heartbeatTimeoutTimer=setTimeout(()=>this.forceReconnect("heartbeat-timeout"),H);}},$);}clearHeartbeatTimeout(){this.heartbeatTimeoutTimer&&(clearTimeout(this.heartbeatTimeoutTimer),this.heartbeatTimeoutTimer=null);}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimeoutTimer&&clearTimeout(this.heartbeatTimeoutTimer),this.heartbeatTimer=null,this.heartbeatTimeoutTimer=null;}forceReconnect(t){if(this.ws)try{this.ws.terminate();}catch{}}scheduleReconnect(){if(this.closedByUser||this.reconnectTimer)return;let t=Math.min(this.reconnectAttempts,U.length-1),s=U[t]??_,n=Math.floor(Math.random()*Math.min(s,1e3)),i=Math.min(s+n,_+1e3);this.reconnectAttempts+=1;let o=this.reconnectAttempts;this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.emit("reconnect",{attempt:o}),this.openOnce().catch(d=>{this.emit("error",{message:"Reconnect failed",cause:d}),this.scheduleReconnect();});},i);}resendSubscriptions(){let t=[];for(let s of this.subs.values())s.kind==="ticks"?t.push({kind:"ticks",accountId:s.accountId,symbols:[...s.symbols]}):t.push({kind:s.kind,accountId:s.accountId});t.length>0&&this.send({type:"subscribe",channels:t});}send(t){if(!(!this.ws||this.ws.readyState!==p__default.default.OPEN))try{this.ws.send(JSON.stringify(t));}catch(s){this.emit("error",{message:"Failed to send frame",cause:s});}}async subscribeTicks(t,s){let n=S("ticks",t),i=this.subs.get(n);if(i&&i.kind==="ticks")for(let o of s)i.symbols.add(o);else this.subs.set(n,{kind:"ticks",accountId:t,symbols:new Set(s)});this.send({type:"subscribe",channels:[{kind:"ticks",accountId:t,symbols:s}]});}async unsubscribeTicks(t,s){let n=S("ticks",t),i=this.subs.get(n);if(i&&i.kind==="ticks"){for(let o of s)i.symbols.delete(o);i.symbols.size===0&&this.subs.delete(n);}this.send({type:"unsubscribe",channels:[{kind:"ticks",accountId:t,symbols:s}]});}async subscribePositions(t){this.subs.set(S("positions",t),{kind:"positions",accountId:t}),this.send({type:"subscribe",channels:[{kind:"positions",accountId:t}]});}async subscribeAccount(t){this.subs.set(S("account",t),{kind:"account",accountId:t}),this.send({type:"subscribe",channels:[{kind:"account",accountId:t}]});}async close(){this.closedByUser=true,this.state="closed",this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.stopHeartbeat();let t=this.ws;if(t&&t.readyState!==p__default.default.CLOSED)return new Promise(s=>{let n=()=>s();t.once("close",n);try{t.close();}catch{s();}})}};function S(r,e){return `${r}:${e}`}var P=class{constructor(e,t){this.client=e,this.streamUrl=t.streamUrl,this.apiKey=t.apiKey,this.userAgent=t.userAgent;}async connect(){let e=new k(this.streamUrl,this.apiKey,this.userAgent);return e.setBeforeResubscribe(()=>this.client.rearmAll()),await e.connect(),e}};var w=class{constructor(e){this.client=e;}async get(e,t,s){let n=new URLSearchParams({symbol:t.symbol,hours:String(t.hours)});return t.timeframe&&n.set("timeframe",t.timeframe),(await this.client.requestPlain({method:"GET",path:`/v1/accounts/${encodeURIComponent(e)}/candles?${n.toString()}`},s)).candles}};function x(r){return r instanceof Date?r.toISOString():String(r)}var I=class{constructor(e){this.client=e;}async get(e,t={},s){let n=new URLSearchParams;t.symbol&&n.set("symbol",t.symbol),t.from!==void 0&&n.set("from",x(t.from)),t.to!==void 0&&n.set("to",x(t.to)),t.limit!==void 0&&n.set("limit",String(t.limit)),t.waitMs!==void 0&&n.set("waitMs",String(t.waitMs));let i=n.toString();return (await this.client.requestPlain({method:"GET",path:`/v1/accounts/${encodeURIComponent(e)}/history${i?`?${i}`:""}`},s)).trades}};var B="https://api.tickerall.com",F="wss://api.tickerall.com/v1/stream",j=3e4,N="0.1.0",Q=6e4,D=[500,1e3,2e3,4e3,8e3];function W(r){return new Promise(e=>setTimeout(e,r))}var C=class{constructor(e){this.replayQueue=[];this.replayDraining=false;this.keptCreds=new Map;if(!e||!e.apiKey)throw new TypeError("Tickerall: `apiKey` is required.");this.apiKey=e.apiKey,this.baseUrl=G(e.baseUrl??B),this.streamUrl=e.streamUrl??F,this.timeout=e.timeout??j;let t=e.fetch??globalThis.fetch;if(typeof t!="function")throw new TypeError("Tickerall: no `fetch` available \u2014 pass `fetch` in the config (Node <18 needs an explicit fetch).");this.fetchImpl=t.bind(globalThis),this.userAgent=e.userAgent?`${e.userAgent} tickerall-js/${N}`:`tickerall-js/${N}`,this.onRearm=e.onRearm,this.sessions=new b(this),this.accounts=new R(this),this.orders=new v(this),this.positions=new E(this),this.candles=new w(this),this.history=new I(this),this.stream=new P(this,{streamUrl:this.streamUrl,apiKey:this.apiKey,userAgent:this.userAgent});}request(e){return this.requestWithRearm(e,false)}async requestWithRearm(e,t){try{return await this.requestOnce(e)}catch(s){if(!t&&e.accountId!==void 0&&this.keptCreds.has(e.accountId)&&s instanceof a&&s.code==="BROKER_ACCOUNT_NOT_HOT")return await this.rearm(e.accountId),this.requestWithRearm(e,true);throw s}}async requestOnce(e){let t=`${this.baseUrl}${e.path}`,s={Authorization:`Bearer ${this.apiKey}`,"User-Agent":this.userAgent,Accept:"application/json"};e.body!==void 0&&(s["Content-Type"]="application/json"),e.idempotencyKey&&(s["Idempotency-Key"]=e.idempotencyKey);let n=e.timeout??this.timeout,i=new AbortController,o=false,d=setTimeout(()=>{o=true,i.abort(new Error(`Request timed out after ${n}ms`));},n);e.signal&&(e.signal.aborted?i.abort(e.signal.reason):e.signal.addEventListener("abort",()=>i.abort(e.signal.reason),{once:true}));let l;try{l=await this.fetchImpl(t,{method:e.method,headers:s,body:e.body!==void 0?JSON.stringify(e.body):void 0,signal:i.signal});}catch(c){if(clearTimeout(d),o)throw new m({status:0,code:"REQUEST_TIMEOUT",message:`Request timed out after ${n}ms`});if(i.signal.aborted){let A=i.signal.reason?.message??c.message??"Request aborted";throw new a({status:0,code:"REQUEST_ABORTED",message:A})}throw new m({status:0,code:"NETWORK_ERROR",message:c.message||"Network request failed",details:c})}finally{clearTimeout(d);}let O=l.headers.get("x-request-id")??void 0;if(e.expectNoContent&&l.status===204)return;if(!l.ok){let c=await J(l);throw q({status:l.status,code:c?.error??`HTTP_${l.status}`,message:c?.message??l.statusText??"Request failed",details:c?.details,requestId:O})}return l.status===204?void 0:await l.json()}async requestIdempotent(e,t){let s=t?.idempotencyKey??Y(),n={...e,idempotencyKey:s,signal:t?.signal,timeout:t?.timeout};return t?.queueIfReconnecting?this.enqueueReplay(n,t.queueMaxMs??Q):this.request(n)}enqueueReplay(e,t){return new Promise((s,n)=>{this.replayQueue.push({run:()=>this.request(e),resolve:s,reject:n,deadline:Date.now()+t}),this.drainReplayQueue();})}async drainReplayQueue(){if(!this.replayDraining){this.replayDraining=true;try{for(;this.replayQueue.length>0;){let e=this.replayQueue[0];if(Date.now()>=e.deadline){e.reject(new m({status:0,code:"QUEUE_TIMEOUT",message:"Queued request expired before connectivity returned"})),this.replayQueue.shift();continue}let t=0;for(;;)try{e.resolve(await e.run()),this.replayQueue.shift();break}catch(s){let n=s instanceof a&&s.transient,i=e.deadline-Date.now();if(!n||i<=0){e.reject(s),this.replayQueue.shift();break}let o=D[Math.min(t,D.length-1)];t+=1,await W(Math.min(o,i));}}}finally{this.replayDraining=false;}}}requestPlain(e,t){return this.request({...e,signal:t?.signal,timeout:t?.timeout})}registerKeptSession(e,t){this.keptCreds.set(e,t);}unregisterKeptSession(e){this.keptCreds.delete(e);}keptSessionIds(){return [...this.keptCreds.keys()]}async rearm(e){let t=this.keptCreds.get(e);if(!t)throw new a({status:0,code:"NO_KEPT_CREDENTIALS",message:`No kept credentials for account ${e}; call sessions.keepAlive first.`});this.onRearm?.(e),await this.requestIdempotent({method:"POST",path:"/v1/sessions",body:t});}async rearmAll(){await Promise.all([...this.keptCreds.keys()].map(e=>this.rearm(e).catch(()=>{})));}};function G(r){return r.endsWith("/")?r.slice(0,-1):r}async function J(r){try{let e=await r.text();if(!e)return null;try{return JSON.parse(e)}catch{return {error:"PARSE_ERROR",message:e}}}catch{return null}}function Y(){let r=globalThis.crypto;return r&&typeof r.randomUUID=="function"?r.randomUUID():z()}function z(){let r=new Uint8Array(16);for(let t=0;t<16;t++)r[t]=Math.floor(Math.random()*256);r[6]=r[6]&15|64,r[8]=r[8]&63|128;let e=Array.from(r,t=>t.toString(16).padStart(2,"0")).join("");return `${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20)}`}
2
+ exports.Tickerall=C;exports.TickerallApiError=a;exports.TickerallAuthError=h;exports.TickerallBrokerError=f;exports.TickerallForbiddenError=y;exports.TickerallNotFoundError=T;exports.TickerallServiceUnavailableError=m;exports.TickerallStream=k;exports.TickerallValidationError=g;//# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map