@usherlabs/cex-broker 0.2.35 → 0.2.36

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.
@@ -316541,7 +316541,26 @@ var DEFAULT_CONFIG2 = {
316541
316541
  depositsLimit: 50
316542
316542
  };
316543
316543
  var ALL_CURRENCIES_CODE = "*";
316544
- function nextDepositCursor(deposits, currentSince, depositsLimit) {
316544
+ function depositTimestamp(record) {
316545
+ const observedAt = depositField(record, [
316546
+ "timestamp",
316547
+ "creditedAt",
316548
+ "credited_at",
316549
+ "updated",
316550
+ "updatedAt",
316551
+ "datetime"
316552
+ ]);
316553
+ const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
316554
+ return Number.isFinite(timestamp) ? timestamp : undefined;
316555
+ }
316556
+ function archivedDepositStatus(exchangeId, record) {
316557
+ const rawStatus = asRecord(record.info)?.status;
316558
+ if (exchangeId?.toLowerCase() === "binance" && String(rawStatus ?? "").trim() === "6") {
316559
+ return "credited_not_withdrawable";
316560
+ }
316561
+ return normalizeCcxtTransactionForArchive(record).status;
316562
+ }
316563
+ function nextDepositCursor(deposits, currentSince, depositsLimit, exchangeId) {
316545
316564
  if (deposits.length >= depositsLimit) {
316546
316565
  return currentSince;
316547
316566
  }
@@ -316552,23 +316571,17 @@ function nextDepositCursor(deposits, currentSince, depositsLimit) {
316552
316571
  if (!record) {
316553
316572
  return currentSince;
316554
316573
  }
316555
- const observedAt = depositField(record, [
316556
- "timestamp",
316557
- "creditedAt",
316558
- "credited_at",
316559
- "updated",
316560
- "updatedAt",
316561
- "datetime"
316562
- ]);
316563
- const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
316574
+ const timestamp = depositTimestamp(record);
316575
+ const archiveStatus = archivedDepositStatus(exchangeId, record);
316564
316576
  const status = normalizeDepositStatus(depositField(record, ["status", "state"]));
316565
- if (!Number.isFinite(timestamp)) {
316566
- if (status === "pending") {
316577
+ const isPending = archiveStatus === "credited_not_withdrawable" || status === "pending";
316578
+ if (timestamp === undefined) {
316579
+ if (isPending) {
316567
316580
  return currentSince;
316568
316581
  }
316569
316582
  continue;
316570
316583
  }
316571
- if (status === "pending") {
316584
+ if (isPending) {
316572
316585
  oldestPending = Math.min(oldestPending, timestamp);
316573
316586
  } else {
316574
316587
  next = Math.max(next, timestamp + 1);
@@ -316583,6 +316596,7 @@ class DepositArchivePoller {
316583
316596
  #stopped = false;
316584
316597
  #running = null;
316585
316598
  #cursors = new Map;
316599
+ #lastArchivedByTarget = new Map;
316586
316600
  #unsupportedLogged = new Set;
316587
316601
  #config;
316588
316602
  constructor(params) {
@@ -316682,7 +316696,7 @@ class DepositArchivePoller {
316682
316696
  ]);
316683
316697
  const txid = depositField(record, ["txid", "txId", "tx_hash", "txHash"]);
316684
316698
  const network = depositField(record, ["network", "chain"]);
316685
- const archiveStatus = normalizeCcxtTransactionForArchive(record).status;
316699
+ const archiveStatus = archivedDepositStatus(target.exchangeId, record);
316686
316700
  const creditedAt = depositField(record, [
316687
316701
  "creditedAt",
316688
316702
  "credited_at",
@@ -316692,6 +316706,10 @@ class DepositArchivePoller {
316692
316706
  "datetime"
316693
316707
  ]);
316694
316708
  const depositTxid = txid === undefined ? undefined : String(txid);
316709
+ const lastArchived = depositTxid === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(depositTxid);
316710
+ if (lastArchived && lastArchived.status === archiveStatus) {
316711
+ continue;
316712
+ }
316695
316713
  this.params.archiver.enqueue(buildTransferEventArchiveRow({
316696
316714
  tags: buildCommonArchiveTags({
316697
316715
  deploymentId: this.params.archiver.getDeploymentId(),
@@ -316712,12 +316730,35 @@ class DepositArchivePoller {
316712
316730
  payload: record
316713
316731
  }
316714
316732
  }));
316733
+ if (depositTxid !== undefined) {
316734
+ let targetDeposits2 = this.#lastArchivedByTarget.get(key);
316735
+ if (!targetDeposits2) {
316736
+ targetDeposits2 = new Map;
316737
+ this.#lastArchivedByTarget.set(key, targetDeposits2);
316738
+ }
316739
+ targetDeposits2.set(depositTxid, {
316740
+ status: archiveStatus,
316741
+ timestamp: depositTimestamp(record)
316742
+ });
316743
+ }
316715
316744
  archived += 1;
316716
316745
  }
316717
316746
  if (archived > 0) {
316718
316747
  this.params.metrics?.recordCounter("cex_deposit_poller_deposits_archived_total", archived, { exchange: target.exchangeId });
316719
316748
  }
316720
- this.#cursors.set(key, nextDepositCursor(deposits, since, this.#config.depositsLimit));
316749
+ const nextCursor = nextDepositCursor(deposits, since, this.#config.depositsLimit, target.exchangeId);
316750
+ this.#cursors.set(key, nextCursor);
316751
+ const targetDeposits = this.#lastArchivedByTarget.get(key);
316752
+ if (targetDeposits) {
316753
+ for (const [externalId, lastArchived] of targetDeposits) {
316754
+ if (lastArchived.timestamp !== undefined && lastArchived.timestamp < nextCursor) {
316755
+ targetDeposits.delete(externalId);
316756
+ }
316757
+ }
316758
+ if (targetDeposits.size === 0) {
316759
+ this.#lastArchivedByTarget.delete(key);
316760
+ }
316761
+ }
316721
316762
  }
316722
316763
  #targetKey(target) {
316723
316764
  return `${target.exchangeId}|${target.account.label}|${target.code}`;
@@ -316971,6 +317012,7 @@ function toAttributes(labels, service) {
316971
317012
  class OtelMetrics extends BaseOtelSignal {
316972
317013
  counters = new Map;
316973
317014
  histograms = new Map;
317015
+ observableGauges = new Map;
316974
317016
  constructor(config) {
316975
317017
  super(config, "metrics");
316976
317018
  }
@@ -317056,6 +317098,32 @@ class OtelMetrics extends BaseOtelSignal {
317056
317098
  log.error("Failed to record gauge:", error);
317057
317099
  }
317058
317100
  }
317101
+ async setObservableGauge(metricName, value, labels, service = this.getServiceName()) {
317102
+ const provider = this.getProvider();
317103
+ if (!this.isOtelEnabled() || !provider)
317104
+ return;
317105
+ try {
317106
+ let state = this.observableGauges.get(metricName);
317107
+ if (!state) {
317108
+ const observations = new Map;
317109
+ const instrument = provider.getMeter("cex-broker-metrics", "1.0.0").createObservableGauge(metricName, { description: metricName });
317110
+ instrument.addCallback((result) => {
317111
+ for (const observation of observations.values()) {
317112
+ result.observe(observation.value, observation.attributes);
317113
+ }
317114
+ });
317115
+ state = { instrument, observations };
317116
+ this.observableGauges.set(metricName, state);
317117
+ }
317118
+ const attributes = toAttributes(labels, service);
317119
+ state.observations.set(stableAttributeKey(attributes), {
317120
+ value,
317121
+ attributes
317122
+ });
317123
+ } catch (error) {
317124
+ log.error("Failed to set observable gauge:", error);
317125
+ }
317126
+ }
317059
317127
  async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
317060
317128
  const provider = this.getProvider();
317061
317129
  if (!this.isOtelEnabled() || !provider)
@@ -317160,27 +317228,33 @@ function getOtelProtocolFromEnv() {
317160
317228
  const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
317161
317229
  return protocol || "http";
317162
317230
  }
317163
- function createOtelMetricsFromEnv() {
317231
+ function createOtelMetricsFromEnv(options = {}) {
317164
317232
  const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
317165
- const host = getOtelHostFromEnv();
317233
+ const serviceName = process.env.OTEL_SERVICE_NAME || options.defaultServiceName || DEFAULT_SERVICE;
317166
317234
  if (otlpEndpoint) {
317167
317235
  return new OtelMetrics({
317168
- otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
317169
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
317236
+ otlpEndpoint,
317237
+ serviceName
317170
317238
  });
317171
317239
  }
317172
- if (!host) {
317173
- return new OtelMetrics;
317240
+ if (options.allowLegacyBrokerConfig === false) {
317241
+ return new OtelMetrics({ serviceName });
317174
317242
  }
317243
+ const host = getOtelHostFromEnv();
317244
+ if (!host)
317245
+ return new OtelMetrics({ serviceName });
317175
317246
  const port = getOtelPortFromEnv();
317176
317247
  const config = {
317177
317248
  host,
317178
317249
  port: port ?? DEFAULT_OTLP_PORT,
317179
317250
  protocol: getOtelProtocolFromEnv(),
317180
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
317251
+ serviceName
317181
317252
  };
317182
317253
  return new OtelMetrics(config);
317183
317254
  }
317255
+ function stableAttributeKey(attributes) {
317256
+ return JSON.stringify(Object.entries(attributes).sort(([left], [right]) => left.localeCompare(right)));
317257
+ }
317184
317258
  function createOtelLogsFromEnv() {
317185
317259
  const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
317186
317260
  const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
@@ -10,7 +10,7 @@ export type DepositArchivePollerConfig = {
10
10
  * Advances the inclusive since-watermark only across a fully observed,
11
11
  * terminal prefix of deposit history.
12
12
  */
13
- export declare function nextDepositCursor(deposits: unknown[], currentSince: number, depositsLimit: number): number;
13
+ export declare function nextDepositCursor(deposits: unknown[], currentSince: number, depositsLimit: number, exchangeId?: string): number;
14
14
  /**
15
15
  * Broker-internal periodic capture of venue deposit history. It polls every
16
16
  * configured account sequentially, using ccxt's unfiltered deposit-history
@@ -27,6 +27,12 @@ export interface MetricData {
27
27
  labels: string;
28
28
  service: string;
29
29
  }
30
+ export interface OtelMetricsEnvOptions {
31
+ /** Service name used when OTEL_SERVICE_NAME is not configured. */
32
+ defaultServiceName?: string;
33
+ /** Whether broker-specific legacy collector host variables are accepted. */
34
+ allowLegacyBrokerConfig?: boolean;
35
+ }
30
36
  declare abstract class BaseOtelSignal<TProvider> {
31
37
  private readonly signal;
32
38
  private provider;
@@ -45,6 +51,7 @@ declare abstract class BaseOtelSignal<TProvider> {
45
51
  export declare class OtelMetrics extends BaseOtelSignal<MeterProviderType> {
46
52
  private readonly counters;
47
53
  private readonly histograms;
54
+ private readonly observableGauges;
48
55
  constructor(config?: OtelConfig);
49
56
  protected createProvider(endpoint: string, serviceName: string, appendSignalPath: boolean): MeterProviderType;
50
57
  protected onProviderCreated(provider: MeterProviderType): void;
@@ -54,6 +61,11 @@ export declare class OtelMetrics extends BaseOtelSignal<MeterProviderType> {
54
61
  insertMetrics(metricsList: MetricData[]): Promise<void>;
55
62
  recordCounter(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
56
63
  recordGauge(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
64
+ /**
65
+ * Set a gauge value that is observed on every export. This is suitable for
66
+ * staleness signals where a quiet live process must keep exporting its last value.
67
+ */
68
+ setObservableGauge(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
57
69
  recordHistogram(metricName: string, value: number, labels: Record<string, string | number>, service?: string): Promise<void>;
58
70
  }
59
71
  export declare class OtelLogs extends BaseOtelSignal<LoggerProvider> {
@@ -65,6 +77,6 @@ export declare class OtelLogs extends BaseOtelSignal<LoggerProvider> {
65
77
  protected onProviderClosed(): void;
66
78
  emit(record: LogRecord): void;
67
79
  }
68
- export declare function createOtelMetricsFromEnv(): OtelMetrics;
80
+ export declare function createOtelMetricsFromEnv(options?: OtelMetricsEnvOptions): OtelMetrics;
69
81
  export declare function createOtelLogsFromEnv(): OtelLogs;
70
82
  export {};
package/dist/index.js CHANGED
@@ -292055,7 +292055,26 @@ var DEFAULT_CONFIG2 = {
292055
292055
  depositsLimit: 50
292056
292056
  };
292057
292057
  var ALL_CURRENCIES_CODE = "*";
292058
- function nextDepositCursor(deposits, currentSince, depositsLimit) {
292058
+ function depositTimestamp(record) {
292059
+ const observedAt = depositField(record, [
292060
+ "timestamp",
292061
+ "creditedAt",
292062
+ "credited_at",
292063
+ "updated",
292064
+ "updatedAt",
292065
+ "datetime"
292066
+ ]);
292067
+ const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
292068
+ return Number.isFinite(timestamp) ? timestamp : undefined;
292069
+ }
292070
+ function archivedDepositStatus(exchangeId, record) {
292071
+ const rawStatus = asRecord(record.info)?.status;
292072
+ if (exchangeId?.toLowerCase() === "binance" && String(rawStatus ?? "").trim() === "6") {
292073
+ return "credited_not_withdrawable";
292074
+ }
292075
+ return normalizeCcxtTransactionForArchive(record).status;
292076
+ }
292077
+ function nextDepositCursor(deposits, currentSince, depositsLimit, exchangeId) {
292059
292078
  if (deposits.length >= depositsLimit) {
292060
292079
  return currentSince;
292061
292080
  }
@@ -292066,23 +292085,17 @@ function nextDepositCursor(deposits, currentSince, depositsLimit) {
292066
292085
  if (!record) {
292067
292086
  return currentSince;
292068
292087
  }
292069
- const observedAt = depositField(record, [
292070
- "timestamp",
292071
- "creditedAt",
292072
- "credited_at",
292073
- "updated",
292074
- "updatedAt",
292075
- "datetime"
292076
- ]);
292077
- const timestamp = typeof observedAt === "number" ? observedAt : typeof observedAt === "string" ? Date.parse(observedAt) : Number.NaN;
292088
+ const timestamp = depositTimestamp(record);
292089
+ const archiveStatus = archivedDepositStatus(exchangeId, record);
292078
292090
  const status = normalizeDepositStatus(depositField(record, ["status", "state"]));
292079
- if (!Number.isFinite(timestamp)) {
292080
- if (status === "pending") {
292091
+ const isPending = archiveStatus === "credited_not_withdrawable" || status === "pending";
292092
+ if (timestamp === undefined) {
292093
+ if (isPending) {
292081
292094
  return currentSince;
292082
292095
  }
292083
292096
  continue;
292084
292097
  }
292085
- if (status === "pending") {
292098
+ if (isPending) {
292086
292099
  oldestPending = Math.min(oldestPending, timestamp);
292087
292100
  } else {
292088
292101
  next = Math.max(next, timestamp + 1);
@@ -292097,6 +292110,7 @@ class DepositArchivePoller {
292097
292110
  #stopped = false;
292098
292111
  #running = null;
292099
292112
  #cursors = new Map;
292113
+ #lastArchivedByTarget = new Map;
292100
292114
  #unsupportedLogged = new Set;
292101
292115
  #config;
292102
292116
  constructor(params) {
@@ -292196,7 +292210,7 @@ class DepositArchivePoller {
292196
292210
  ]);
292197
292211
  const txid = depositField(record, ["txid", "txId", "tx_hash", "txHash"]);
292198
292212
  const network = depositField(record, ["network", "chain"]);
292199
- const archiveStatus = normalizeCcxtTransactionForArchive(record).status;
292213
+ const archiveStatus = archivedDepositStatus(target.exchangeId, record);
292200
292214
  const creditedAt = depositField(record, [
292201
292215
  "creditedAt",
292202
292216
  "credited_at",
@@ -292206,6 +292220,10 @@ class DepositArchivePoller {
292206
292220
  "datetime"
292207
292221
  ]);
292208
292222
  const depositTxid = txid === undefined ? undefined : String(txid);
292223
+ const lastArchived = depositTxid === undefined ? undefined : this.#lastArchivedByTarget.get(key)?.get(depositTxid);
292224
+ if (lastArchived && lastArchived.status === archiveStatus) {
292225
+ continue;
292226
+ }
292209
292227
  this.params.archiver.enqueue(buildTransferEventArchiveRow({
292210
292228
  tags: buildCommonArchiveTags({
292211
292229
  deploymentId: this.params.archiver.getDeploymentId(),
@@ -292226,12 +292244,35 @@ class DepositArchivePoller {
292226
292244
  payload: record
292227
292245
  }
292228
292246
  }));
292247
+ if (depositTxid !== undefined) {
292248
+ let targetDeposits2 = this.#lastArchivedByTarget.get(key);
292249
+ if (!targetDeposits2) {
292250
+ targetDeposits2 = new Map;
292251
+ this.#lastArchivedByTarget.set(key, targetDeposits2);
292252
+ }
292253
+ targetDeposits2.set(depositTxid, {
292254
+ status: archiveStatus,
292255
+ timestamp: depositTimestamp(record)
292256
+ });
292257
+ }
292229
292258
  archived += 1;
292230
292259
  }
292231
292260
  if (archived > 0) {
292232
292261
  this.params.metrics?.recordCounter("cex_deposit_poller_deposits_archived_total", archived, { exchange: target.exchangeId });
292233
292262
  }
292234
- this.#cursors.set(key, nextDepositCursor(deposits, since, this.#config.depositsLimit));
292263
+ const nextCursor = nextDepositCursor(deposits, since, this.#config.depositsLimit, target.exchangeId);
292264
+ this.#cursors.set(key, nextCursor);
292265
+ const targetDeposits = this.#lastArchivedByTarget.get(key);
292266
+ if (targetDeposits) {
292267
+ for (const [externalId, lastArchived] of targetDeposits) {
292268
+ if (lastArchived.timestamp !== undefined && lastArchived.timestamp < nextCursor) {
292269
+ targetDeposits.delete(externalId);
292270
+ }
292271
+ }
292272
+ if (targetDeposits.size === 0) {
292273
+ this.#lastArchivedByTarget.delete(key);
292274
+ }
292275
+ }
292235
292276
  }
292236
292277
  #targetKey(target) {
292237
292278
  return `${target.exchangeId}|${target.account.label}|${target.code}`;
@@ -292485,6 +292526,7 @@ function toAttributes(labels, service) {
292485
292526
  class OtelMetrics extends BaseOtelSignal {
292486
292527
  counters = new Map;
292487
292528
  histograms = new Map;
292529
+ observableGauges = new Map;
292488
292530
  constructor(config) {
292489
292531
  super(config, "metrics");
292490
292532
  }
@@ -292570,6 +292612,32 @@ class OtelMetrics extends BaseOtelSignal {
292570
292612
  log.error("Failed to record gauge:", error);
292571
292613
  }
292572
292614
  }
292615
+ async setObservableGauge(metricName, value, labels, service = this.getServiceName()) {
292616
+ const provider = this.getProvider();
292617
+ if (!this.isOtelEnabled() || !provider)
292618
+ return;
292619
+ try {
292620
+ let state = this.observableGauges.get(metricName);
292621
+ if (!state) {
292622
+ const observations = new Map;
292623
+ const instrument = provider.getMeter("cex-broker-metrics", "1.0.0").createObservableGauge(metricName, { description: metricName });
292624
+ instrument.addCallback((result) => {
292625
+ for (const observation of observations.values()) {
292626
+ result.observe(observation.value, observation.attributes);
292627
+ }
292628
+ });
292629
+ state = { instrument, observations };
292630
+ this.observableGauges.set(metricName, state);
292631
+ }
292632
+ const attributes = toAttributes(labels, service);
292633
+ state.observations.set(stableAttributeKey(attributes), {
292634
+ value,
292635
+ attributes
292636
+ });
292637
+ } catch (error) {
292638
+ log.error("Failed to set observable gauge:", error);
292639
+ }
292640
+ }
292573
292641
  async recordHistogram(metricName, value, labels, service = this.getServiceName()) {
292574
292642
  const provider = this.getProvider();
292575
292643
  if (!this.isOtelEnabled() || !provider)
@@ -292674,27 +292742,33 @@ function getOtelProtocolFromEnv() {
292674
292742
  const protocol = process.env.CEX_BROKER_OTEL_PROTOCOL ?? process.env.CEX_BROKER_CLICKHOUSE_PROTOCOL;
292675
292743
  return protocol || "http";
292676
292744
  }
292677
- function createOtelMetricsFromEnv() {
292745
+ function createOtelMetricsFromEnv(options = {}) {
292678
292746
  const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
292679
- const host = getOtelHostFromEnv();
292747
+ const serviceName = process.env.OTEL_SERVICE_NAME || options.defaultServiceName || DEFAULT_SERVICE;
292680
292748
  if (otlpEndpoint) {
292681
292749
  return new OtelMetrics({
292682
- otlpEndpoint: otlpEndpoint.replace(/\/v1\/metrics\/?$/, ""),
292683
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
292750
+ otlpEndpoint,
292751
+ serviceName
292684
292752
  });
292685
292753
  }
292686
- if (!host) {
292687
- return new OtelMetrics;
292754
+ if (options.allowLegacyBrokerConfig === false) {
292755
+ return new OtelMetrics({ serviceName });
292688
292756
  }
292757
+ const host = getOtelHostFromEnv();
292758
+ if (!host)
292759
+ return new OtelMetrics({ serviceName });
292689
292760
  const port = getOtelPortFromEnv();
292690
292761
  const config = {
292691
292762
  host,
292692
292763
  port: port ?? DEFAULT_OTLP_PORT,
292693
292764
  protocol: getOtelProtocolFromEnv(),
292694
- serviceName: process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE
292765
+ serviceName
292695
292766
  };
292696
292767
  return new OtelMetrics(config);
292697
292768
  }
292769
+ function stableAttributeKey(attributes) {
292770
+ return JSON.stringify(Object.entries(attributes).sort(([left], [right]) => left.localeCompare(right)));
292771
+ }
292698
292772
  function createOtelLogsFromEnv() {
292699
292773
  const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
292700
292774
  const genericEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
@@ -310486,4 +310560,4 @@ export {
310486
310560
  CEXBroker as default
310487
310561
  };
310488
310562
 
310489
- //# debugId=A02D63306D4E3E3864756E2164756E21
310563
+ //# debugId=10F44147004B63EC64756E2164756E21