@dromney/mapthis 0.1.0

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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +56 -0
  3. package/dist/ai/index.cjs +474 -0
  4. package/dist/ai/index.cjs.map +1 -0
  5. package/dist/ai/index.d.cts +117 -0
  6. package/dist/ai/index.d.ts +117 -0
  7. package/dist/ai/index.js +447 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/domain-CZ-L-ntu.d.ts +163 -0
  10. package/dist/domain-Dc1wSTkf.d.cts +163 -0
  11. package/dist/errors-Bw97z_4m.d.cts +12 -0
  12. package/dist/errors-Bw97z_4m.d.ts +12 -0
  13. package/dist/generate/index.cjs +222 -0
  14. package/dist/generate/index.cjs.map +1 -0
  15. package/dist/generate/index.d.cts +140 -0
  16. package/dist/generate/index.d.ts +140 -0
  17. package/dist/generate/index.js +220 -0
  18. package/dist/generate/index.js.map +1 -0
  19. package/dist/geocoding/index.cjs +90 -0
  20. package/dist/geocoding/index.cjs.map +1 -0
  21. package/dist/geocoding/index.d.cts +36 -0
  22. package/dist/geocoding/index.d.ts +36 -0
  23. package/dist/geocoding/index.js +86 -0
  24. package/dist/geocoding/index.js.map +1 -0
  25. package/dist/index.cjs +546 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +5 -0
  28. package/dist/index.d.ts +5 -0
  29. package/dist/index.js +469 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/parser-CzXzpmVv.d.cts +111 -0
  32. package/dist/parser-N7-fNxeu.d.ts +111 -0
  33. package/dist/react/index.cjs +394 -0
  34. package/dist/react/index.cjs.map +1 -0
  35. package/dist/react/index.js +383 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/schemas-Dy5coqXo.d.cts +484 -0
  38. package/dist/schemas-Dy5coqXo.d.ts +484 -0
  39. package/dist/scrape/index.cjs +133 -0
  40. package/dist/scrape/index.cjs.map +1 -0
  41. package/dist/scrape/index.d.cts +60 -0
  42. package/dist/scrape/index.d.ts +60 -0
  43. package/dist/scrape/index.js +125 -0
  44. package/dist/scrape/index.js.map +1 -0
  45. package/dist/search/index.cjs +76 -0
  46. package/dist/search/index.cjs.map +1 -0
  47. package/dist/search/index.d.cts +75 -0
  48. package/dist/search/index.d.ts +75 -0
  49. package/dist/search/index.js +71 -0
  50. package/dist/search/index.js.map +1 -0
  51. package/dist/types/index.cjs +215 -0
  52. package/dist/types/index.cjs.map +1 -0
  53. package/dist/types/index.d.cts +4 -0
  54. package/dist/types/index.d.ts +4 -0
  55. package/dist/types/index.js +171 -0
  56. package/dist/types/index.js.map +1 -0
  57. package/dist/types-BhqKlq0k.d.ts +31 -0
  58. package/dist/types-rFjK5YcJ.d.cts +31 -0
  59. package/dist/utils/index.cjs +335 -0
  60. package/dist/utils/index.cjs.map +1 -0
  61. package/dist/utils/index.d.cts +363 -0
  62. package/dist/utils/index.d.ts +363 -0
  63. package/dist/utils/index.js +301 -0
  64. package/dist/utils/index.js.map +1 -0
  65. package/package.json +150 -0
@@ -0,0 +1,60 @@
1
+ import { M as MapthisError } from '../errors-Bw97z_4m.js';
2
+
3
+ declare class ScrapeError extends MapthisError {
4
+ constructor(message?: string, options?: ErrorOptions);
5
+ }
6
+ /**
7
+ * Thrown when a provided URL fails validation or cannot be parsed by `fetch`.
8
+ */
9
+ declare class InvalidUrlError extends ScrapeError {
10
+ constructor(message?: string, options?: ErrorOptions);
11
+ }
12
+ /**
13
+ * Thrown when a remote server returns 401 or 403 for an HTML fetch, meaning
14
+ * the page does not permit anonymous scraping.
15
+ */
16
+ declare class HtmlUnauthorizedError extends ScrapeError {
17
+ constructor(message?: string, options?: ErrorOptions);
18
+ }
19
+ /**
20
+ * Thrown when the underlying `html-to-text` conversion fails.
21
+ */
22
+ declare class HtmlToTextError extends ScrapeError {
23
+ constructor(message?: string, options?: ErrorOptions);
24
+ }
25
+
26
+ /**
27
+ * Fetch the raw HTML of a URL, performing light validation first.
28
+ *
29
+ * - URLs without a scheme are prefixed with `https://`.
30
+ * - URL shape is validated via Zod before the network call.
31
+ * - 401/403 responses throw {@link HtmlUnauthorizedError}.
32
+ * - Other non-OK responses throw {@link ScrapeError} with the status text.
33
+ * - `TypeError` from `fetch` (typically malformed URL) is rethrown as
34
+ * {@link InvalidUrlError}.
35
+ *
36
+ * This function does not set any special headers, user agent, or cookies.
37
+ * Sites with aggressive bot-detection may return empty bodies or errors; the
38
+ * caller is responsible for retry / user-agent rotation strategies if needed.
39
+ */
40
+ declare function getHtmlFromUrl(url: string): Promise<string>;
41
+
42
+ /**
43
+ * Convert raw HTML to plain text suitable for LLM consumption.
44
+ *
45
+ * The converter is configured to:
46
+ * - strip anchor hrefs when they duplicate the link text;
47
+ * - skip navigation, footer, comments, related-post widgets, images, and
48
+ * other non-content elements;
49
+ * - collapse repeated blank lines and double spaces so token count doesn't
50
+ * balloon from whitespace.
51
+ */
52
+ declare function htmlToText(html: string): string;
53
+ /**
54
+ * Convenience: fetch a URL and convert its HTML to plain text in one call.
55
+ *
56
+ * Equivalent to `htmlToText(await getHtmlFromUrl(url))`.
57
+ */
58
+ declare function getTextFromUrl(url: string): Promise<string>;
59
+
60
+ export { HtmlToTextError, HtmlUnauthorizedError, InvalidUrlError, ScrapeError, getHtmlFromUrl, getTextFromUrl, htmlToText };
@@ -0,0 +1,125 @@
1
+ import { z } from 'zod';
2
+ import { convert } from 'html-to-text';
3
+
4
+ // src/types/errors.ts
5
+ var MapthisError = class extends Error {
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = "MapthisError";
9
+ }
10
+ };
11
+
12
+ // src/scrape/errors.ts
13
+ var ScrapeError = class extends MapthisError {
14
+ constructor(message, options) {
15
+ super(message, options);
16
+ this.name = "ScrapeError";
17
+ }
18
+ };
19
+ var InvalidUrlError = class extends ScrapeError {
20
+ constructor(message, options) {
21
+ super(message, options);
22
+ this.name = "InvalidUrlError";
23
+ }
24
+ };
25
+ var HtmlUnauthorizedError = class extends ScrapeError {
26
+ constructor(message, options) {
27
+ super(message, options);
28
+ this.name = "HtmlUnauthorizedError";
29
+ }
30
+ };
31
+ var HtmlToTextError = class extends ScrapeError {
32
+ constructor(message, options) {
33
+ super(message, options);
34
+ this.name = "HtmlToTextError";
35
+ }
36
+ };
37
+ var withHttps = (url) => {
38
+ const hasHttp = /^http?:\/\//i.test(url);
39
+ const hasHttps = /^https?:\/\//i.test(url);
40
+ if (!hasHttp && !hasHttps) return `https://${url}`;
41
+ return url;
42
+ };
43
+ async function getHtmlFromUrl(url) {
44
+ const urlFixed = withHttps(url.toLowerCase());
45
+ const isUrl = z.string().url().safeParse(urlFixed);
46
+ if (!isUrl.success) throw new InvalidUrlError(`Invalid URL: ${url}`);
47
+ try {
48
+ const response = await fetch(urlFixed);
49
+ const html = await response.text();
50
+ if (response.status === 403 || response.status === 401) {
51
+ throw new HtmlUnauthorizedError(
52
+ `${response.status} Not authorized to scrape this website`
53
+ );
54
+ }
55
+ if (!response.ok) {
56
+ throw new ScrapeError(
57
+ `Bad response when getting HTML. Status: ${response.status} ${response.statusText}`
58
+ );
59
+ }
60
+ return html;
61
+ } catch (error) {
62
+ if (error instanceof TypeError) {
63
+ throw new InvalidUrlError(`Invalid URL. Original error: ${error.message}`);
64
+ }
65
+ throw error;
66
+ }
67
+ }
68
+ var SKIP_SELECTORS = [
69
+ "img",
70
+ "header",
71
+ "footer",
72
+ "audio",
73
+ "button",
74
+ "canvas",
75
+ "code",
76
+ "nav",
77
+ "#nav",
78
+ "figure",
79
+ "figcaption",
80
+ ".comment",
81
+ ".comments",
82
+ "#comments",
83
+ "#related-posts",
84
+ "#related",
85
+ ".related",
86
+ ".related-posts"
87
+ ];
88
+ function htmlToText(html) {
89
+ try {
90
+ let out = convert(html, {
91
+ wordwrap: false,
92
+ selectors: [
93
+ {
94
+ selector: "a",
95
+ options: {
96
+ ignoreHref: true,
97
+ hideLinkHrefIfSameAsText: true
98
+ }
99
+ },
100
+ ...SKIP_SELECTORS.map((tag) => ({ selector: tag, format: "skip" }))
101
+ ]
102
+ });
103
+ out = out.replaceAll("\r\n", "\n");
104
+ for (let i = 0; i < 20; i++) {
105
+ out = out.replaceAll("\n\n\n", "\n\n");
106
+ out = out.replaceAll(" ", " ");
107
+ }
108
+ return out;
109
+ } catch (error) {
110
+ if (error instanceof Error) {
111
+ throw new HtmlToTextError(`HTML to text conversion failed: ${error.message}`, {
112
+ cause: error
113
+ });
114
+ }
115
+ throw new HtmlToTextError("HTML to text conversion failed");
116
+ }
117
+ }
118
+ async function getTextFromUrl(url) {
119
+ const html = await getHtmlFromUrl(url);
120
+ return htmlToText(html);
121
+ }
122
+
123
+ export { HtmlToTextError, HtmlUnauthorizedError, InvalidUrlError, ScrapeError, getHtmlFromUrl, getTextFromUrl, htmlToText };
124
+ //# sourceMappingURL=index.js.map
125
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/errors.ts","../../src/scrape/errors.ts","../../src/scrape/html.ts","../../src/scrape/text.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base error class for every error thrown by the mapthis package.\n *\n * All feature-specific error classes (scrape, search, ai, geocoding) extend\n * this, so consumers can do `catch (e) { if (e instanceof MapthisError) ... }`\n * to distinguish library errors from other exceptions.\n */\nexport class MapthisError extends Error {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"MapthisError\"\n }\n}\n","import { MapthisError } from \"../types/errors\"\n\nexport class ScrapeError extends MapthisError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"ScrapeError\"\n }\n}\n\n/**\n * Thrown when a provided URL fails validation or cannot be parsed by `fetch`.\n */\nexport class InvalidUrlError extends ScrapeError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"InvalidUrlError\"\n }\n}\n\n/**\n * Thrown when a remote server returns 401 or 403 for an HTML fetch, meaning\n * the page does not permit anonymous scraping.\n */\nexport class HtmlUnauthorizedError extends ScrapeError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"HtmlUnauthorizedError\"\n }\n}\n\n/**\n * Thrown when the underlying `html-to-text` conversion fails.\n */\nexport class HtmlToTextError extends ScrapeError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"HtmlToTextError\"\n }\n}\n","import { z } from \"zod\"\nimport { HtmlUnauthorizedError, InvalidUrlError, ScrapeError } from \"./errors\"\n\nconst withHttps = (url: string): string => {\n const hasHttp = /^http?:\\/\\//i.test(url)\n const hasHttps = /^https?:\\/\\//i.test(url)\n if (!hasHttp && !hasHttps) return `https://${url}`\n return url\n}\n\n/**\n * Fetch the raw HTML of a URL, performing light validation first.\n *\n * - URLs without a scheme are prefixed with `https://`.\n * - URL shape is validated via Zod before the network call.\n * - 401/403 responses throw {@link HtmlUnauthorizedError}.\n * - Other non-OK responses throw {@link ScrapeError} with the status text.\n * - `TypeError` from `fetch` (typically malformed URL) is rethrown as\n * {@link InvalidUrlError}.\n *\n * This function does not set any special headers, user agent, or cookies.\n * Sites with aggressive bot-detection may return empty bodies or errors; the\n * caller is responsible for retry / user-agent rotation strategies if needed.\n */\nexport async function getHtmlFromUrl(url: string): Promise<string> {\n const urlFixed = withHttps(url.toLowerCase())\n const isUrl = z.string().url().safeParse(urlFixed)\n if (!isUrl.success) throw new InvalidUrlError(`Invalid URL: ${url}`)\n\n try {\n const response = await fetch(urlFixed)\n const html = await response.text()\n if (response.status === 403 || response.status === 401) {\n throw new HtmlUnauthorizedError(\n `${response.status} Not authorized to scrape this website`,\n )\n }\n if (!response.ok) {\n throw new ScrapeError(\n `Bad response when getting HTML. Status: ${response.status} ${response.statusText}`,\n )\n }\n return html\n } catch (error) {\n if (error instanceof TypeError) {\n throw new InvalidUrlError(`Invalid URL. Original error: ${error.message}`)\n }\n throw error\n }\n}\n","import { convert } from \"html-to-text\"\nimport { HtmlToTextError } from \"./errors\"\nimport { getHtmlFromUrl } from \"./html\"\n\nconst SKIP_SELECTORS = [\n \"img\",\n \"header\",\n \"footer\",\n \"audio\",\n \"button\",\n \"canvas\",\n \"code\",\n \"nav\",\n \"#nav\",\n \"figure\",\n \"figcaption\",\n \".comment\",\n \".comments\",\n \"#comments\",\n \"#related-posts\",\n \"#related\",\n \".related\",\n \".related-posts\",\n] as const\n\n/**\n * Convert raw HTML to plain text suitable for LLM consumption.\n *\n * The converter is configured to:\n * - strip anchor hrefs when they duplicate the link text;\n * - skip navigation, footer, comments, related-post widgets, images, and\n * other non-content elements;\n * - collapse repeated blank lines and double spaces so token count doesn't\n * balloon from whitespace.\n */\nexport function htmlToText(html: string): string {\n try {\n let out = convert(html, {\n wordwrap: false,\n selectors: [\n {\n selector: \"a\",\n options: {\n ignoreHref: true,\n hideLinkHrefIfSameAsText: true,\n },\n },\n ...SKIP_SELECTORS.map((tag) => ({ selector: tag, format: \"skip\" })),\n ],\n })\n out = out.replaceAll(\"\\r\\n\", \"\\n\")\n // Iteratively collapse blank lines and double spaces. 20 passes is\n // overkill for any realistic input but cheap and bounds worst case.\n for (let i = 0; i < 20; i++) {\n out = out.replaceAll(\"\\n\\n\\n\", \"\\n\\n\")\n out = out.replaceAll(\" \", \" \")\n }\n return out\n } catch (error) {\n if (error instanceof Error) {\n throw new HtmlToTextError(`HTML to text conversion failed: ${error.message}`, {\n cause: error,\n })\n }\n throw new HtmlToTextError(\"HTML to text conversion failed\")\n }\n}\n\n/**\n * Convenience: fetch a URL and convert its HTML to plain text in one call.\n *\n * Equivalent to `htmlToText(await getHtmlFromUrl(url))`.\n */\nexport async function getTextFromUrl(url: string): Promise<string> {\n const html = await getHtmlFromUrl(url)\n return htmlToText(html)\n}\n"]}
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ // src/types/errors.ts
4
+ var MapthisError = class extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = "MapthisError";
8
+ }
9
+ };
10
+
11
+ // src/search/errors.ts
12
+ var SearchError = class extends MapthisError {
13
+ constructor(message, options) {
14
+ super(message, options);
15
+ this.name = "SearchError";
16
+ }
17
+ };
18
+ var NoSearchResultsError = class extends SearchError {
19
+ constructor(message, options) {
20
+ super(message ?? "No search results found", options);
21
+ this.name = "NoSearchResultsError";
22
+ }
23
+ };
24
+
25
+ // src/search/blacklist.ts
26
+ var DEFAULT_SEARCH_BLACKLIST = [
27
+ "reddit.com",
28
+ "quora.com",
29
+ "tripadvisor.com",
30
+ "youtube.com"
31
+ ];
32
+
33
+ // src/search/client.ts
34
+ function createSearchClient(config) {
35
+ const blacklist = config.blacklist ?? DEFAULT_SEARCH_BLACKLIST;
36
+ const exclusions = blacklist.map((site) => `-site:${site}`).join(" ");
37
+ return {
38
+ async getUrlsFromQuery(req) {
39
+ const { query } = req;
40
+ const q = encodeURIComponent(`${query} ${exclusions}`.trim());
41
+ const url = `https://www.googleapis.com/customsearch/v1?key=${config.apiKey}&cx=${config.engineId}&q=${q}`;
42
+ let response;
43
+ try {
44
+ response = await fetch(url);
45
+ } catch (error) {
46
+ throw new SearchError(
47
+ `Search request failed: ${error instanceof Error ? error.message : "unknown"}`,
48
+ { cause: error instanceof Error ? error : void 0 }
49
+ );
50
+ }
51
+ if (!response.ok) {
52
+ throw new SearchError(
53
+ `Search API returned ${response.status} ${response.statusText}`
54
+ );
55
+ }
56
+ const search = await response.json();
57
+ if (!search?.items?.length) throw new NoSearchResultsError();
58
+ return {
59
+ query,
60
+ items: search.items.map((item) => ({
61
+ url: item.link,
62
+ title: item.title,
63
+ snippet: item.snippet,
64
+ image: item.pagemap?.cse_image?.[0]?.src
65
+ }))
66
+ };
67
+ }
68
+ };
69
+ }
70
+
71
+ exports.DEFAULT_SEARCH_BLACKLIST = DEFAULT_SEARCH_BLACKLIST;
72
+ exports.NoSearchResultsError = NoSearchResultsError;
73
+ exports.SearchError = SearchError;
74
+ exports.createSearchClient = createSearchClient;
75
+ //# sourceMappingURL=index.cjs.map
76
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/errors.ts","../../src/search/errors.ts","../../src/search/blacklist.ts","../../src/search/client.ts"],"names":[],"mappings":";;;AAOO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EACpC,WAAA,CAAY,SAAkB,OAAA,EAAwB;AAClD,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AAAA,EAChB;AACJ,CAAA;;;ACVO,IAAM,WAAA,GAAN,cAA0B,YAAA,CAAa;AAAA,EAC1C,WAAA,CAAY,SAAkB,OAAA,EAAwB;AAClD,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AAAA,EAChB;AACJ;AAKO,IAAM,oBAAA,GAAN,cAAmC,WAAA,CAAY;AAAA,EAClD,WAAA,CAAY,SAAkB,OAAA,EAAwB;AAClD,IAAA,KAAA,CAAM,OAAA,IAAW,2BAA2B,OAAO,CAAA;AACnD,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAAA,EAChB;AACJ;;;ACTO,IAAM,wBAAA,GAA8C;AAAA,EACvD,YAAA;AAAA,EACA,WAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACJ;;;ACyBO,SAAS,mBAAmB,MAAA,EAA0C;AACzE,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,wBAAA;AACtC,EAAA,MAAM,UAAA,GAAa,SAAA,CAAU,GAAA,CAAI,CAAC,IAAA,KAAS,SAAS,IAAI,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAEpE,EAAA,OAAO;AAAA,IACH,MAAM,iBAAiB,GAAA,EAA6C;AAChE,MAAA,MAAM,EAAE,OAAM,GAAI,GAAA;AAClB,MAAA,MAAM,CAAA,GAAI,mBAAmB,CAAA,EAAG,KAAK,IAAI,UAAU,CAAA,CAAA,CAAG,MAAM,CAAA;AAC5D,MAAA,MAAM,GAAA,GAAM,kDAAkD,MAAA,CAAO,MAAM,OAAO,MAAA,CAAO,QAAQ,MAAM,CAAC,CAAA,CAAA;AAExG,MAAA,IAAI,QAAA;AACJ,MAAA,IAAI;AACA,QAAA,QAAA,GAAW,MAAM,MAAM,GAAG,CAAA;AAAA,MAC9B,SAAS,KAAA,EAAO;AACZ,QAAA,MAAM,IAAI,WAAA;AAAA,UACN,CAAA,uBAAA,EAA0B,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,UAAU,SAAS,CAAA,CAAA;AAAA,UAC5E,EAAE,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,QAAQ,MAAA;AAAU,SACxD;AAAA,MACJ;AAEA,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,IAAI,WAAA;AAAA,UACN,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,SACjE;AAAA,MACJ;AAEA,MAAA,MAAM,MAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AACpC,MAAA,IAAI,CAAC,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,MAAM,IAAI,oBAAA,EAAqB;AAE3D,MAAA,OAAO;AAAA,QACH,KAAA;AAAA,QACA,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,MAAU;AAAA,UAC/B,KAAK,IAAA,CAAK,IAAA;AAAA,UACV,OAAO,IAAA,CAAK,KAAA;AAAA,UACZ,SAAS,IAAA,CAAK,OAAA;AAAA,UACd,KAAA,EAAO,IAAA,CAAK,OAAA,EAAS,SAAA,GAAY,CAAC,CAAA,EAAG;AAAA,SACzC,CAAE;AAAA,OACN;AAAA,IACJ;AAAA,GACJ;AACJ","file":"index.cjs","sourcesContent":["/**\n * Base error class for every error thrown by the mapthis package.\n *\n * All feature-specific error classes (scrape, search, ai, geocoding) extend\n * this, so consumers can do `catch (e) { if (e instanceof MapthisError) ... }`\n * to distinguish library errors from other exceptions.\n */\nexport class MapthisError extends Error {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"MapthisError\"\n }\n}\n","import { MapthisError } from \"../types/errors\"\n\nexport class SearchError extends MapthisError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"SearchError\"\n }\n}\n\n/**\n * Thrown when a search query returned no items.\n */\nexport class NoSearchResultsError extends SearchError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message ?? \"No search results found\", options)\n this.name = \"NoSearchResultsError\"\n }\n}\n","/**\n * Sites that do not allow anonymous scraping (by robots policy or aggressive\n * bot-detection) and are excluded from search results by default.\n *\n * Consumers can override this by passing their own `blacklist` option when\n * creating a search client, or extend it by spreading\n * `DEFAULT_SEARCH_BLACKLIST` into a custom list.\n */\nexport const DEFAULT_SEARCH_BLACKLIST: readonly string[] = [\n \"reddit.com\",\n \"quora.com\",\n \"tripadvisor.com\",\n \"youtube.com\",\n]\n","import { DEFAULT_SEARCH_BLACKLIST } from \"./blacklist\"\nimport type { CustomSearchResponse } from \"./cse-types\"\nimport { NoSearchResultsError, SearchError } from \"./errors\"\nimport type { SearchRequest, SearchResponse } from \"./types\"\n\nexport interface SearchClientConfig {\n /** Google API key with Custom Search API enabled. */\n apiKey: string\n /** Google Custom Search Engine ID (the `cx` parameter). */\n engineId: string\n /**\n * Domains to exclude from results (added as `-site:` operators to the\n * query). Defaults to {@link DEFAULT_SEARCH_BLACKLIST}.\n */\n blacklist?: readonly string[]\n}\n\nexport interface SearchClient {\n /**\n * Execute a search and return the top results, filtered by the client's\n * blacklist. Throws {@link NoSearchResultsError} if the response is empty\n * and {@link SearchError} on network / API failures.\n */\n getUrlsFromQuery(req: SearchRequest): Promise<SearchResponse>\n}\n\n/**\n * Create a Google Custom Search client.\n *\n * @example\n * ```ts\n * const search = createSearchClient({\n * apiKey: process.env.GOOGLE_CUSTOM_SEARCH_KEY!,\n * engineId: process.env.GOOGLE_CUSTOM_SEARCH_ENGINE!,\n * })\n * const { items } = await search.getUrlsFromQuery({ query: \"best ramen in tokyo\" })\n * ```\n */\nexport function createSearchClient(config: SearchClientConfig): SearchClient {\n const blacklist = config.blacklist ?? DEFAULT_SEARCH_BLACKLIST\n const exclusions = blacklist.map((site) => `-site:${site}`).join(\" \")\n\n return {\n async getUrlsFromQuery(req: SearchRequest): Promise<SearchResponse> {\n const { query } = req\n const q = encodeURIComponent(`${query} ${exclusions}`.trim())\n const url = `https://www.googleapis.com/customsearch/v1?key=${config.apiKey}&cx=${config.engineId}&q=${q}`\n\n let response: Response\n try {\n response = await fetch(url)\n } catch (error) {\n throw new SearchError(\n `Search request failed: ${error instanceof Error ? error.message : \"unknown\"}`,\n { cause: error instanceof Error ? error : undefined },\n )\n }\n\n if (!response.ok) {\n throw new SearchError(\n `Search API returned ${response.status} ${response.statusText}`,\n )\n }\n\n const search = (await response.json()) as CustomSearchResponse\n if (!search?.items?.length) throw new NoSearchResultsError()\n\n return {\n query,\n items: search.items.map((item) => ({\n url: item.link,\n title: item.title,\n snippet: item.snippet,\n image: item.pagemap?.cse_image?.[0]?.src,\n })),\n }\n },\n }\n}\n"]}
@@ -0,0 +1,75 @@
1
+ import { M as MapthisError } from '../errors-Bw97z_4m.cjs';
2
+
3
+ declare class SearchError extends MapthisError {
4
+ constructor(message?: string, options?: ErrorOptions);
5
+ }
6
+ /**
7
+ * Thrown when a search query returned no items.
8
+ */
9
+ declare class NoSearchResultsError extends SearchError {
10
+ constructor(message?: string, options?: ErrorOptions);
11
+ }
12
+
13
+ interface SearchRequest {
14
+ query: string;
15
+ /**
16
+ * Maximum number of items to return. The underlying Google CSE cap is 10
17
+ * per call; consumers wanting more must paginate themselves.
18
+ */
19
+ num?: number;
20
+ }
21
+ interface SearchResultItem {
22
+ url: string;
23
+ title: string;
24
+ snippet: string;
25
+ image?: string;
26
+ }
27
+ interface SearchResponse {
28
+ query: string;
29
+ items: SearchResultItem[];
30
+ }
31
+
32
+ /**
33
+ * Sites that do not allow anonymous scraping (by robots policy or aggressive
34
+ * bot-detection) and are excluded from search results by default.
35
+ *
36
+ * Consumers can override this by passing their own `blacklist` option when
37
+ * creating a search client, or extend it by spreading
38
+ * `DEFAULT_SEARCH_BLACKLIST` into a custom list.
39
+ */
40
+ declare const DEFAULT_SEARCH_BLACKLIST: readonly string[];
41
+
42
+ interface SearchClientConfig {
43
+ /** Google API key with Custom Search API enabled. */
44
+ apiKey: string;
45
+ /** Google Custom Search Engine ID (the `cx` parameter). */
46
+ engineId: string;
47
+ /**
48
+ * Domains to exclude from results (added as `-site:` operators to the
49
+ * query). Defaults to {@link DEFAULT_SEARCH_BLACKLIST}.
50
+ */
51
+ blacklist?: readonly string[];
52
+ }
53
+ interface SearchClient {
54
+ /**
55
+ * Execute a search and return the top results, filtered by the client's
56
+ * blacklist. Throws {@link NoSearchResultsError} if the response is empty
57
+ * and {@link SearchError} on network / API failures.
58
+ */
59
+ getUrlsFromQuery(req: SearchRequest): Promise<SearchResponse>;
60
+ }
61
+ /**
62
+ * Create a Google Custom Search client.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const search = createSearchClient({
67
+ * apiKey: process.env.GOOGLE_CUSTOM_SEARCH_KEY!,
68
+ * engineId: process.env.GOOGLE_CUSTOM_SEARCH_ENGINE!,
69
+ * })
70
+ * const { items } = await search.getUrlsFromQuery({ query: "best ramen in tokyo" })
71
+ * ```
72
+ */
73
+ declare function createSearchClient(config: SearchClientConfig): SearchClient;
74
+
75
+ export { DEFAULT_SEARCH_BLACKLIST, NoSearchResultsError, type SearchClient, type SearchClientConfig, SearchError, type SearchRequest, type SearchResponse, type SearchResultItem, createSearchClient };
@@ -0,0 +1,75 @@
1
+ import { M as MapthisError } from '../errors-Bw97z_4m.js';
2
+
3
+ declare class SearchError extends MapthisError {
4
+ constructor(message?: string, options?: ErrorOptions);
5
+ }
6
+ /**
7
+ * Thrown when a search query returned no items.
8
+ */
9
+ declare class NoSearchResultsError extends SearchError {
10
+ constructor(message?: string, options?: ErrorOptions);
11
+ }
12
+
13
+ interface SearchRequest {
14
+ query: string;
15
+ /**
16
+ * Maximum number of items to return. The underlying Google CSE cap is 10
17
+ * per call; consumers wanting more must paginate themselves.
18
+ */
19
+ num?: number;
20
+ }
21
+ interface SearchResultItem {
22
+ url: string;
23
+ title: string;
24
+ snippet: string;
25
+ image?: string;
26
+ }
27
+ interface SearchResponse {
28
+ query: string;
29
+ items: SearchResultItem[];
30
+ }
31
+
32
+ /**
33
+ * Sites that do not allow anonymous scraping (by robots policy or aggressive
34
+ * bot-detection) and are excluded from search results by default.
35
+ *
36
+ * Consumers can override this by passing their own `blacklist` option when
37
+ * creating a search client, or extend it by spreading
38
+ * `DEFAULT_SEARCH_BLACKLIST` into a custom list.
39
+ */
40
+ declare const DEFAULT_SEARCH_BLACKLIST: readonly string[];
41
+
42
+ interface SearchClientConfig {
43
+ /** Google API key with Custom Search API enabled. */
44
+ apiKey: string;
45
+ /** Google Custom Search Engine ID (the `cx` parameter). */
46
+ engineId: string;
47
+ /**
48
+ * Domains to exclude from results (added as `-site:` operators to the
49
+ * query). Defaults to {@link DEFAULT_SEARCH_BLACKLIST}.
50
+ */
51
+ blacklist?: readonly string[];
52
+ }
53
+ interface SearchClient {
54
+ /**
55
+ * Execute a search and return the top results, filtered by the client's
56
+ * blacklist. Throws {@link NoSearchResultsError} if the response is empty
57
+ * and {@link SearchError} on network / API failures.
58
+ */
59
+ getUrlsFromQuery(req: SearchRequest): Promise<SearchResponse>;
60
+ }
61
+ /**
62
+ * Create a Google Custom Search client.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const search = createSearchClient({
67
+ * apiKey: process.env.GOOGLE_CUSTOM_SEARCH_KEY!,
68
+ * engineId: process.env.GOOGLE_CUSTOM_SEARCH_ENGINE!,
69
+ * })
70
+ * const { items } = await search.getUrlsFromQuery({ query: "best ramen in tokyo" })
71
+ * ```
72
+ */
73
+ declare function createSearchClient(config: SearchClientConfig): SearchClient;
74
+
75
+ export { DEFAULT_SEARCH_BLACKLIST, NoSearchResultsError, type SearchClient, type SearchClientConfig, SearchError, type SearchRequest, type SearchResponse, type SearchResultItem, createSearchClient };
@@ -0,0 +1,71 @@
1
+ // src/types/errors.ts
2
+ var MapthisError = class extends Error {
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = "MapthisError";
6
+ }
7
+ };
8
+
9
+ // src/search/errors.ts
10
+ var SearchError = class extends MapthisError {
11
+ constructor(message, options) {
12
+ super(message, options);
13
+ this.name = "SearchError";
14
+ }
15
+ };
16
+ var NoSearchResultsError = class extends SearchError {
17
+ constructor(message, options) {
18
+ super(message ?? "No search results found", options);
19
+ this.name = "NoSearchResultsError";
20
+ }
21
+ };
22
+
23
+ // src/search/blacklist.ts
24
+ var DEFAULT_SEARCH_BLACKLIST = [
25
+ "reddit.com",
26
+ "quora.com",
27
+ "tripadvisor.com",
28
+ "youtube.com"
29
+ ];
30
+
31
+ // src/search/client.ts
32
+ function createSearchClient(config) {
33
+ const blacklist = config.blacklist ?? DEFAULT_SEARCH_BLACKLIST;
34
+ const exclusions = blacklist.map((site) => `-site:${site}`).join(" ");
35
+ return {
36
+ async getUrlsFromQuery(req) {
37
+ const { query } = req;
38
+ const q = encodeURIComponent(`${query} ${exclusions}`.trim());
39
+ const url = `https://www.googleapis.com/customsearch/v1?key=${config.apiKey}&cx=${config.engineId}&q=${q}`;
40
+ let response;
41
+ try {
42
+ response = await fetch(url);
43
+ } catch (error) {
44
+ throw new SearchError(
45
+ `Search request failed: ${error instanceof Error ? error.message : "unknown"}`,
46
+ { cause: error instanceof Error ? error : void 0 }
47
+ );
48
+ }
49
+ if (!response.ok) {
50
+ throw new SearchError(
51
+ `Search API returned ${response.status} ${response.statusText}`
52
+ );
53
+ }
54
+ const search = await response.json();
55
+ if (!search?.items?.length) throw new NoSearchResultsError();
56
+ return {
57
+ query,
58
+ items: search.items.map((item) => ({
59
+ url: item.link,
60
+ title: item.title,
61
+ snippet: item.snippet,
62
+ image: item.pagemap?.cse_image?.[0]?.src
63
+ }))
64
+ };
65
+ }
66
+ };
67
+ }
68
+
69
+ export { DEFAULT_SEARCH_BLACKLIST, NoSearchResultsError, SearchError, createSearchClient };
70
+ //# sourceMappingURL=index.js.map
71
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/errors.ts","../../src/search/errors.ts","../../src/search/blacklist.ts","../../src/search/client.ts"],"names":[],"mappings":";AAOO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EACpC,WAAA,CAAY,SAAkB,OAAA,EAAwB;AAClD,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AAAA,EAChB;AACJ,CAAA;;;ACVO,IAAM,WAAA,GAAN,cAA0B,YAAA,CAAa;AAAA,EAC1C,WAAA,CAAY,SAAkB,OAAA,EAAwB;AAClD,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AAAA,EAChB;AACJ;AAKO,IAAM,oBAAA,GAAN,cAAmC,WAAA,CAAY;AAAA,EAClD,WAAA,CAAY,SAAkB,OAAA,EAAwB;AAClD,IAAA,KAAA,CAAM,OAAA,IAAW,2BAA2B,OAAO,CAAA;AACnD,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAAA,EAChB;AACJ;;;ACTO,IAAM,wBAAA,GAA8C;AAAA,EACvD,YAAA;AAAA,EACA,WAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACJ;;;ACyBO,SAAS,mBAAmB,MAAA,EAA0C;AACzE,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,wBAAA;AACtC,EAAA,MAAM,UAAA,GAAa,SAAA,CAAU,GAAA,CAAI,CAAC,IAAA,KAAS,SAAS,IAAI,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAEpE,EAAA,OAAO;AAAA,IACH,MAAM,iBAAiB,GAAA,EAA6C;AAChE,MAAA,MAAM,EAAE,OAAM,GAAI,GAAA;AAClB,MAAA,MAAM,CAAA,GAAI,mBAAmB,CAAA,EAAG,KAAK,IAAI,UAAU,CAAA,CAAA,CAAG,MAAM,CAAA;AAC5D,MAAA,MAAM,GAAA,GAAM,kDAAkD,MAAA,CAAO,MAAM,OAAO,MAAA,CAAO,QAAQ,MAAM,CAAC,CAAA,CAAA;AAExG,MAAA,IAAI,QAAA;AACJ,MAAA,IAAI;AACA,QAAA,QAAA,GAAW,MAAM,MAAM,GAAG,CAAA;AAAA,MAC9B,SAAS,KAAA,EAAO;AACZ,QAAA,MAAM,IAAI,WAAA;AAAA,UACN,CAAA,uBAAA,EAA0B,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,UAAU,SAAS,CAAA,CAAA;AAAA,UAC5E,EAAE,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,QAAQ,MAAA;AAAU,SACxD;AAAA,MACJ;AAEA,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,IAAI,WAAA;AAAA,UACN,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,UAAU,CAAA;AAAA,SACjE;AAAA,MACJ;AAEA,MAAA,MAAM,MAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AACpC,MAAA,IAAI,CAAC,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,MAAM,IAAI,oBAAA,EAAqB;AAE3D,MAAA,OAAO;AAAA,QACH,KAAA;AAAA,QACA,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,MAAU;AAAA,UAC/B,KAAK,IAAA,CAAK,IAAA;AAAA,UACV,OAAO,IAAA,CAAK,KAAA;AAAA,UACZ,SAAS,IAAA,CAAK,OAAA;AAAA,UACd,KAAA,EAAO,IAAA,CAAK,OAAA,EAAS,SAAA,GAAY,CAAC,CAAA,EAAG;AAAA,SACzC,CAAE;AAAA,OACN;AAAA,IACJ;AAAA,GACJ;AACJ","file":"index.js","sourcesContent":["/**\n * Base error class for every error thrown by the mapthis package.\n *\n * All feature-specific error classes (scrape, search, ai, geocoding) extend\n * this, so consumers can do `catch (e) { if (e instanceof MapthisError) ... }`\n * to distinguish library errors from other exceptions.\n */\nexport class MapthisError extends Error {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"MapthisError\"\n }\n}\n","import { MapthisError } from \"../types/errors\"\n\nexport class SearchError extends MapthisError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = \"SearchError\"\n }\n}\n\n/**\n * Thrown when a search query returned no items.\n */\nexport class NoSearchResultsError extends SearchError {\n constructor(message?: string, options?: ErrorOptions) {\n super(message ?? \"No search results found\", options)\n this.name = \"NoSearchResultsError\"\n }\n}\n","/**\n * Sites that do not allow anonymous scraping (by robots policy or aggressive\n * bot-detection) and are excluded from search results by default.\n *\n * Consumers can override this by passing their own `blacklist` option when\n * creating a search client, or extend it by spreading\n * `DEFAULT_SEARCH_BLACKLIST` into a custom list.\n */\nexport const DEFAULT_SEARCH_BLACKLIST: readonly string[] = [\n \"reddit.com\",\n \"quora.com\",\n \"tripadvisor.com\",\n \"youtube.com\",\n]\n","import { DEFAULT_SEARCH_BLACKLIST } from \"./blacklist\"\nimport type { CustomSearchResponse } from \"./cse-types\"\nimport { NoSearchResultsError, SearchError } from \"./errors\"\nimport type { SearchRequest, SearchResponse } from \"./types\"\n\nexport interface SearchClientConfig {\n /** Google API key with Custom Search API enabled. */\n apiKey: string\n /** Google Custom Search Engine ID (the `cx` parameter). */\n engineId: string\n /**\n * Domains to exclude from results (added as `-site:` operators to the\n * query). Defaults to {@link DEFAULT_SEARCH_BLACKLIST}.\n */\n blacklist?: readonly string[]\n}\n\nexport interface SearchClient {\n /**\n * Execute a search and return the top results, filtered by the client's\n * blacklist. Throws {@link NoSearchResultsError} if the response is empty\n * and {@link SearchError} on network / API failures.\n */\n getUrlsFromQuery(req: SearchRequest): Promise<SearchResponse>\n}\n\n/**\n * Create a Google Custom Search client.\n *\n * @example\n * ```ts\n * const search = createSearchClient({\n * apiKey: process.env.GOOGLE_CUSTOM_SEARCH_KEY!,\n * engineId: process.env.GOOGLE_CUSTOM_SEARCH_ENGINE!,\n * })\n * const { items } = await search.getUrlsFromQuery({ query: \"best ramen in tokyo\" })\n * ```\n */\nexport function createSearchClient(config: SearchClientConfig): SearchClient {\n const blacklist = config.blacklist ?? DEFAULT_SEARCH_BLACKLIST\n const exclusions = blacklist.map((site) => `-site:${site}`).join(\" \")\n\n return {\n async getUrlsFromQuery(req: SearchRequest): Promise<SearchResponse> {\n const { query } = req\n const q = encodeURIComponent(`${query} ${exclusions}`.trim())\n const url = `https://www.googleapis.com/customsearch/v1?key=${config.apiKey}&cx=${config.engineId}&q=${q}`\n\n let response: Response\n try {\n response = await fetch(url)\n } catch (error) {\n throw new SearchError(\n `Search request failed: ${error instanceof Error ? error.message : \"unknown\"}`,\n { cause: error instanceof Error ? error : undefined },\n )\n }\n\n if (!response.ok) {\n throw new SearchError(\n `Search API returned ${response.status} ${response.statusText}`,\n )\n }\n\n const search = (await response.json()) as CustomSearchResponse\n if (!search?.items?.length) throw new NoSearchResultsError()\n\n return {\n query,\n items: search.items.map((item) => ({\n url: item.link,\n title: item.title,\n snippet: item.snippet,\n image: item.pagemap?.cse_image?.[0]?.src,\n })),\n }\n },\n }\n}\n"]}