@adaptic/utils 0.0.1028 → 0.0.1030

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/dist/index.mjs CHANGED
@@ -817,11 +817,15 @@ let fs;
817
817
  let path;
818
818
  if (isNode) {
819
819
  try {
820
- const readline = require("readline");
820
+ // Referenced inside the guard: in a browser bundle `require` is not defined,
821
+ // and touching the identifier at module scope would throw before the catch
822
+ // below could degrade the module to no-ops.
823
+ const loadNodeBuiltin = require;
824
+ const readline = loadNodeBuiltin("readline");
821
825
  clearLine = readline.clearLine;
822
826
  cursorTo = readline.cursorTo;
823
- fs = require("fs");
824
- path = require("path");
827
+ fs = loadNodeBuiltin("fs");
828
+ path = loadNodeBuiltin("path");
825
829
  }
826
830
  catch {
827
831
  // Silently degrade — all operations will be no-ops.
@@ -7006,7 +7010,7 @@ async function makeRequest(auth, params) {
7006
7010
  });
7007
7011
  // Handle 207 Multi-Status responses (used by closeAll positions)
7008
7012
  if (response.status === 207 || response.ok) {
7009
- return await response.json();
7013
+ return (await response.json());
7010
7014
  }
7011
7015
  const errorText = await response.text();
7012
7016
  getLogger().error(`Alpaca API error (${response.status}): ${errorText}`, {
@@ -8641,8 +8645,7 @@ async function fetchAccountDetails({ accountId, client, alpacaAccount, auth, })
8641
8645
  if (!response.ok) {
8642
8646
  throw new Error(`Failed to fetch account details: ${response.statusText}`);
8643
8647
  }
8644
- const data = await response.json();
8645
- return data;
8648
+ return (await response.json());
8646
8649
  }
8647
8650
  catch (error) {
8648
8651
  getLogger().error("Error in fetchAccountDetails:", error);
@@ -8709,8 +8712,7 @@ async function fetchPortfolioHistory({ params, accountId, client, alpacaAccount,
8709
8712
  const errorText = await response.text();
8710
8713
  throw new Error(`Failed to fetch portfolio history: ${response.status} ${response.statusText} ${errorText}`);
8711
8714
  }
8712
- const data = await response.json();
8713
- return data;
8715
+ return (await response.json());
8714
8716
  }
8715
8717
  catch (error) {
8716
8718
  getLogger().error("[fetchPortfolioHistory] Error fetching portfolio history call to Alpaca:", error);
@@ -9265,8 +9267,7 @@ const fetchQuote = async (ticker, options) => {
9265
9267
  if (!response.ok) {
9266
9268
  throw new Error(`Failed to fetch quote for ${ticker}: ${response.status}`);
9267
9269
  }
9268
- const data = await response.json();
9269
- return data;
9270
+ return (await response.json());
9270
9271
  }, API_RETRY_CONFIGS.ALPHA_VANTAGE, `AlphaVantage.fetchQuote(${ticker})`);
9271
9272
  });
9272
9273
  };
@@ -9498,7 +9499,7 @@ async function fetchBars(params) {
9498
9499
  const errorText = await response.text();
9499
9500
  throw new Error(`Alpaca API error (${response.status}): ${errorText}`);
9500
9501
  }
9501
- const data = await response.json();
9502
+ const data = (await response.json());
9502
9503
  // Convert timestamp strings to Date objects and merge bars
9503
9504
  Object.entries(data.bars).forEach(([symbol, bars]) => {
9504
9505
  if (allBars[symbol]) {
@@ -9576,7 +9577,7 @@ async function fetchNews(params, auth) {
9576
9577
  const errorText = await response.text();
9577
9578
  throw new Error(`Alpaca API error (${response.status}): ${errorText}`);
9578
9579
  }
9579
- const data = await response.json();
9580
+ const data = (await response.json());
9580
9581
  const pageArticles = (data.news ?? []).map((article) => ({
9581
9582
  id: article.id,
9582
9583
  author: article.author,
@@ -9645,7 +9646,7 @@ async function fetchLatestTrades(params, auth) {
9645
9646
  const errorText = await response.text();
9646
9647
  throw new Error(`Alpaca API error (${response.status}): ${errorText}`);
9647
9648
  }
9648
- const data = await response.json();
9649
+ const data = (await response.json());
9649
9650
  logIfDebug(`Received latest trades for ${Object.keys(data.trades).length} symbols`);
9650
9651
  return data;
9651
9652
  }, API_RETRY_CONFIGS.CRYPTO, `Crypto.fetchLatestTrades(${symbolsParam})`);
@@ -9688,7 +9689,7 @@ async function fetchLatestQuotes(params, auth) {
9688
9689
  const errorText = await response.text();
9689
9690
  throw new Error(`Alpaca API error (${response.status}): ${errorText}`);
9690
9691
  }
9691
- const data = await response.json();
9692
+ const data = (await response.json());
9692
9693
  logIfDebug(`Received latest quotes for ${Object.keys(data.quotes).length} symbols`);
9693
9694
  return data;
9694
9695
  }, API_RETRY_CONFIGS.CRYPTO, `Crypto.fetchLatestQuotes(${symbolsParam})`);
@@ -9898,6 +9899,39 @@ function normalizeMassiveSymbol(symbol) {
9898
9899
  }
9899
9900
  return `X:${base.toUpperCase()}-${quote.toUpperCase()}`;
9900
9901
  }
9902
+ /**
9903
+ * Fields of {@link MassiveTickerInfo} that carry no sensible default and whose
9904
+ * absence therefore makes a ticker-details response unusable rather than
9905
+ * merely sparse.
9906
+ */
9907
+ const REQUIRED_TICKER_FIELDS = [
9908
+ "active",
9909
+ "currency_name",
9910
+ "locale",
9911
+ "market",
9912
+ "name",
9913
+ "primary_exchange",
9914
+ "ticker",
9915
+ "type",
9916
+ ];
9917
+ /**
9918
+ * Rejects a ticker payload that is missing a field {@link MassiveTickerInfo}
9919
+ * declares as required, naming the first field found absent.
9920
+ *
9921
+ * The vendor answers 200 for a partially-populated ticker, so without this
9922
+ * check an absent field would be published to consumers as a value typed
9923
+ * `string` that holds `undefined` — an unknown masquerading as a reading.
9924
+ *
9925
+ * @param results - The ticker payload as received from the vendor.
9926
+ * @throws When any required field is absent.
9927
+ */
9928
+ function assertRequiredTickerFields(results) {
9929
+ for (const field of REQUIRED_TICKER_FIELDS) {
9930
+ if (results[field] === undefined) {
9931
+ throw new Error(`Missing required field in Massive API response: ${field}`);
9932
+ }
9933
+ }
9934
+ }
9901
9935
  // Use to update general information about stocks
9902
9936
  /**
9903
9937
  * Fetches general information about a stock ticker.
@@ -9920,7 +9954,7 @@ const fetchTickerInfo = async (symbol, options) => {
9920
9954
  await rateLimiters$1.massive.acquire();
9921
9955
  try {
9922
9956
  const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 1000);
9923
- const data = await response.json();
9957
+ const data = (await response.json());
9924
9958
  // Check for "NOT_FOUND" status and return null
9925
9959
  if (data.status === "NOT_FOUND") {
9926
9960
  getLogger().warn(`Ticker not found: ${symbol}`);
@@ -9932,21 +9966,7 @@ const fetchTickerInfo = async (symbol, options) => {
9932
9966
  throw new Error("No results in Massive API response");
9933
9967
  }
9934
9968
  // Validate required fields
9935
- const requiredFields = [
9936
- "active",
9937
- "currency_name",
9938
- "locale",
9939
- "market",
9940
- "name",
9941
- "primary_exchange",
9942
- "ticker",
9943
- "type",
9944
- ];
9945
- for (const field of requiredFields) {
9946
- if (results[field] === undefined) {
9947
- throw new Error(`Missing required field in Massive API response: ${field}`);
9948
- }
9949
- }
9969
+ assertRequiredTickerFields(results);
9950
9970
  // Handle optional share_class_shares_outstanding field
9951
9971
  if (results.share_class_shares_outstanding === undefined) {
9952
9972
  results.share_class_shares_outstanding = null;
@@ -10192,7 +10212,7 @@ const fetchPrices = async (params, options) => {
10192
10212
  //getLogger().info(`Debug: Fetching ${nextUrl}`);
10193
10213
  await rateLimiters$1.massive.acquire();
10194
10214
  const response = await fetchWithRetry(nextUrl, { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 1000);
10195
- const data = await response.json();
10215
+ const data = (await response.json());
10196
10216
  if (!MASSIVE_VALID_STATUSES.has(data.status)) {
10197
10217
  throw new Error(`Massive.com API responded with status: ${data.status}`);
10198
10218
  }
@@ -10219,7 +10239,7 @@ const fetchPrices = async (params, options) => {
10219
10239
  receivedAt: new Date(),
10220
10240
  ...(aggregatedStatus === "DELAYED" ? { delayedSince: null } : {}),
10221
10241
  };
10222
- return allResults.map((entry) => ({
10242
+ const bars = allResults.map((entry) => ({
10223
10243
  date: new Date(entry.t).toLocaleString("en-US", {
10224
10244
  year: "numeric",
10225
10245
  month: "short",
@@ -10241,6 +10261,12 @@ const fetchPrices = async (params, options) => {
10241
10261
  trades: entry.n,
10242
10262
  _freshness: freshness,
10243
10263
  }));
10264
+ // The aggregates endpoint names the ticker once on the envelope rather
10265
+ // than on each bar, and this mapping does not copy it down, so the bars
10266
+ // carry every field of `MassivePriceData` except `symbol`. The assertion
10267
+ // records that divergence at the single point where it arises instead of
10268
+ // leaving it implicit in an untyped payload.
10269
+ return bars;
10244
10270
  }
10245
10271
  catch (error) {
10246
10272
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -10380,7 +10406,7 @@ const fetchGroupedDaily = async (date, options) => {
10380
10406
  await rateLimiters$1.massive.acquire();
10381
10407
  try {
10382
10408
  const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 1000);
10383
- const data = await response.json();
10409
+ const data = (await response.json());
10384
10410
  if (!MASSIVE_VALID_STATUSES.has(data.status)) {
10385
10411
  throw new Error(`Massive.com API responded with status: ${data.status}`);
10386
10412
  }
@@ -10402,6 +10428,10 @@ const fetchGroupedDaily = async (date, options) => {
10402
10428
  trades: result.n,
10403
10429
  })),
10404
10430
  };
10431
+ // Grouped daily bars are keyed by timestamp only; this mapping does not
10432
+ // derive the human-readable `date` string that `MassivePriceData` also
10433
+ // declares, so the reshaped bars carry every field except that one. The
10434
+ // assertion records that divergence at its single point of origin.
10405
10435
  return groupedDaily;
10406
10436
  }
10407
10437
  catch (error) {
@@ -10474,7 +10504,7 @@ symbol, date = new Date(), options) => {
10474
10504
  await rateLimiters$1.massive.acquire();
10475
10505
  try {
10476
10506
  const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 1000);
10477
- const data = await response.json();
10507
+ const data = (await response.json());
10478
10508
  if (!MASSIVE_VALID_STATUSES.has(data.status)) {
10479
10509
  throw new Error(`Failed to fetch daily open/close data for ${symbol}: ${data.status}`);
10480
10510
  }
@@ -10606,6 +10636,19 @@ const validateApiKey = (apiKey) => {
10606
10636
  }
10607
10637
  return key;
10608
10638
  };
10639
+ /**
10640
+ * Narrows a decoded Massive Indices body to its error form.
10641
+ *
10642
+ * The API reports failures in band: the transport answers 200 and the `status`
10643
+ * word carries the outcome, so the body has to be classified before any of its
10644
+ * success fields can be read. Expressing that classification as a type guard is
10645
+ * what lets each fetcher return its payload directly rather than asserting a
10646
+ * success shape over a body that may be an error.
10647
+ *
10648
+ * @param body - A decoded Massive Indices response body.
10649
+ * @returns True when the body reports an error rather than a payload.
10650
+ */
10651
+ const isIndicesErrorResponse = (body) => body.status === "ERROR";
10609
10652
  /**
10610
10653
  * Fetches aggregate bars for an index over a given date range in custom time window sizes.
10611
10654
  *
@@ -10631,8 +10674,8 @@ const fetchIndicesAggregates = async (params, options) => {
10631
10674
  await rateLimiters$1.massive.acquire();
10632
10675
  try {
10633
10676
  const response = await fetchWithRetry(url.toString(), { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 300);
10634
- const data = await response.json();
10635
- if (data.status === "ERROR") {
10677
+ const data = (await response.json());
10678
+ if (isIndicesErrorResponse(data)) {
10636
10679
  throw new Error(`Massive API Error: ${data.error}`);
10637
10680
  }
10638
10681
  return data;
@@ -10661,8 +10704,8 @@ const fetchIndicesPreviousClose = async (indicesTicker, options) => {
10661
10704
  await rateLimiters$1.massive.acquire();
10662
10705
  try {
10663
10706
  const response = await fetchWithRetry(url.toString(), { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 300);
10664
- const data = await response.json();
10665
- if (data.status === "ERROR") {
10707
+ const data = (await response.json());
10708
+ if (isIndicesErrorResponse(data)) {
10666
10709
  throw new Error(`Massive API Error: ${data.error}`);
10667
10710
  }
10668
10711
  return data;
@@ -10692,8 +10735,8 @@ const fetchIndicesDailyOpenClose = async (indicesTicker, date, options) => {
10692
10735
  await rateLimiters$1.massive.acquire();
10693
10736
  try {
10694
10737
  const response = await fetchWithRetry(url.toString(), { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 300);
10695
- const data = await response.json();
10696
- if (data.status === "ERROR") {
10738
+ const data = (await response.json());
10739
+ if (isIndicesErrorResponse(data)) {
10697
10740
  throw new Error(`Massive API Error: ${data.error}`);
10698
10741
  }
10699
10742
  return data;
@@ -10734,8 +10777,8 @@ const fetchIndicesSnapshot = async (params, options) => {
10734
10777
  await rateLimiters$1.massive.acquire();
10735
10778
  try {
10736
10779
  const response = await fetchWithRetry(url.toString(), { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 300);
10737
- const data = await response.json();
10738
- if (data.status === "ERROR") {
10780
+ const data = (await response.json());
10781
+ if (isIndicesErrorResponse(data)) {
10739
10782
  throw new Error(`Massive API Error: ${data.error}`);
10740
10783
  }
10741
10784
  return data;
@@ -10783,8 +10826,8 @@ const fetchUniversalSnapshot = async (tickers, options) => {
10783
10826
  await rateLimiters$1.massive.acquire();
10784
10827
  try {
10785
10828
  const response = await fetchWithRetry(url.toString(), { signal: createTimeoutSignal(DEFAULT_TIMEOUTS.MASSIVE_API) }, 3, 300);
10786
- const data = await response.json();
10787
- if (data.status === "ERROR") {
10829
+ const data = (await response.json());
10830
+ if (isIndicesErrorResponse(data)) {
10788
10831
  throw new Error(`Massive API Error: ${data.error}`);
10789
10832
  }
10790
10833
  return data;
@@ -56541,9 +56584,12 @@ function stripHtml(html) {
56541
56584
  .replace(/”/g, '"')
56542
56585
  .replace(/‘/g, "'")
56543
56586
  .replace(/’/g, "'");
56544
- // Decode numeric HTML entities
56545
- text = text.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
56546
- text = text.replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
56587
+ // Decode numeric HTML entities. The replacer's capture arguments are declared
56588
+ // by `String.prototype.replace` as `...args: any[]`, since their count depends
56589
+ // on the pattern; each pattern here has exactly one capture group, and a
56590
+ // matched group is always a string, so the parameters are named accordingly.
56591
+ text = text.replace(/&#(\d+);/g, (_match, code) => String.fromCharCode(parseInt(code, 10)));
56592
+ text = text.replace(/&#x([0-9a-fA-F]+);/g, (_match, code) => String.fromCharCode(parseInt(code, 16)));
56547
56593
  // Normalize whitespace
56548
56594
  text = text.replace(/\s+/g, " ").trim();
56549
56595
  return text;