@imbingox/acex 0.4.0-beta.7 → 0.4.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/api.md +35 -0
- package/package.json +1 -1
- package/src/adapters/juplend/private-adapter.ts +3 -11
- package/src/client/private-subscription-coordinator.ts +44 -13
- package/src/client/runtime.ts +8 -2
- package/src/errors.ts +155 -2
- package/src/index.ts +8 -1
- package/src/managers/market-manager.ts +46 -6
- package/src/managers/order-manager.ts +12 -3
package/docs/api.md
CHANGED
|
@@ -1458,8 +1458,30 @@ interface AcexInternalError {
|
|
|
1458
1458
|
ts: number;
|
|
1459
1459
|
}
|
|
1460
1460
|
|
|
1461
|
+
interface AcexErrorDetails {
|
|
1462
|
+
venue?: Venue;
|
|
1463
|
+
accountId?: string;
|
|
1464
|
+
symbol?: string;
|
|
1465
|
+
venueError?: {
|
|
1466
|
+
code?: string;
|
|
1467
|
+
message?: string;
|
|
1468
|
+
};
|
|
1469
|
+
transport?: {
|
|
1470
|
+
kind?: "timeout" | "http" | "network" | "rate_limited" | "parse";
|
|
1471
|
+
status?: number;
|
|
1472
|
+
statusText?: string;
|
|
1473
|
+
retryAfterMs?: number;
|
|
1474
|
+
retryable?: boolean;
|
|
1475
|
+
attempts?: number;
|
|
1476
|
+
rawBody?: string;
|
|
1477
|
+
url?: string;
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1461
1481
|
class AcexError extends Error {
|
|
1462
1482
|
readonly code: AcexErrorCode;
|
|
1483
|
+
readonly details?: AcexErrorDetails;
|
|
1484
|
+
readonly cause?: unknown;
|
|
1463
1485
|
}
|
|
1464
1486
|
```
|
|
1465
1487
|
|
|
@@ -1475,10 +1497,23 @@ try {
|
|
|
1475
1497
|
} catch (err) {
|
|
1476
1498
|
if (err instanceof AcexError) {
|
|
1477
1499
|
console.log(err.code, err.message);
|
|
1500
|
+
console.log(err.details?.venueError?.code);
|
|
1501
|
+
console.log(err.details?.venueError?.message);
|
|
1478
1502
|
}
|
|
1479
1503
|
}
|
|
1480
1504
|
```
|
|
1481
1505
|
|
|
1506
|
+
`details.venueError` 是下游读取交易所结构化拒绝原因的首选字段,例如 Binance 返回 `{ "code": -2010, "msg": "Order would immediately trigger." }` 时会映射为:
|
|
1507
|
+
|
|
1508
|
+
```ts
|
|
1509
|
+
{
|
|
1510
|
+
code: "-2010",
|
|
1511
|
+
message: "Order would immediately trigger.",
|
|
1512
|
+
}
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
`details.transport` 保留已脱敏的 HTTP/transport 诊断信息,例如 `kind`、`status`、`retryAfterMs`、`attempts`、`rawBody`、`url`。`rawBody` 和 `url` 只用于排障兜底,不建议作为业务分支首选字段。market stream 首包超时时,`MARKET_STREAM_TIMEOUT` 会带 `details.venue` / `details.symbol` 和底层 `cause`,通常不填 `details.venueError`。`cause` 保留底层错误链,用于高级调试。
|
|
1516
|
+
|
|
1482
1517
|
完整错误码列表:
|
|
1483
1518
|
|
|
1484
1519
|
| Code | 典型场景 |
|
package/package.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import BigNumber from "bignumber.js";
|
|
2
|
-
import { AcexError } from "../../errors.ts";
|
|
3
2
|
import {
|
|
4
3
|
type HttpClientMessages,
|
|
5
4
|
httpRequest,
|
|
@@ -734,28 +733,21 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
|
|
|
734
733
|
_credentials: AccountCredentials,
|
|
735
734
|
_request: CreateOrderRequest,
|
|
736
735
|
): Promise<RawOrderUpdate> {
|
|
737
|
-
throw new
|
|
738
|
-
"VENUE_NOT_SUPPORTED",
|
|
739
|
-
"Juplend is read-only and does not support createOrder",
|
|
740
|
-
);
|
|
736
|
+
throw new Error("Juplend is read-only and does not support createOrder");
|
|
741
737
|
}
|
|
742
738
|
|
|
743
739
|
cancelOrder(
|
|
744
740
|
_credentials: AccountCredentials,
|
|
745
741
|
_request: CancelOrderRequest,
|
|
746
742
|
): Promise<RawOrderUpdate> {
|
|
747
|
-
throw new
|
|
748
|
-
"VENUE_NOT_SUPPORTED",
|
|
749
|
-
"Juplend is read-only and does not support cancelOrder",
|
|
750
|
-
);
|
|
743
|
+
throw new Error("Juplend is read-only and does not support cancelOrder");
|
|
751
744
|
}
|
|
752
745
|
|
|
753
746
|
cancelAllOrders(
|
|
754
747
|
_credentials: AccountCredentials,
|
|
755
748
|
_request: CancelAllOrdersRequest,
|
|
756
749
|
): Promise<RawOrderUpdate[]> {
|
|
757
|
-
throw new
|
|
758
|
-
"VENUE_NOT_SUPPORTED",
|
|
750
|
+
throw new Error(
|
|
759
751
|
"Juplend is read-only and does not support cancelAllOrders",
|
|
760
752
|
);
|
|
761
753
|
}
|
|
@@ -2,8 +2,12 @@ import type {
|
|
|
2
2
|
PrivateUserDataAdapter,
|
|
3
3
|
StreamHandle,
|
|
4
4
|
} from "../adapters/types.ts";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
AcexError,
|
|
7
|
+
buildAcexErrorDetails,
|
|
8
|
+
formatAcexErrorMessage,
|
|
9
|
+
} from "../errors.ts";
|
|
10
|
+
import { isTransportError } from "../internal/http-client.ts";
|
|
7
11
|
import type {
|
|
8
12
|
AccountRuntimeOptions,
|
|
9
13
|
PrivateRuntimeReason,
|
|
@@ -55,14 +59,6 @@ function transportReason(
|
|
|
55
59
|
: fallback;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
function bootstrapErrorDetail(error: unknown): string {
|
|
59
|
-
if (!(error instanceof Error) || !error.message) {
|
|
60
|
-
return "";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return ` (${redactSecrets(error.message)})`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
62
|
export class PrivateSubscriptionCoordinator {
|
|
67
63
|
private readonly context: ClientContext;
|
|
68
64
|
private readonly adapters: Map<Venue, PrivateUserDataAdapter>;
|
|
@@ -260,6 +256,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
260
256
|
throw new AcexError(
|
|
261
257
|
"VENUE_NOT_SUPPORTED",
|
|
262
258
|
`Venue is not supported yet: ${account.venue}`,
|
|
259
|
+
{ details: buildAcexErrorDetails({ venue: account.venue }) },
|
|
263
260
|
);
|
|
264
261
|
}
|
|
265
262
|
|
|
@@ -272,6 +269,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
272
269
|
throw new AcexError(
|
|
273
270
|
"VENUE_NOT_SUPPORTED",
|
|
274
271
|
`Venue is not supported yet: ${venue}`,
|
|
272
|
+
{ details: buildAcexErrorDetails({ venue }) },
|
|
275
273
|
);
|
|
276
274
|
}
|
|
277
275
|
|
|
@@ -503,6 +501,12 @@ export class PrivateSubscriptionCoordinator {
|
|
|
503
501
|
throw new AcexError(
|
|
504
502
|
"CREDENTIALS_MISSING",
|
|
505
503
|
`Account credentials are required for private subscriptions: ${account.accountId}`,
|
|
504
|
+
{
|
|
505
|
+
details: buildAcexErrorDetails({
|
|
506
|
+
accountId: account.accountId,
|
|
507
|
+
venue: account.venue,
|
|
508
|
+
}),
|
|
509
|
+
},
|
|
506
510
|
);
|
|
507
511
|
}
|
|
508
512
|
|
|
@@ -716,10 +720,23 @@ export class PrivateSubscriptionCoordinator {
|
|
|
716
720
|
),
|
|
717
721
|
},
|
|
718
722
|
);
|
|
719
|
-
const
|
|
723
|
+
const details = buildAcexErrorDetails(
|
|
724
|
+
{
|
|
725
|
+
accountId: record.accountId,
|
|
726
|
+
venue: record.venue,
|
|
727
|
+
},
|
|
728
|
+
error,
|
|
729
|
+
);
|
|
720
730
|
throw new AcexError(
|
|
721
731
|
"ACCOUNT_BOOTSTRAP_FAILED",
|
|
722
|
-
|
|
732
|
+
formatAcexErrorMessage(
|
|
733
|
+
`Failed to bootstrap account data: ${record.accountId}`,
|
|
734
|
+
details,
|
|
735
|
+
),
|
|
736
|
+
{
|
|
737
|
+
cause: error,
|
|
738
|
+
details,
|
|
739
|
+
},
|
|
723
740
|
);
|
|
724
741
|
}
|
|
725
742
|
}
|
|
@@ -766,9 +783,23 @@ export class PrivateSubscriptionCoordinator {
|
|
|
766
783
|
reason: transportReason(error, "auth_failed"),
|
|
767
784
|
},
|
|
768
785
|
);
|
|
786
|
+
const details = buildAcexErrorDetails(
|
|
787
|
+
{
|
|
788
|
+
accountId: record.accountId,
|
|
789
|
+
venue: record.venue,
|
|
790
|
+
},
|
|
791
|
+
error,
|
|
792
|
+
);
|
|
769
793
|
throw new AcexError(
|
|
770
794
|
"ORDER_BOOTSTRAP_FAILED",
|
|
771
|
-
|
|
795
|
+
formatAcexErrorMessage(
|
|
796
|
+
`Failed to bootstrap order data: ${record.accountId}`,
|
|
797
|
+
details,
|
|
798
|
+
),
|
|
799
|
+
{
|
|
800
|
+
cause: error,
|
|
801
|
+
details,
|
|
802
|
+
},
|
|
772
803
|
);
|
|
773
804
|
}
|
|
774
805
|
}
|
package/src/client/runtime.ts
CHANGED
|
@@ -9,7 +9,11 @@ import type {
|
|
|
9
9
|
PrivateUserDataAdapter,
|
|
10
10
|
RawOrderUpdate,
|
|
11
11
|
} from "../adapters/types.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
AcexError,
|
|
14
|
+
type AcexErrorCode,
|
|
15
|
+
buildAcexErrorDetails,
|
|
16
|
+
} from "../errors.ts";
|
|
13
17
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
14
18
|
import { matchesHealthFilter } from "../internal/filters.ts";
|
|
15
19
|
import { ReactiveRateLimiter } from "../internal/rate-limiter.ts";
|
|
@@ -431,7 +435,9 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
431
435
|
message: string,
|
|
432
436
|
metadata?: Omit<AcexInternalError, "error" | "source" | "ts">,
|
|
433
437
|
): AcexError {
|
|
434
|
-
const error = new AcexError(code, message
|
|
438
|
+
const error = new AcexError(code, message, {
|
|
439
|
+
details: buildAcexErrorDetails(metadata),
|
|
440
|
+
});
|
|
435
441
|
this.errorBus.publish({
|
|
436
442
|
source: "client",
|
|
437
443
|
ts: this.now(),
|
package/src/errors.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { isTransportError } from "./internal/http-client.ts";
|
|
2
|
+
import type { Venue } from "./types/shared.ts";
|
|
3
|
+
|
|
1
4
|
export type AcexErrorCode =
|
|
2
5
|
| "ACCOUNT_ALREADY_EXISTS"
|
|
3
6
|
| "ACCOUNT_BOOTSTRAP_FAILED"
|
|
@@ -17,12 +20,162 @@ export type AcexErrorCode =
|
|
|
17
20
|
| "ORDER_CREATE_FAILED"
|
|
18
21
|
| "ORDER_INPUT_INVALID";
|
|
19
22
|
|
|
23
|
+
export type AcexErrorTransportKind =
|
|
24
|
+
| "timeout"
|
|
25
|
+
| "http"
|
|
26
|
+
| "network"
|
|
27
|
+
| "rate_limited"
|
|
28
|
+
| "parse";
|
|
29
|
+
|
|
30
|
+
export interface AcexVenueErrorDetails {
|
|
31
|
+
readonly code?: string;
|
|
32
|
+
readonly message?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AcexErrorTransportDetails {
|
|
36
|
+
readonly kind?: AcexErrorTransportKind;
|
|
37
|
+
readonly status?: number;
|
|
38
|
+
readonly statusText?: string;
|
|
39
|
+
readonly retryAfterMs?: number;
|
|
40
|
+
readonly retryable?: boolean;
|
|
41
|
+
readonly attempts?: number;
|
|
42
|
+
readonly rawBody?: string;
|
|
43
|
+
readonly url?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AcexErrorDetails {
|
|
47
|
+
readonly venue?: Venue;
|
|
48
|
+
readonly accountId?: string;
|
|
49
|
+
readonly symbol?: string;
|
|
50
|
+
readonly venueError?: AcexVenueErrorDetails;
|
|
51
|
+
readonly transport?: AcexErrorTransportDetails;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AcexErrorOptions {
|
|
55
|
+
readonly cause?: unknown;
|
|
56
|
+
readonly details?: AcexErrorDetails;
|
|
57
|
+
}
|
|
58
|
+
|
|
20
59
|
export class AcexError extends Error {
|
|
21
60
|
readonly code: AcexErrorCode;
|
|
61
|
+
readonly details?: AcexErrorDetails;
|
|
62
|
+
override readonly cause?: unknown;
|
|
22
63
|
|
|
23
|
-
constructor(
|
|
24
|
-
|
|
64
|
+
constructor(
|
|
65
|
+
code: AcexErrorCode,
|
|
66
|
+
message: string,
|
|
67
|
+
options: AcexErrorOptions = {},
|
|
68
|
+
) {
|
|
69
|
+
super(message, { cause: options.cause });
|
|
25
70
|
this.name = "AcexError";
|
|
26
71
|
this.code = code;
|
|
72
|
+
this.details = options.details;
|
|
73
|
+
this.cause = options.cause;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildAcexErrorDetails(
|
|
78
|
+
context?: Pick<AcexErrorDetails, "venue" | "accountId" | "symbol">,
|
|
79
|
+
cause?: unknown,
|
|
80
|
+
): AcexErrorDetails | undefined {
|
|
81
|
+
const transport = buildTransportDetails(cause);
|
|
82
|
+
const venueError = parseVenueErrorDetails(transport?.rawBody);
|
|
83
|
+
const details: AcexErrorDetails = {
|
|
84
|
+
venue: context?.venue,
|
|
85
|
+
accountId: context?.accountId,
|
|
86
|
+
symbol: context?.symbol,
|
|
87
|
+
venueError,
|
|
88
|
+
transport,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return hasDetails(details) ? details : undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function formatAcexErrorMessage(
|
|
95
|
+
message: string,
|
|
96
|
+
details?: AcexErrorDetails,
|
|
97
|
+
): string {
|
|
98
|
+
const venueErrorMessage = details?.venueError?.message?.trim();
|
|
99
|
+
if (!venueErrorMessage) {
|
|
100
|
+
return message;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const venue = details?.venue;
|
|
104
|
+
const venueLabel = venue ? formatVenueLabel(venue) : "Exchange";
|
|
105
|
+
return `${message} (${venueLabel} rejected: ${venueErrorMessage})`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildTransportDetails(
|
|
109
|
+
cause: unknown,
|
|
110
|
+
): AcexErrorTransportDetails | undefined {
|
|
111
|
+
if (!isTransportError(cause)) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return pruneUndefined({
|
|
116
|
+
kind: cause.kind,
|
|
117
|
+
status: cause.status,
|
|
118
|
+
statusText: cause.statusText,
|
|
119
|
+
retryAfterMs: cause.retryAfterMs,
|
|
120
|
+
retryable: cause.retryable,
|
|
121
|
+
attempts: cause.attempts,
|
|
122
|
+
rawBody: cause.rawBody,
|
|
123
|
+
url: cause.url,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseVenueErrorDetails(
|
|
128
|
+
rawBody: string | undefined,
|
|
129
|
+
): AcexVenueErrorDetails | undefined {
|
|
130
|
+
if (!rawBody) {
|
|
131
|
+
return undefined;
|
|
27
132
|
}
|
|
133
|
+
|
|
134
|
+
let parsed: unknown;
|
|
135
|
+
try {
|
|
136
|
+
parsed = JSON.parse(rawBody);
|
|
137
|
+
} catch {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const record = parsed as Record<string, unknown>;
|
|
146
|
+
const code = record.code;
|
|
147
|
+
const message = record.msg ?? record.message;
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
(typeof code !== "string" && typeof code !== "number") ||
|
|
151
|
+
typeof message !== "string" ||
|
|
152
|
+
message.trim() === ""
|
|
153
|
+
) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
code: String(code),
|
|
159
|
+
message,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function hasDetails(details: AcexErrorDetails): boolean {
|
|
164
|
+
return Boolean(
|
|
165
|
+
details.venue ||
|
|
166
|
+
details.accountId ||
|
|
167
|
+
details.symbol ||
|
|
168
|
+
details.venueError ||
|
|
169
|
+
details.transport,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function formatVenueLabel(venue: Venue): string {
|
|
174
|
+
return `${venue.charAt(0).toUpperCase()}${venue.slice(1)}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function pruneUndefined<T extends Record<string, unknown>>(input: T): T {
|
|
178
|
+
return Object.fromEntries(
|
|
179
|
+
Object.entries(input).filter(([, value]) => value !== undefined),
|
|
180
|
+
) as T;
|
|
28
181
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export { BigNumber } from "bignumber.js";
|
|
2
2
|
export { createClient } from "./client/create-client.ts";
|
|
3
|
-
export type {
|
|
3
|
+
export type {
|
|
4
|
+
AcexErrorCode,
|
|
5
|
+
AcexErrorDetails,
|
|
6
|
+
AcexErrorOptions,
|
|
7
|
+
AcexErrorTransportDetails,
|
|
8
|
+
AcexErrorTransportKind,
|
|
9
|
+
AcexVenueErrorDetails,
|
|
10
|
+
} from "./errors.ts";
|
|
4
11
|
export { AcexError } from "./errors.ts";
|
|
5
12
|
export * from "./types/index.ts";
|
|
@@ -14,7 +14,11 @@ import type {
|
|
|
14
14
|
HealthReporter,
|
|
15
15
|
ManagerLifecycle,
|
|
16
16
|
} from "../client/context.ts";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
AcexError,
|
|
19
|
+
buildAcexErrorDetails,
|
|
20
|
+
formatAcexErrorMessage,
|
|
21
|
+
} from "../errors.ts";
|
|
18
22
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
19
23
|
import { toCanonical } from "../internal/decimal.ts";
|
|
20
24
|
import { matchesMarketFilter } from "../internal/filters.ts";
|
|
@@ -606,9 +610,17 @@ export class MarketManagerImpl
|
|
|
606
610
|
}
|
|
607
611
|
|
|
608
612
|
private createCatalogLoadError(venue: Venue, error: unknown): AcexError {
|
|
613
|
+
const details = buildAcexErrorDetails({ venue }, error);
|
|
609
614
|
const wrapped = new AcexError(
|
|
610
615
|
"MARKET_CATALOG_LOAD_FAILED",
|
|
611
|
-
|
|
616
|
+
formatAcexErrorMessage(
|
|
617
|
+
`Failed to load market catalog from ${venue}`,
|
|
618
|
+
details,
|
|
619
|
+
),
|
|
620
|
+
{
|
|
621
|
+
cause: error,
|
|
622
|
+
details,
|
|
623
|
+
},
|
|
612
624
|
);
|
|
613
625
|
this.context.publishRuntimeError(
|
|
614
626
|
"adapter",
|
|
@@ -621,9 +633,17 @@ export class MarketManagerImpl
|
|
|
621
633
|
}
|
|
622
634
|
|
|
623
635
|
private createServerTimeFetchError(venue: Venue, error: unknown): AcexError {
|
|
636
|
+
const details = buildAcexErrorDetails({ venue }, error);
|
|
624
637
|
const wrapped = new AcexError(
|
|
625
638
|
"MARKET_SERVER_TIME_FETCH_FAILED",
|
|
626
|
-
|
|
639
|
+
formatAcexErrorMessage(
|
|
640
|
+
`Failed to fetch server time from ${venue}`,
|
|
641
|
+
details,
|
|
642
|
+
),
|
|
643
|
+
{
|
|
644
|
+
cause: error,
|
|
645
|
+
details,
|
|
646
|
+
},
|
|
627
647
|
);
|
|
628
648
|
this.context.publishRuntimeError(
|
|
629
649
|
"adapter",
|
|
@@ -758,11 +778,20 @@ export class MarketManagerImpl
|
|
|
758
778
|
|
|
759
779
|
try {
|
|
760
780
|
await record.l1BookStream.ready;
|
|
761
|
-
} catch {
|
|
781
|
+
} catch (error) {
|
|
782
|
+
record.l1BookStream.close();
|
|
762
783
|
record.l1BookStream = undefined;
|
|
784
|
+
const details = buildAcexErrorDetails(
|
|
785
|
+
{ venue: market.venue, symbol: market.symbol },
|
|
786
|
+
error,
|
|
787
|
+
);
|
|
763
788
|
const timeoutError = new AcexError(
|
|
764
789
|
"MARKET_STREAM_TIMEOUT",
|
|
765
790
|
`Timed out waiting for market data: ${market.symbol}`,
|
|
791
|
+
{
|
|
792
|
+
cause: error,
|
|
793
|
+
details,
|
|
794
|
+
},
|
|
766
795
|
);
|
|
767
796
|
this.context.publishRuntimeError("runtime", timeoutError, {
|
|
768
797
|
venue: market.venue,
|
|
@@ -786,11 +815,20 @@ export class MarketManagerImpl
|
|
|
786
815
|
|
|
787
816
|
try {
|
|
788
817
|
await record.fundingRateStream.ready;
|
|
789
|
-
} catch {
|
|
818
|
+
} catch (error) {
|
|
819
|
+
record.fundingRateStream.close();
|
|
790
820
|
record.fundingRateStream = undefined;
|
|
821
|
+
const details = buildAcexErrorDetails(
|
|
822
|
+
{ venue: market.venue, symbol: market.symbol },
|
|
823
|
+
error,
|
|
824
|
+
);
|
|
791
825
|
const timeoutError = new AcexError(
|
|
792
826
|
"MARKET_STREAM_TIMEOUT",
|
|
793
827
|
`Timed out waiting for market data: ${market.symbol}`,
|
|
828
|
+
{
|
|
829
|
+
cause: error,
|
|
830
|
+
details,
|
|
831
|
+
},
|
|
794
832
|
);
|
|
795
833
|
this.context.publishRuntimeError("runtime", timeoutError, {
|
|
796
834
|
venue: market.venue,
|
|
@@ -1189,7 +1227,9 @@ export class MarketManagerImpl
|
|
|
1189
1227
|
metadata?: { venue?: Venue; symbol?: string },
|
|
1190
1228
|
source: "market" | "client" = "market",
|
|
1191
1229
|
): AcexError {
|
|
1192
|
-
const error = new AcexError(code, message
|
|
1230
|
+
const error = new AcexError(code, message, {
|
|
1231
|
+
details: buildAcexErrorDetails(metadata),
|
|
1232
|
+
});
|
|
1193
1233
|
this.context.publishRuntimeError(source, error, metadata);
|
|
1194
1234
|
return error;
|
|
1195
1235
|
}
|
|
@@ -8,7 +8,11 @@ import type {
|
|
|
8
8
|
PrivateOrderDataConsumer,
|
|
9
9
|
PrivateSubscriptionState,
|
|
10
10
|
} from "../client/context.ts";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
AcexError,
|
|
13
|
+
buildAcexErrorDetails,
|
|
14
|
+
formatAcexErrorMessage,
|
|
15
|
+
} from "../errors.ts";
|
|
12
16
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
13
17
|
import { toCanonical } from "../internal/decimal.ts";
|
|
14
18
|
import { matchesOrderFilter } from "../internal/filters.ts";
|
|
@@ -656,7 +660,8 @@ export class OrderManagerImpl
|
|
|
656
660
|
symbol?: string;
|
|
657
661
|
},
|
|
658
662
|
): AcexError {
|
|
659
|
-
const
|
|
663
|
+
const details = buildAcexErrorDetails(metadata);
|
|
664
|
+
const error = new AcexError(code, message, { details });
|
|
660
665
|
this.context.publishRuntimeError("order", error, metadata);
|
|
661
666
|
return error;
|
|
662
667
|
}
|
|
@@ -683,6 +688,10 @@ export class OrderManagerImpl
|
|
|
683
688
|
error instanceof Error ? error : new Error(message),
|
|
684
689
|
metadata,
|
|
685
690
|
);
|
|
686
|
-
|
|
691
|
+
const details = buildAcexErrorDetails(metadata, error);
|
|
692
|
+
return new AcexError(code, formatAcexErrorMessage(message, details), {
|
|
693
|
+
cause: error,
|
|
694
|
+
details,
|
|
695
|
+
});
|
|
687
696
|
}
|
|
688
697
|
}
|