@fileverse-dev/formulajs 4.4.11-mod-68 → 4.4.11-mod-68-patch-1

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/lib/esm/index.mjs CHANGED
@@ -13142,15 +13142,126 @@ const SERVICES_API_KEY = {
13142
13142
  Defillama: 'Defillama'
13143
13143
  };
13144
13144
 
13145
+ // Proxy map configuration
13146
+ const PROXY_MAP = {
13147
+ Etherscan: {
13148
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13149
+ removeParams: ['apikey']
13150
+ },
13151
+ Basescan: {
13152
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13153
+ removeParams: ['apikey']
13154
+ },
13155
+ Gnosisscan: {
13156
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13157
+ removeParams: ['apikey']
13158
+ },
13159
+ Coingecko: {
13160
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13161
+ removeParams: ['apikey']
13162
+ },
13163
+ Firefly: {
13164
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13165
+ removeParams: ['apikey']
13166
+ },
13167
+ Neynar: {
13168
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13169
+ removeParams: ['api_key']
13170
+ },
13171
+ Safe: {
13172
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13173
+ removeParams: ['api_key']
13174
+ },
13175
+ Defillama: {
13176
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13177
+ removeParams: ['api_key']
13178
+ },
13179
+ GnosisPay: {
13180
+ url: "https://staging-api-proxy-ca4268d7d581.herokuapp.com/proxy",
13181
+ removeParams: ['api_key']
13182
+ },
13183
+ // Add more services as needed. It can be direct url instead of ENV variable
13184
+ // ANOTHER_SERVICE: "https://another-proxy-url.com"
13185
+ };
13186
+
13187
+ /**
13188
+ * Removes specified parameters from a URL
13189
+ * @param {string} url - The original URL
13190
+ * @param {string[]} paramsToRemove - Array of parameter names to remove
13191
+ * @returns {string} URL with specified parameters removed
13192
+ */
13193
+ function removeUrlParams(url, paramsToRemove) {
13194
+ if (!paramsToRemove || paramsToRemove.length === 0) {
13195
+ return url;
13196
+ }
13197
+
13198
+ const urlObj = new URL(url);
13199
+
13200
+ paramsToRemove.forEach(param => {
13201
+ if (urlObj.searchParams.has(param)) {
13202
+ urlObj.searchParams.delete(param);
13203
+ }
13204
+ });
13205
+
13206
+ return urlObj.toString();
13207
+ }
13208
+
13209
+ /**
13210
+ * Handles URL routing through proxy or direct API calls
13211
+ * @param {string} url - The original API URL
13212
+ * @param {string} serviceName - The name of the service (e.g., 'EOA')
13213
+ * @param {string} headers - The name of the service (e.g., 'EOA')
13214
+ * @returns {Object} Object containing URL and HEADERS for the fetch request
13215
+ */
13216
+ function getUrlAndHeaders({ url, serviceName, headers = {} }) {
13217
+ console.log('getUrlAndHeaders new modified function from formulajs', url, apiKeyName, serviceName);
13218
+ // Check if proxy is enabled in localStorage
13219
+ const apiKeyLS = window.localStorage.getItem(SERVICES_API_KEY[serviceName]);
13220
+ const isProxyModeEnabledValue = apiKeyLS === 'DEFAULT_PROXY_MODE';
13221
+
13222
+ // Check if proxy URL exists for this service
13223
+ const proxyConfig = PROXY_MAP[serviceName];
13224
+
13225
+ // If proxy mode is enabled AND proxy URL exists for this service
13226
+ if (isProxyModeEnabledValue && proxyConfig && serviceName && SERVICES_API_KEY[serviceName]) {
13227
+ console.log('isProxyModeEnabledValue', isProxyModeEnabledValue);
13228
+ // Remove specified parameters from the target URL
13229
+ const cleanedUrl = removeUrlParams(url, proxyConfig.removeParams);
13230
+
13231
+ return {
13232
+ URL: proxyConfig.url,
13233
+ HEADERS: {
13234
+ 'target-url': cleanedUrl,
13235
+ method: 'GET',
13236
+ 'Content-Type': 'application/json'
13237
+ }
13238
+ };
13239
+ }
13240
+
13241
+
13242
+ return {
13243
+ URL: url,
13244
+ HEADERS: {
13245
+ ...headers,
13246
+ method: 'GET',
13247
+ 'Content-Type': 'application/json'
13248
+ }
13249
+ };
13250
+ }
13251
+
13145
13252
  const fromTimeStampToBlock = async (timestamp, chain, apiKey) => {
13146
- if(!timestamp || !chain || !apiKey) return
13147
- const chainId = CHAIN_ID_MAP[chain];
13148
- const url = `https://api.etherscan.io/v2/api?module=block&action=getblocknobytime&timestamp=${timestamp}&closest=before&apikey=${apiKey}&chainId=${chainId}`;
13149
- const res = await fetch(url);
13150
- const json = await res.json();
13151
- return parseInt(json.result);
13253
+ if (!timestamp || !chain || !apiKey) return
13254
+ const chainId = CHAIN_ID_MAP[chain];
13255
+ const url = `https://api.etherscan.io/v2/api?module=block&action=getblocknobytime&timestamp=${timestamp}&closest=before&apikey=${apiKey}&chainId=${chainId}`;
13256
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders(url);
13257
+ const res = await fetch(finalUrl, {
13258
+ method: 'GET',
13259
+ headers: HEADERS,
13260
+ });
13261
+ const json = await res.json();
13262
+ return parseInt(json.result);
13152
13263
 
13153
- };
13264
+ };
13154
13265
 
13155
13266
  var fromTimestampToBlock = {
13156
13267
  fromTimeStampToBlock
@@ -13307,7 +13418,11 @@ async function handleScanRequest({
13307
13418
  }
13308
13419
  url += `&page=${page}&offset=${offset}`;
13309
13420
  }
13310
- const res = await fetch(url);
13421
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({url, serviceName: 'Etherscan', headers: {}});
13422
+ const res = await fetch(finalUrl, {
13423
+ method: 'GET',
13424
+ headers: HEADERS,
13425
+ });
13311
13426
  if (!res.ok) {
13312
13427
  throw new NetworkError(apiInfo.apiKeyName, res.status)
13313
13428
  }
@@ -13326,14 +13441,19 @@ async function handleScanRequest({
13326
13441
  }
13327
13442
 
13328
13443
  const fromUsernameToFid = async (username, apiKey) => {
13329
- if(!username) return null
13444
+ if (!username) return null
13330
13445
  const url = `https://api.neynar.com/v2/farcaster/user/search/?q=${username}&limit=5`;
13331
- const res = await fetch(url, {
13332
- headers: {
13446
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({
13447
+ url, serviceName: 'Neynar', headers: {
13333
13448
  'x-api-key': apiKey,
13334
13449
  'x-neynar-experimental': 'false'
13335
13450
  }
13336
13451
  });
13452
+
13453
+ const res = await fetch(finalUrl, {
13454
+ method: 'GET',
13455
+ headers: HEADERS,
13456
+ });
13337
13457
  const json = await res.json();
13338
13458
  const users = json.result ? json.result.users : [];
13339
13459
  const user = users.find(user => user.username === username);
@@ -17561,9 +17681,9 @@ const aaveParamsSchema = objectType({
17561
17681
 
17562
17682
  async function FIREFLY() {
17563
17683
  try {
17564
- const [platform, contentType, identifier, start = 0, end = 10] = argsToArray(arguments);
17684
+ const [platform, contentType, identifier, start = 0, end = 10] = argsToArray(arguments);
17565
17685
 
17566
- validateParams(fireflyParamsSchema, {
17686
+ validateParams(fireflyParamsSchema, {
17567
17687
  platform,
17568
17688
  contentType,
17569
17689
  identifier,
@@ -17585,12 +17705,14 @@ validateParams(fireflyParamsSchema, {
17585
17705
  .filter(Boolean)
17586
17706
  .join(',')
17587
17707
  );
17588
- url.searchParams.set('type', fireFlyPlaformType[platform][contentType]);
17589
- url.searchParams.set('start', String(start));
17590
- url.searchParams.set('end', String(end));
17708
+ url.searchParams.set('type', fireFlyPlaformType[platform][contentType]);
17709
+ url.searchParams.set('start', String(start));
17710
+ url.searchParams.set('end', String(end));
17591
17711
 
17592
- const response = await fetch(url.toString(), {
17593
- headers: { 'x-api-key': apiKey }
17712
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({ url: url.toString(), serviceName: 'Firefly', headers });
17713
+ const response = await fetch(finalUrl, {
17714
+ method: 'GET',
17715
+ headers: HEADERS,
17594
17716
  });
17595
17717
  if (!response.ok) {
17596
17718
  throw new NetworkError(SERVICES_API_KEY.Firefly, response.status)
@@ -17648,15 +17770,18 @@ async function LENS() {
17648
17770
  .join(',')
17649
17771
  );
17650
17772
  const typeMap = {
17651
- posts: 'lensid',
17773
+ posts: 'lensid',
17652
17774
  replies: 'lenspostid',
17653
17775
  };
17654
17776
  url.searchParams.set('type', typeMap[contentType]);
17655
17777
  url.searchParams.set('start', String(start));
17656
- url.searchParams.set('end', String(end));
17778
+ url.searchParams.set('end', String(end));
17657
17779
 
17658
- const response = await fetch(url.toString(), {
17659
- headers: { 'x-api-key': apiKey },
17780
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({ url: url.toString(), serviceName: 'Firefly', headers });
17781
+
17782
+ const response = await fetch(finalUrl, {
17783
+ method: 'GET',
17784
+ headers: HEADERS,
17660
17785
  });
17661
17786
  if (!response.ok) {
17662
17787
  throw new NetworkError(SERVICES_API_KEY.Firefly, response.status)
@@ -17683,7 +17808,7 @@ async function FARCASTER() {
17683
17808
  try {
17684
17809
  const [contentType, identifier, start = 0, end = 10] =
17685
17810
  argsToArray(arguments);
17686
- validateParams(farcasterParamsSchema, {
17811
+ validateParams(farcasterParamsSchema, {
17687
17812
  contentType,
17688
17813
  identifier,
17689
17814
  start,
@@ -17709,16 +17834,19 @@ validateParams(farcasterParamsSchema, {
17709
17834
  .join(',')
17710
17835
  );
17711
17836
  const typeMap = {
17712
- posts: 'farcasterid',
17713
- replies: 'farcasterpostid',
17714
- channels: 'farcasterchannels',
17715
- };
17837
+ posts: 'farcasterid',
17838
+ replies: 'farcasterpostid',
17839
+ channels: 'farcasterchannels',
17840
+ };
17716
17841
  url.searchParams.set('type', typeMap[contentType]);
17717
17842
  url.searchParams.set('start', String(start));
17718
- url.searchParams.set('end', String(end));
17843
+ url.searchParams.set('end', String(end));
17844
+
17845
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({ url: url.toString(), serviceName: 'Firefly', headers });
17719
17846
 
17720
- const response = await fetch(url.toString(), {
17721
- headers: { 'x-api-key': apiKey },
17847
+ const response = await fetch(finalUrl, {
17848
+ method: 'GET',
17849
+ headers: HEADERS,
17722
17850
  });
17723
17851
  if (!response.ok) {
17724
17852
  throw new NetworkError(
@@ -17827,12 +17955,12 @@ async function BLOCKSCOUT() {
17827
17955
  }
17828
17956
 
17829
17957
  async function BASE() {
17830
- try {
17958
+ try {
17831
17959
  const [type, address, startDate, endDate, page, limit] = argsToArray(arguments);
17832
- validateParams(baseParamsSchema, { type, address, startDate, endDate, page, limit });
17960
+ validateParams(baseParamsSchema, { type, address, startDate, endDate, page, limit });
17833
17961
  const API_KEY = window.localStorage.getItem(SERVICES_API_KEY.Basescan);
17834
17962
  if (!API_KEY) throw new MissingApiKeyError(SERVICES_API_KEY.Basescan)
17835
-
17963
+
17836
17964
  return await handleScanRequest({
17837
17965
  type,
17838
17966
  address,
@@ -17845,9 +17973,9 @@ try {
17845
17973
  chainId: CHAIN_ID_MAP.base,
17846
17974
  network: 'base'
17847
17975
  })
17848
- } catch (error) {
17849
- return errorMessageHandler(error, 'BASE')
17850
- }
17976
+ } catch (error) {
17977
+ return errorMessageHandler(error, 'BASE')
17978
+ }
17851
17979
  }
17852
17980
  async function GNOSIS() {
17853
17981
  try {
@@ -17888,9 +18016,9 @@ async function GNOSIS() {
17888
18016
 
17889
18017
  async function NEYNAR() {
17890
18018
  try {
17891
- const neynarParamsSchema = objectType({
17892
- username: stringType().nonempty()
17893
- });
18019
+ const neynarParamsSchema = objectType({
18020
+ username: stringType().nonempty()
18021
+ });
17894
18022
 
17895
18023
  const [username] = argsToArray(arguments);
17896
18024
 
@@ -17904,12 +18032,19 @@ async function NEYNAR() {
17904
18032
 
17905
18033
  const url = `https://api.neynar.com/v2/farcaster/followers?fid=${fid}`;
17906
18034
 
17907
- const response = await fetch(url, {
17908
- headers: {
17909
- 'x-api-key': apiKey,
17910
- 'x-neynar-experimental': 'false',
18035
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({
18036
+ url: url.toString(), serviceName: 'Firefly', headers: {
18037
+ headers: {
18038
+ 'x-api-key': API_KEY,
18039
+ 'x-neynar-experimental': 'false'
18040
+ }
17911
18041
  }
17912
18042
  });
18043
+
18044
+ const response = await fetch(finalUrl, {
18045
+ method: 'GET',
18046
+ headers: HEADERS,
18047
+ });
17913
18048
  if (!response.ok) {
17914
18049
  throw new NetworkError(SERVICES_API_KEY.Neynar, response.status)
17915
18050
  }
@@ -18043,20 +18178,20 @@ async function COINGECKO() {
18043
18178
  break
18044
18179
  }
18045
18180
  case 'market': {
18046
- const map = { all:'', base:'base-ecosystem', meme:'meme-token', aiagents:'ai-agents', bitcoin:'bitcoin-ecosystem', ethereum:'ethereum-ecosystem', hyperliquid:'hyperliquid-ecosystem', pump:'pump-ecosystem', solana:'solana-ecosystem' };
18181
+ const map = { all: '', base: 'base-ecosystem', meme: 'meme-token', aiagents: 'ai-agents', bitcoin: 'bitcoin-ecosystem', ethereum: 'ethereum-ecosystem', hyperliquid: 'hyperliquid-ecosystem', pump: 'pump-ecosystem', solana: 'solana-ecosystem' };
18047
18182
  const _category = map[param1] || '';
18048
18183
  const trend = param2 ? `&price_change_percentage=${param2}` : '';
18049
- url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&include_tokens=top&page=1&per_page=100${_category?`&category=${_category}`:''}${trend}`;
18184
+ url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&include_tokens=top&page=1&per_page=100${_category ? `&category=${_category}` : ''}${trend}`;
18050
18185
  break
18051
18186
  }
18052
18187
  case 'stablecoins': {
18053
- const _category = param1==='all'? 'stablecoins' : param1;
18188
+ const _category = param1 === 'all' ? 'stablecoins' : param1;
18054
18189
  const trend = param2 ? `&price_change_percentage=${param2}` : '';
18055
18190
  url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&category=${_category}&order=market_cap_desc&page=1&per_page=100${trend}`;
18056
18191
  break
18057
18192
  }
18058
18193
  case 'derivatives': {
18059
- url = (!param1 || param1==='all')
18194
+ url = (!param1 || param1 === 'all')
18060
18195
  ? 'https://api.coingecko.com/api/v3/derivatives'
18061
18196
  : `https://api.coingecko.com/api/v3/derivatives/exchanges/${param1}?include_tickers=all`;
18062
18197
  break
@@ -18071,17 +18206,17 @@ async function COINGECKO() {
18071
18206
  throw new NetworkError(SERVICES_API_KEY.Coingecko, res.status)
18072
18207
  }
18073
18208
 
18074
- if (category==='price') {
18209
+ if (category === 'price') {
18075
18210
  const out = {};
18076
18211
  for (const [token, prices] of Object.entries(json))
18077
- for (const [cur,val] of Object.entries(prices))
18078
- out[`${token.charAt(0).toUpperCase()+token.slice(1)}_${cur.toUpperCase()}`]=val;
18212
+ for (const [cur, val] of Object.entries(prices))
18213
+ out[`${token.charAt(0).toUpperCase() + token.slice(1)}_${cur.toUpperCase()}`] = val;
18079
18214
  return [out]
18080
18215
  }
18081
18216
 
18082
18217
  const data = Array.isArray(json) ? json : [json];
18083
- return data.map(item=>{
18084
- const flat={};
18218
+ return data.map(item => {
18219
+ const flat = {};
18085
18220
  for (const [key, value] of Object.entries(item)) {
18086
18221
  if (typeof value !== 'object' || value === null) {
18087
18222
  flat[key] = value;
@@ -18089,12 +18224,13 @@ async function COINGECKO() {
18089
18224
  }
18090
18225
  return flat
18091
18226
  })
18092
- } catch(err) {
18093
- return errorMessageHandler(err,'COINGECKO')
18227
+ } catch (err) {
18228
+ return errorMessageHandler(err, 'COINGECKO')
18094
18229
  }
18095
18230
  }
18096
18231
 
18097
18232
  async function EOA() {
18233
+ console.log('EOA');
18098
18234
  try {
18099
18235
  const [addresses, category, chains, startTime, endTime, page = 1, offset = 10] =
18100
18236
  argsToArray(arguments);
@@ -18103,8 +18239,8 @@ async function EOA() {
18103
18239
  const apiKey = window.localStorage.getItem(SERVICES_API_KEY.Etherscan);
18104
18240
  if (!apiKey) throw new MissingApiKeyError(SERVICES_API_KEY.Etherscan)
18105
18241
 
18106
- const INPUTS = addresses.split(',').map(s=>s.trim()).filter(Boolean);
18107
- const CHAINS = chains.split(',').map(s=>s.trim()).filter(Boolean);
18242
+ const INPUTS = addresses.split(',').map(s => s.trim()).filter(Boolean);
18243
+ const CHAINS = chains.split(',').map(s => s.trim()).filter(Boolean);
18108
18244
 
18109
18245
  const ADDRESS_MAP = {};
18110
18246
  for (const inp of INPUTS) {
@@ -18121,7 +18257,12 @@ async function EOA() {
18121
18257
  const out = [];
18122
18258
 
18123
18259
  async function fetchJSON(url) {
18124
- const res = await fetch(url);
18260
+ const { URL: finalUrl, HEADERS } = getUrlAndHeaders({ url, serviceName: 'Etherscan', headers: {} });
18261
+ console.log('finalUrl', finalUrl, HEADERS);
18262
+ const res = await fetch(finalUrl, {
18263
+ method: 'GET',
18264
+ headers: HEADERS,
18265
+ });
18125
18266
  if (!res.ok) throw new NetworkError(SERVICES_API_KEY.Etherscan, res.status)
18126
18267
  const json = await res.json();
18127
18268
 
@@ -18139,15 +18280,15 @@ async function EOA() {
18139
18280
 
18140
18281
  if (category === 'balance') {
18141
18282
  // chunk 20
18142
- for (let i=0; i<ADDRS.length; i+=20) {
18143
- const slice = ADDRS.slice(i,i+20).join(',');
18283
+ for (let i = 0; i < ADDRS.length; i += 20) {
18284
+ const slice = ADDRS.slice(i, i + 20).join(',');
18144
18285
  const url =
18145
- `https://api.etherscan.io/v2/api?chainid=${chainId}`+
18146
- `&module=account&action=addresstokenbalance&address=${slice}`+
18286
+ `https://api.etherscan.io/v2/api?chainid=${chainId}` +
18287
+ `&module=account&action=addresstokenbalance&address=${slice}` +
18147
18288
  `&page=${page}&offset=${offset}&apikey=${apiKey}`;
18148
18289
  const data = await fetchJSON(url);
18149
18290
  if (!Array.isArray(data)) return data
18150
- data.forEach((item, idx) => out.push({ chain, address: ADDRS[i+idx], name: ADDRESS_MAP[ADDRS[i+idx]], ...item }));
18291
+ data.forEach((item, idx) => out.push({ chain, address: ADDRS[i + idx], name: ADDRESS_MAP[ADDRS[i + idx]], ...item }));
18151
18292
  }
18152
18293
  } else {
18153
18294
  // txns
@@ -18157,9 +18298,9 @@ async function EOA() {
18157
18298
  if (!eb) throw new ValidationError(`Invalid endTime: ${endTime}`)
18158
18299
  for (const addr of ADDRS) {
18159
18300
  const url =
18160
- `https://api.etherscan.io/v2/api?chainid=${chainId}`+
18161
- `&module=account&action=tokentx&address=${addr}`+
18162
- `&startblock=${sb}&endblock=${eb}`+
18301
+ `https://api.etherscan.io/v2/api?chainid=${chainId}` +
18302
+ `&module=account&action=tokentx&address=${addr}` +
18303
+ `&startblock=${sb}&endblock=${eb}` +
18163
18304
  `&page=${page}&offset=${offset}&sort=asc&apikey=${apiKey}`;
18164
18305
  const data = await fetchJSON(url);
18165
18306
  if (!Array.isArray(data)) return data
@@ -18234,20 +18375,20 @@ async function DEFILLAMA() {
18234
18375
 
18235
18376
  switch (category) {
18236
18377
  case 'protocols':
18237
- json = Array.isArray(json) ? json.slice(0,500) : [];
18378
+ json = Array.isArray(json) ? json.slice(0, 500) : [];
18238
18379
  break
18239
18380
  case 'yields':
18240
- json = Array.isArray(json.data) ? json.data.slice(0,500) : [];
18381
+ json = Array.isArray(json.data) ? json.data.slice(0, 500) : [];
18241
18382
  break
18242
18383
  case 'dex':
18243
18384
  case 'fees':
18244
- json = Array.isArray(json.protocols) ? json.protocols.slice(0,500) : [];
18385
+ json = Array.isArray(json.protocols) ? json.protocols.slice(0, 500) : [];
18245
18386
  break
18246
18387
  }
18247
18388
 
18248
18389
  return (Array.isArray(json) ? json : [json]).map(item => {
18249
18390
  const out = {};
18250
- for (const [k,v] of Object.entries(item)) {
18391
+ for (const [k, v] of Object.entries(item)) {
18251
18392
  if (v === null || typeof v !== 'object') out[k] = v;
18252
18393
  }
18253
18394
  return out
@@ -18283,7 +18424,7 @@ async function UNISWAP() {
18283
18424
  // flatten nested
18284
18425
  return json.map(item => {
18285
18426
  const flat = {};
18286
- Object.entries(item).forEach(([k,v]) => {
18427
+ Object.entries(item).forEach(([k, v]) => {
18287
18428
  if (v === null || typeof v !== 'object') flat[k] = v;
18288
18429
  });
18289
18430
  return flat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fileverse-dev/formulajs",
3
- "version": "4.4.11-mod-68",
3
+ "version": "4.4.11-mod-68-patch-1",
4
4
  "description": "JavaScript implementation of most Microsoft Excel formula functions",
5
5
  "author": "Formulajs",
6
6
  "publishConfig": {