nosible 0.2.14 → 0.2.18

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
@@ -148,11 +148,11 @@ function generateSqlFilter(input) {
148
148
  }
149
149
  if (includeDocs) {
150
150
  const docHashes = includeDocs.map((doc) => `'${doc}'`).join(", ");
151
- clauses.push(`doc_hash IN (${docHashes})`);
151
+ clauses.push(`docs IN (${docHashes})`);
152
152
  }
153
153
  if (excludeDocs) {
154
154
  const docHashes = excludeDocs.map((doc) => `'${doc}'`).join(", ");
155
- clauses.push(`doc_hash NOT IN (${docHashes})`);
155
+ clauses.push(`docs NOT IN (${docHashes})`);
156
156
  }
157
157
  if (clauses.length > 0) {
158
158
  sql.push("WHERE " + clauses.join(" AND "));
@@ -386,6 +386,22 @@ var bulkResSchema = import_zod3.default.object({
386
386
  decrypt_using: import_zod3.default.string(),
387
387
  download_from: import_zod3.default.string()
388
388
  });
389
+ var limitItemSchema = import_zod3.default.object({
390
+ name: import_zod3.default.string(),
391
+ query_type: import_zod3.default.string(),
392
+ duration_seconds: import_zod3.default.number(),
393
+ duration_string: import_zod3.default.string(),
394
+ limit: import_zod3.default.number(),
395
+ limited: import_zod3.default.boolean(),
396
+ remaining: import_zod3.default.number(),
397
+ period_start: import_zod3.default.string().nullable(),
398
+ period_end: import_zod3.default.string().nullable()
399
+ });
400
+ var limitsResponseSchema = import_zod3.default.object({
401
+ api_key_id: import_zod3.default.string(),
402
+ subscription_id: import_zod3.default.string(),
403
+ limits: import_zod3.default.array(limitItemSchema)
404
+ });
389
405
 
390
406
  // src/utils/llm.ts
391
407
  var defaultModel = "google/gemini-2.0-flash-001";
@@ -576,21 +592,28 @@ var callNosibleApi = async ({
576
592
  baseUrl = "https://www.nosible.ai/search/v2",
577
593
  endpoint,
578
594
  body,
579
- apiKey
595
+ apiKey,
596
+ method = "POST"
580
597
  }) => {
581
598
  const now = Date.now();
582
- console.debug("Body: ", JSON.stringify(body, null, 2));
583
- console.time(`Request-${endpoint}(${now})`);
599
+ if (method === "POST" && body && process.env.NOSIBLE_DEBUG) {
600
+ console.debug("Body: ", JSON.stringify(body, null, 2));
601
+ }
602
+ if (process.env.NOSIBLE_DEBUG) {
603
+ console.time(`Request-${endpoint}(${now})`);
604
+ }
584
605
  const response = await fetch(`${baseUrl}/${endpoint}`, {
585
- method: "POST",
606
+ method,
586
607
  headers: {
587
608
  Accept: "application/json",
588
- "Content-Type": "application/json",
609
+ ...method === "POST" ? { "Content-Type": "application/json" } : {},
589
610
  "Api-Key": `${apiKey}`
590
611
  },
591
- body: JSON.stringify(body)
612
+ ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
592
613
  });
593
- console.timeEnd(`Request-${endpoint}(${now})`);
614
+ if (process.env.NOSIBLE_DEBUG) {
615
+ console.timeEnd(`Request-${endpoint}(${now})`);
616
+ }
594
617
  if (!response.ok) {
595
618
  console.error("Status: " + response.status);
596
619
  if (response.status === 401) {
@@ -974,175 +997,35 @@ class TopicTrend {
974
997
  }
975
998
  }
976
999
 
977
- // src/utils/userPlan.ts
1000
+ // src/utils/rateLimiters.ts
978
1001
  var import_bottleneck = __toESM(require("bottleneck"));
979
- var planLimits = [
980
- {
981
- key: "basic",
982
- name: "Legacy",
983
- rateLimits: {
984
- scrapeUrl: { minute: 60, month: 1400 },
985
- bulk: { minute: 60, month: 1400 },
986
- fast: { minute: 60, month: 14000 }
987
- }
988
- },
989
- {
990
- key: "pro",
991
- name: "Legacy",
992
- rateLimits: {
993
- scrapeUrl: { minute: 60, month: 6700 },
994
- bulk: { minute: 60, month: 6700 },
995
- fast: { minute: 60, month: 67000 }
996
- }
997
- },
998
- {
999
- key: "pro+",
1000
- name: "Legacy",
1001
- rateLimits: {
1002
- scrapeUrl: { minute: 60, month: 32000 },
1003
- bulk: { minute: 60, month: 32000 },
1004
- fast: { minute: 60, month: 320000 }
1005
- }
1006
- },
1007
- {
1008
- key: "bus",
1009
- name: "Legacy",
1010
- rateLimits: {
1011
- scrapeUrl: { minute: 60, month: 200000 },
1012
- bulk: { minute: 60, month: 200000 },
1013
- fast: { minute: 60, month: 2000000 }
1014
- }
1015
- },
1016
- {
1017
- key: "bus+",
1018
- name: "Legacy",
1019
- rateLimits: {
1020
- scrapeUrl: { minute: 60, month: 500000 },
1021
- bulk: { minute: 60, month: 500000 },
1022
- fast: { minute: 120, month: 5000000 }
1023
- }
1024
- },
1025
- {
1026
- key: "prod",
1027
- name: "Legacy",
1028
- rateLimits: {
1029
- scrapeUrl: { minute: 60, month: 1500000 },
1030
- bulk: { minute: 60, month: 1500000 },
1031
- fast: { minute: 360, month: 15000000 }
1032
- }
1033
- },
1034
- {
1035
- key: "chat",
1036
- name: "Legacy",
1037
- rateLimits: {
1038
- scrapeUrl: { minute: 60, month: 1500000 },
1039
- bulk: { minute: 60, month: 1500000 },
1040
- fast: { minute: 360, month: 15000000 }
1041
- }
1042
- },
1043
- {
1044
- key: "self",
1045
- name: "Legacy",
1046
- rateLimits: {
1047
- scrapeUrl: { minute: 6000, month: 1500000 },
1048
- bulk: { minute: 6000, month: 1500000 },
1049
- fast: { minute: 36000, month: 15000000 }
1050
- }
1051
- },
1052
- {
1053
- key: "test",
1054
- name: "Free",
1055
- rateLimits: {
1056
- scrapeUrl: { minute: 60, month: 300 },
1057
- bulk: { minute: 60, month: 300 },
1058
- fast: { minute: 60, month: 3000 }
1059
- }
1060
- },
1061
- {
1062
- key: "cons",
1063
- name: "Consumer",
1064
- rateLimits: {
1065
- scrapeUrl: { minute: 60, month: 3000 },
1066
- bulk: { minute: 60, month: 3000 },
1067
- fast: { minute: 120, month: 30000 }
1068
- }
1069
- },
1070
- {
1071
- key: "stup",
1072
- name: "Startup",
1073
- rateLimits: {
1074
- scrapeUrl: { minute: 60, month: 30000 },
1075
- bulk: { minute: 60, month: 30000 },
1076
- fast: { minute: 360, month: 300000 }
1077
- }
1078
- },
1079
- {
1080
- key: "busn",
1081
- name: "Business",
1082
- rateLimits: {
1083
- scrapeUrl: { minute: 60, month: 300000 },
1084
- bulk: { minute: 60, month: 300000 },
1085
- fast: { minute: 360, month: 3000000 }
1086
- }
1087
- },
1088
- {
1089
- key: "ent",
1090
- name: "Enterprise",
1091
- rateLimits: {
1092
- scrapeUrl: { minute: 60, month: 1500000 },
1093
- bulk: { minute: 60, month: 1500000 },
1094
- fast: { minute: 360, month: 15000000 }
1095
- }
1002
+ var createChainedLimiter = (limitItems) => {
1003
+ if (limitItems.length === 0) {
1004
+ return new import_bottleneck.default;
1096
1005
  }
1097
- ];
1098
- var getUserPlan = (apiKey) => {
1099
- if (apiKey.split("|").length > 2) {
1100
- throw new Error("Invalid API key");
1101
- }
1102
- const planKey = apiKey.split("|")[0];
1103
- const plan = planLimits.find((p) => p.key === planKey);
1104
- if (!plan) {
1105
- throw new Error("Invalid API key");
1006
+ const sortedLimits = [...limitItems].sort((a, b) => a.duration_seconds - b.duration_seconds);
1007
+ const limiters = sortedLimits.map((limitItem) => {
1008
+ const intervalMs = limitItem.duration_seconds * 1000;
1009
+ return new import_bottleneck.default({
1010
+ reservoir: limitItem.remaining ?? limitItem.limit,
1011
+ reservoirRefreshAmount: limitItem.limit,
1012
+ reservoirRefreshInterval: intervalMs
1013
+ });
1014
+ });
1015
+ let chainedLimiter = limiters[0];
1016
+ for (let i = 1;i < limiters.length; i++) {
1017
+ chainedLimiter = chainedLimiter.chain(limiters[i]);
1106
1018
  }
1107
- return plan;
1019
+ return chainedLimiter;
1108
1020
  };
1109
- var minMs = 60 * 1000;
1110
- var monthMs = 60 * 60 * 24 * 30 * 1000;
1111
- var getLimiters = (planKey) => {
1112
- const plan = getUserPlan(planKey);
1113
- const scrapeUrlMinLimiter = new import_bottleneck.default({
1114
- reservoir: plan.rateLimits.scrapeUrl.minute,
1115
- reservoirRefreshAmount: plan.rateLimits.scrapeUrl.minute,
1116
- reservoirRefreshInterval: minMs
1117
- });
1118
- const scrapeUrlMonthLimiter = new import_bottleneck.default({
1119
- reservoir: plan.rateLimits.scrapeUrl.month,
1120
- reservoirRefreshAmount: plan.rateLimits.scrapeUrl.month,
1121
- reservoirRefreshInterval: monthMs
1122
- });
1123
- const scrapeUrlLimiter = scrapeUrlMinLimiter.chain(scrapeUrlMonthLimiter);
1124
- const bulkMinLimiter = new import_bottleneck.default({
1125
- reservoir: plan.rateLimits.bulk.minute,
1126
- reservoirRefreshAmount: plan.rateLimits.bulk.minute,
1127
- reservoirRefreshInterval: minMs
1128
- });
1129
- const bulkMonthLimiter = new import_bottleneck.default({
1130
- reservoir: plan.rateLimits.bulk.month,
1131
- reservoirRefreshAmount: plan.rateLimits.bulk.month,
1132
- reservoirRefreshInterval: monthMs
1133
- });
1134
- const bulkLimiter = bulkMinLimiter.chain(bulkMonthLimiter);
1135
- const fastMinLimiter = new import_bottleneck.default({
1136
- reservoir: plan.rateLimits.fast.minute,
1137
- reservoirRefreshAmount: plan.rateLimits.fast.minute,
1138
- reservoirRefreshInterval: minMs
1139
- });
1140
- const fastMonthLimiter = new import_bottleneck.default({
1141
- reservoir: plan.rateLimits.fast.month,
1142
- reservoirRefreshAmount: plan.rateLimits.fast.month,
1143
- reservoirRefreshInterval: monthMs
1144
- });
1145
- const fastLimiter = fastMinLimiter.chain(fastMonthLimiter);
1021
+ var createLimiters = (limitsResponse) => {
1022
+ const { limits } = limitsResponse;
1023
+ const visitLimits = limits.filter((l) => l.query_type === "visit");
1024
+ const slowLimits = limits.filter((l) => l.query_type === "slow");
1025
+ const fastLimits = limits.filter((l) => l.query_type === "fast");
1026
+ const scrapeUrlLimiter = createChainedLimiter(visitLimits);
1027
+ const bulkLimiter = createChainedLimiter(slowLimits);
1028
+ const fastLimiter = createChainedLimiter(fastLimits);
1146
1029
  return {
1147
1030
  scrapeUrl: scrapeUrlLimiter,
1148
1031
  bulk: bulkLimiter,
@@ -1708,23 +1591,26 @@ var nosibleClientParams = import_zod7.default.union([
1708
1591
  openAiBaseURL: import_zod7.default.string().optional(),
1709
1592
  sentimentModel: import_zod7.default.string().optional(),
1710
1593
  expansionsModel: import_zod7.default.string().optional(),
1711
- searchDefaults: clientDefaultSearchParamsSchema.optional()
1594
+ searchDefaults: clientDefaultSearchParamsSchema.optional(),
1595
+ rateLimit: import_zod7.default.boolean().optional()
1712
1596
  })
1713
1597
  ]);
1714
1598
 
1715
1599
  class NosibleClient {
1716
1600
  baseUrl = "https://www.nosible.ai/search/v2";
1717
1601
  apiKey;
1602
+ api_key_version;
1718
1603
  llmClient;
1719
1604
  expansionsModel = "openai/gpt-4o";
1720
1605
  searchDefaults;
1721
- plan;
1606
+ rateLimit = true;
1722
1607
  limiters;
1723
1608
  constructor(params) {
1724
1609
  let apiKeyToUse;
1725
1610
  let llmApiKeyToUse;
1726
1611
  let openAiBaseURL;
1727
1612
  let searchDefaultsToUse;
1613
+ let rateLimitToUse = true;
1728
1614
  if (typeof params === "string") {
1729
1615
  apiKeyToUse = params;
1730
1616
  } else if (params) {
@@ -1732,6 +1618,7 @@ class NosibleClient {
1732
1618
  llmApiKeyToUse = params.llmApiKey || process.env.LLM_API_KEY;
1733
1619
  openAiBaseURL = params.openAiBaseURL;
1734
1620
  searchDefaultsToUse = params.searchDefaults;
1621
+ rateLimitToUse = params.rateLimit ?? false;
1735
1622
  } else {
1736
1623
  apiKeyToUse = process.env.NOSIBLE_API_KEY;
1737
1624
  llmApiKeyToUse = process.env.LLM_API_KEY;
@@ -1749,8 +1636,7 @@ class NosibleClient {
1749
1636
  }
1750
1637
  this.apiKey = apiKeyToUse;
1751
1638
  this.searchDefaults = searchDefaultsToUse;
1752
- this.plan = getUserPlan(this.apiKey);
1753
- this.limiters = getLimiters(this.apiKey);
1639
+ this.rateLimit = rateLimitToUse;
1754
1640
  }
1755
1641
  mergeWithDefaultSearch(params) {
1756
1642
  if (!this.searchDefaults) {
@@ -1765,43 +1651,70 @@ class NosibleClient {
1765
1651
  if (!apiKey) {
1766
1652
  throw new Error("API key is required");
1767
1653
  }
1654
+ if (apiKey.startsWith("nos_sk_")) {
1655
+ this.api_key_version = "v2";
1656
+ this.apiKey = apiKey;
1657
+ return;
1658
+ }
1768
1659
  const splits = apiKey.split("|");
1769
1660
  if (splits.length !== 2) {
1770
- throw new Error("API key is invalid - expected format 'key_id|secret_key'");
1661
+ throw new Error("API key is invalid - expected format 'plan_id|secret_key' (v1) or 'nos_sk_...' (v2)");
1771
1662
  }
1663
+ this.api_key_version = "v1";
1772
1664
  this.apiKey = apiKey;
1773
1665
  }
1774
- async _request(endpoint, body) {
1666
+ async _request(endpoint, body, method = "POST") {
1775
1667
  return callNosibleApi({
1776
1668
  endpoint,
1777
1669
  body,
1778
- apiKey: this.apiKey
1670
+ apiKey: this.apiKey,
1671
+ method
1779
1672
  });
1780
1673
  }
1674
+ async _initializeLimiters() {
1675
+ if (!this.rateLimit || this.limiters) {
1676
+ return;
1677
+ }
1678
+ const limitsResponse = await this.limits();
1679
+ this.limiters = createLimiters(limitsResponse);
1680
+ }
1681
+ async limits() {
1682
+ const response = await this._request("limits", undefined, "GET");
1683
+ return response;
1684
+ }
1781
1685
  async fastSearch(params) {
1782
- let search;
1783
- const hasSearchInstance = (params2) => {
1784
- return "search" in params2;
1785
- };
1786
- if (hasSearchInstance(params)) {
1787
- if (params.search instanceof Search) {
1788
- search = params.search;
1686
+ if (this.rateLimit && !this.limiters) {
1687
+ await this._initializeLimiters();
1688
+ }
1689
+ const executeSearch = async () => {
1690
+ let search;
1691
+ const hasSearchInstance = (params2) => {
1692
+ return "search" in params2;
1693
+ };
1694
+ if (hasSearchInstance(params)) {
1695
+ if (params.search instanceof Search) {
1696
+ search = params.search;
1697
+ } else {
1698
+ throw new Error("Invalid search parameter provided.");
1699
+ }
1789
1700
  } else {
1790
- throw new Error("Invalid search parameter provided.");
1701
+ const mergedParams = this.mergeWithDefaultSearch(params);
1702
+ search = new Search(mergedParams);
1791
1703
  }
1792
- } else {
1793
- const mergedParams = this.mergeWithDefaultSearch(params);
1794
- search = new Search(mergedParams);
1704
+ const body = await search.searchBody({ client: this });
1705
+ const validatedBody = validateFastSearchParams(body);
1706
+ const { message, query, response } = await this._request("fast-search", validatedBody);
1707
+ const results = response.map((result) => {
1708
+ const fullResult = { ...query, ...result };
1709
+ return fullResult;
1710
+ });
1711
+ const resultSet = createResultSet(this, results);
1712
+ return resultSet;
1713
+ };
1714
+ if (this.rateLimit && this.limiters) {
1715
+ return this.limiters.fast.schedule(executeSearch);
1795
1716
  }
1796
- const body = await search.searchBody({ client: this });
1797
- const validatedBody = validateFastSearchParams(body);
1798
- const { message, query, response } = await this._request("fast-search", validatedBody);
1799
- const results = response.map((result) => {
1800
- const fullResult = { ...query, ...result };
1801
- return fullResult;
1802
- });
1803
- const resultSet = createResultSet(this, results);
1804
- return resultSet;
1717
+ return executeSearch();
1805
1718
  }
1806
1719
  async fastSearches(params) {
1807
1720
  let searchSet;
@@ -1817,49 +1730,74 @@ class NosibleClient {
1817
1730
  return results;
1818
1731
  }
1819
1732
  async aiSearch(params) {
1820
- const json = await this._request("search", params);
1821
- const results = json.response.map((result) => {
1822
- const fullResult = { ...json.query, ...result };
1823
- return fullResult;
1824
- });
1825
- const resultSet = createResultSet(this, results);
1826
- return resultSet;
1733
+ if (this.rateLimit && !this.limiters) {
1734
+ await this._initializeLimiters();
1735
+ }
1736
+ const executeSearch = async () => {
1737
+ const json = await this._request("search", params);
1738
+ const results = json.response.map((result) => {
1739
+ const fullResult = { ...json.query, ...result };
1740
+ return fullResult;
1741
+ });
1742
+ const resultSet = createResultSet(this, results);
1743
+ return resultSet;
1744
+ };
1745
+ if (this.rateLimit && this.limiters) {
1746
+ return this.limiters.fast.schedule(executeSearch);
1747
+ }
1748
+ return executeSearch();
1827
1749
  }
1828
1750
  async bulkSearch(params) {
1829
- let search;
1830
- const hasSearchInstance = (params2) => {
1831
- return "search" in params2;
1832
- };
1833
- if (hasSearchInstance(params)) {
1834
- if (params.search instanceof Search) {
1835
- search = params.search;
1751
+ if (this.rateLimit && !this.limiters) {
1752
+ await this._initializeLimiters();
1753
+ }
1754
+ const executeSearch = async () => {
1755
+ let search;
1756
+ const hasSearchInstance = (params2) => {
1757
+ return "search" in params2;
1758
+ };
1759
+ if (hasSearchInstance(params)) {
1760
+ if (params.search instanceof Search) {
1761
+ search = params.search;
1762
+ } else {
1763
+ throw new Error("Invalid search parameter provided.");
1764
+ }
1836
1765
  } else {
1837
- throw new Error("Invalid search parameter provided.");
1766
+ const mergedParams = this.mergeWithDefaultSearch(params);
1767
+ search = new Search(mergedParams);
1838
1768
  }
1839
- } else {
1840
- const mergedParams = this.mergeWithDefaultSearch(params);
1841
- search = new Search(mergedParams);
1769
+ const body = await search.searchBody({ client: this });
1770
+ const validatedBody = validateBulkSearchParams(body);
1771
+ const fileLocation = await this._request("bulk-search", validatedBody);
1772
+ const json = await bulkSearchDownload({
1773
+ downloadFrom: fileLocation.download_from,
1774
+ decryptUsing: fileLocation.decrypt_using
1775
+ });
1776
+ const results = json.response.map((result) => {
1777
+ const fullResult = { ...json.query, ...result };
1778
+ return fullResult;
1779
+ });
1780
+ const resultSet = createResultSet(this, results);
1781
+ return resultSet;
1782
+ };
1783
+ if (this.rateLimit && this.limiters) {
1784
+ return this.limiters.bulk.schedule(executeSearch);
1842
1785
  }
1843
- const body = await search.searchBody({ client: this });
1844
- const validatedBody = validateBulkSearchParams(body);
1845
- const fileLocation = await this._request("bulk-search", validatedBody);
1846
- const json = await bulkSearchDownload({
1847
- downloadFrom: fileLocation.download_from,
1848
- decryptUsing: fileLocation.decrypt_using
1849
- });
1850
- const results = json.response.map((result) => {
1851
- const fullResult = { ...json.query, ...result };
1852
- return fullResult;
1853
- });
1854
- const resultSet = createResultSet(this, results);
1855
- return resultSet;
1786
+ return executeSearch();
1856
1787
  }
1857
1788
  async scrapeUrl(url) {
1858
- return this.limiters.scrapeUrl.schedule(async () => {
1789
+ if (this.rateLimit && !this.limiters) {
1790
+ await this._initializeLimiters();
1791
+ }
1792
+ const executeScrape = async () => {
1859
1793
  const json = await this._request("scrape-url", { url });
1860
1794
  const webPage = new WebPageData(this, json.response);
1861
1795
  return webPage;
1862
- });
1796
+ };
1797
+ if (this.rateLimit && this.limiters) {
1798
+ return this.limiters.scrapeUrl.schedule(executeScrape);
1799
+ }
1800
+ return executeScrape();
1863
1801
  }
1864
1802
  async topicTrend(query, sqlFilter) {
1865
1803
  const body = { query, sql_filter: sqlFilter };
@@ -1869,4 +1807,4 @@ class NosibleClient {
1869
1807
  }
1870
1808
  }
1871
1809
 
1872
- //# debugId=A7861032A0192DD264756E2164756E21
1810
+ //# debugId=0207BA2B2FF9617B64756E2164756E21