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