nosible 0.1.5

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 (44) hide show
  1. package/README.md +438 -0
  2. package/dist/index.cjs +1841 -0
  3. package/dist/index.cjs.map +29 -0
  4. package/dist/index.js +1815 -0
  5. package/dist/index.js.map +29 -0
  6. package/package.json +63 -0
  7. package/src/api/api.test.ts +366 -0
  8. package/src/api/index.ts +179 -0
  9. package/src/api/schemas.ts +152 -0
  10. package/src/client.test.ts +685 -0
  11. package/src/client.ts +762 -0
  12. package/src/index.ts +4 -0
  13. package/src/scrape/types.ts +119 -0
  14. package/src/scrape/webPageData.test.ts +302 -0
  15. package/src/scrape/webPageData.ts +103 -0
  16. package/src/search/analyze.test.ts +396 -0
  17. package/src/search/analyze.ts +151 -0
  18. package/src/search/bulkSearch.ts +62 -0
  19. package/src/search/result.test.ts +423 -0
  20. package/src/search/result.ts +391 -0
  21. package/src/search/result.types.ts +32 -0
  22. package/src/search/resultFactory.ts +21 -0
  23. package/src/search/resultSet.io.test.ts +320 -0
  24. package/src/search/resultSet.test.ts +368 -0
  25. package/src/search/resultSet.ts +387 -0
  26. package/src/search/resultSet.types.ts +3 -0
  27. package/src/search/search.test.ts +299 -0
  28. package/src/search/search.ts +187 -0
  29. package/src/search/searchSet.io.test.ts +321 -0
  30. package/src/search/searchSet.ts +122 -0
  31. package/src/search/sqlFilter.test.ts +129 -0
  32. package/src/search/sqlFilter.ts +147 -0
  33. package/src/test-utils/mocks.ts +159 -0
  34. package/src/topicTrend/topicTrend.ts +53 -0
  35. package/src/utils/browser.test.ts +209 -0
  36. package/src/utils/browser.ts +21 -0
  37. package/src/utils/fernet.ts +47 -0
  38. package/src/utils/file.test.ts +81 -0
  39. package/src/utils/file.ts +195 -0
  40. package/src/utils/index.ts +7 -0
  41. package/src/utils/llm.test.ts +279 -0
  42. package/src/utils/llm.ts +244 -0
  43. package/src/utils/userPlan.test.ts +332 -0
  44. package/src/utils/userPlan.ts +211 -0
@@ -0,0 +1,187 @@
1
+ import {generateSqlFilter, sqlFilterSchema} from "./sqlFilter";
2
+ import {z} from "zod";
3
+ import {
4
+ algorithmEnum,
5
+ brandSafetyEnum,
6
+ continentEnum,
7
+ regionEnum,
8
+ type AlgorithmEnum,
9
+ type BrandSafetyEnum,
10
+ type ContinentEnum,
11
+ type RegionEnum,
12
+ } from "../api/schemas";
13
+ import type {NosibleClient} from "../client";
14
+ import {generateExpansions} from "../utils/llm";
15
+ import {aiSearchParamsSchema, type StdSearchParamsType} from "../api/index";
16
+
17
+ /**
18
+ * User Search Params
19
+ * A user can search using additional filters, these are passed as a SQL filter string.
20
+ * */
21
+ // User AI Search Params
22
+ export const userAiSearchParamsSchema = aiSearchParamsSchema;
23
+ export type UserAiSearchParamsType = z.infer<typeof userAiSearchParamsSchema>;
24
+
25
+ // User Fast Search Params
26
+ export const userSearchParamsSchema = sqlFilterSchema.extend({
27
+ // Standard search params for fast and bulk search only
28
+ question: z.string().optional(),
29
+ autoGenerateExpansions: z.boolean().optional(),
30
+ expansions: z.array(z.string()).optional(),
31
+ sqlFilter: z.string().optional(),
32
+ algorithm: algorithmEnum.optional(),
33
+ nResults: z.number().optional(),
34
+ nProbes: z.number().min(1).max(10).optional(),
35
+ nContextify: z.number().min(64).max(1024).optional(),
36
+ minSimilarity: z.number().min(0).max(1).optional(),
37
+ mustInclude: z.array(z.string()).max(100).optional(),
38
+ mustExclude: z.array(z.string()).max(100).optional(),
39
+ brandSafety: brandSafetyEnum.optional(),
40
+ language: z.string().min(2).max(2).optional(),
41
+ continent: continentEnum.optional(),
42
+ region: regionEnum.optional(),
43
+ country: z.string().optional(),
44
+ sector: z.string().optional(),
45
+ industryGroup: z.string().optional(),
46
+ industry: z.string().optional(),
47
+ subIndustry: z.string().optional(),
48
+ iabTier1: z.string().optional(),
49
+ iabTier2: z.string().optional(),
50
+ iabTier3: z.string().optional(),
51
+ iabTier4: z.string().optional(),
52
+ companies: z.array(z.string()).max(30).optional(),
53
+ });
54
+ export type UserSearchParamsType = z.infer<typeof userSearchParamsSchema>;
55
+
56
+ export const clientDefaultSearchParamsSchema = userSearchParamsSchema.omit({
57
+ question: true,
58
+ });
59
+
60
+ export type ClientDefaultSearchParamsType = z.infer<
61
+ typeof clientDefaultSearchParamsSchema
62
+ >;
63
+
64
+ export type ClientSearchParamsSchema =
65
+ | (UserSearchParamsType & {search?: never})
66
+ | {search: Search};
67
+
68
+ // Similar Search User Params
69
+ const similarUserSearchParams = userSearchParamsSchema.omit({
70
+ question: true,
71
+ });
72
+
73
+ export type SimilarUserSearchParamsType = z.infer<
74
+ typeof similarUserSearchParams
75
+ >;
76
+
77
+ export class Search {
78
+ question!: string;
79
+ nResults!: number;
80
+ expansions?: string[];
81
+ autoGenerateExpansions?: boolean;
82
+ nProbes?: number;
83
+ nContextify?: number;
84
+ algorithm?: AlgorithmEnum;
85
+ sqlFilter?: string;
86
+ minSimilarity?: number;
87
+ mustInclude?: string[];
88
+ mustExclude?: string[];
89
+ brandSafety?: BrandSafetyEnum;
90
+ language?: string;
91
+ continent?: ContinentEnum;
92
+ region?: RegionEnum;
93
+ country?: string;
94
+ sector?: string;
95
+ industryGroup?: string;
96
+ industry?: string;
97
+ subIndustry?: string;
98
+ iabTier1?: string;
99
+ iabTier2?: string;
100
+ iabTier3?: string;
101
+ iabTier4?: string;
102
+ companies?: string[];
103
+ // SQL Filter
104
+ publishStart?: Date;
105
+ publishEnd?: Date;
106
+ visitedStart?: Date;
107
+ visitedEnd?: Date;
108
+ certain?: boolean;
109
+ includeNetlocs?: string[];
110
+ excludeNetlocs?: string[];
111
+ includeCompanies?: string[];
112
+ excludeCompanies?: string[];
113
+ includeDocs?: string[];
114
+ excludeDocs?: string[];
115
+
116
+ constructor(params: UserSearchParamsType) {
117
+ Object.assign(this, params);
118
+ }
119
+
120
+ /* Returns the search body for the API call
121
+
122
+ Generates the expansions and sql filter from the search params if required
123
+ */
124
+ public searchBody = async ({
125
+ client,
126
+ }: {
127
+ client: NosibleClient;
128
+ }): Promise<StdSearchParamsType> => {
129
+ let expansions: string[] | undefined = this.expansions;
130
+
131
+ if (this.autoGenerateExpansions) {
132
+ expansions = await generateExpansions({
133
+ question: this.question,
134
+ model: client.expansionsModel,
135
+ llmClient: client.llmClient,
136
+ });
137
+ }
138
+
139
+ let sqlFilter = generateSqlFilter({
140
+ publishStart: this.publishStart || undefined,
141
+ publishEnd: this.publishEnd || undefined,
142
+ visitedStart: this.visitedStart || undefined,
143
+ visitedEnd: this.visitedEnd || undefined,
144
+ certain: this.certain || undefined,
145
+ includeNetlocs: this.includeNetlocs || undefined,
146
+ excludeNetlocs: this.excludeNetlocs || undefined,
147
+ includeCompanies: this.includeCompanies || undefined,
148
+ excludeCompanies: this.excludeCompanies || undefined,
149
+ includeDocs: this.includeDocs || undefined,
150
+ excludeDocs: this.excludeDocs || undefined,
151
+ });
152
+
153
+ const output: StdSearchParamsType = {
154
+ question: this.question,
155
+ expansions: expansions,
156
+ sql_filter: sqlFilter || undefined,
157
+ algorithm: this.algorithm,
158
+ n_probes: this.nProbes,
159
+ n_results: this.nResults,
160
+ n_contextify: this.nContextify,
161
+ min_similarity: this.minSimilarity,
162
+ must_include: this.mustInclude,
163
+ must_exclude: this.mustExclude,
164
+ brand_safety: this.brandSafety,
165
+ language: this.language,
166
+ continent: this.continent,
167
+ region: this.region,
168
+ country: this.country,
169
+ sector: this.sector,
170
+ industry_group: this.industryGroup,
171
+ industry: this.industry,
172
+ sub_industry: this.subIndustry,
173
+ iab_tier_1: this.iabTier1,
174
+ iab_tier_2: this.iabTier2,
175
+ iab_tier_3: this.iabTier3,
176
+ iab_tier_4: this.iabTier4,
177
+ companies: this.companies,
178
+ };
179
+
180
+ // remove undefined values
181
+ const outputWithoutUndefined = Object.fromEntries(
182
+ Object.entries(output).filter(([, value]) => value !== undefined)
183
+ ) as StdSearchParamsType;
184
+
185
+ return outputWithoutUndefined;
186
+ };
187
+ }
@@ -0,0 +1,321 @@
1
+ import {describe, it, expect, beforeAll} from "bun:test";
2
+ import {SearchSet} from "./searchSet";
3
+ import {Search} from "./search";
4
+ import {importCsv, importJson} from "../utils/file";
5
+ import {randomUUID} from "node:crypto";
6
+
7
+ /**
8
+ * Test configuration
9
+ */
10
+ const TEST_DIR = `${process.cwd()}/tmp/searchSet/`; // Directory where test files will be created
11
+ const CLEANUP = false; // Set to false to keep test files for inspection
12
+
13
+ /**
14
+ * Helper function to conditionally cleanup test files
15
+ */
16
+ const cleanupFile = async (filePath: string) => {
17
+ if (CLEANUP) {
18
+ const fs = await import("node:fs/promises");
19
+ await fs.unlink(filePath).catch(() => {});
20
+ } else {
21
+ console.log(`Keeping test file: ${filePath}`);
22
+ }
23
+ };
24
+
25
+ /**
26
+ * I/O tests for SearchSet with file system operations
27
+ * These tests require file system access for reading/writing
28
+ */
29
+ describe("SearchSet - I/O Tests", () => {
30
+ let testSearchSet: SearchSet;
31
+
32
+ beforeAll(async () => {
33
+ try {
34
+ // Create test directory if it doesn't exist
35
+ const fs = await import("node:fs/promises");
36
+ await fs.mkdir(TEST_DIR, {recursive: true}).catch(() => {});
37
+
38
+ // Create a test SearchSet with sample searches
39
+ const searches = [
40
+ new Search({
41
+ question: "artificial intelligence",
42
+ nResults: 10,
43
+ }),
44
+ new Search({
45
+ question: "machine learning",
46
+ nResults: 20,
47
+ algorithm: "hybrid-1",
48
+ }),
49
+ new Search({
50
+ question: "deep learning",
51
+ nResults: 15,
52
+ language: "en",
53
+ brandSafety: "safe",
54
+ }),
55
+ ];
56
+
57
+ testSearchSet = new SearchSet(searches);
58
+ } catch (error) {
59
+ console.warn("Skipping I/O tests due to setup error");
60
+ throw error;
61
+ }
62
+ });
63
+
64
+ describe("File export methods", () => {
65
+ it("should export SearchSet to JSON file", async () => {
66
+ const uniqueId = randomUUID();
67
+ const fileName = `${TEST_DIR}/test-searches-${uniqueId}.json`;
68
+ await testSearchSet.writeToJson(fileName);
69
+
70
+ console.log(
71
+ `Successfully exported ${testSearchSet.searches.length} searches to JSON`
72
+ );
73
+
74
+ // Verify the file was created
75
+ const fs = await import("node:fs/promises");
76
+ await new Promise((resolve) => setTimeout(resolve, 100));
77
+
78
+ const fileExists = await fs
79
+ .access(fileName)
80
+ .then(() => true)
81
+ .catch(() => false);
82
+ expect(fileExists).toBe(true);
83
+
84
+ // Clean up
85
+ await cleanupFile(fileName);
86
+ });
87
+
88
+ it("should export SearchSet to NDJSON file", async () => {
89
+ const uniqueId = randomUUID();
90
+ const fileName = `${TEST_DIR}/test-searches-${uniqueId}.ndjson`;
91
+ await testSearchSet.writeToNdjson(fileName);
92
+
93
+ console.log(
94
+ `Successfully exported ${testSearchSet.searches.length} searches to NDJSON`
95
+ );
96
+
97
+ // Verify the file was created
98
+ const fs = await import("node:fs/promises");
99
+ await new Promise((resolve) => setTimeout(resolve, 100));
100
+
101
+ const fileExists = await fs
102
+ .access(fileName)
103
+ .then(() => true)
104
+ .catch(() => false);
105
+ expect(fileExists).toBe(true);
106
+
107
+ // Clean up
108
+ await cleanupFile(fileName);
109
+ });
110
+
111
+ it("should export SearchSet to CSV file", async () => {
112
+ const uniqueId = randomUUID();
113
+ const fileName = `${TEST_DIR}/test-searches-${uniqueId}.csv`;
114
+ await testSearchSet.writeToCsv(fileName);
115
+
116
+ console.log(
117
+ `Successfully exported ${testSearchSet.searches.length} searches to CSV`
118
+ );
119
+
120
+ // Verify the file was created
121
+ const fs = await import("node:fs/promises");
122
+ await new Promise((resolve) => setTimeout(resolve, 100));
123
+
124
+ const fileExists = await fs
125
+ .access(fileName)
126
+ .then(() => true)
127
+ .catch(() => false);
128
+ expect(fileExists).toBe(true);
129
+
130
+ // Read file and verify contents
131
+ const json = await importCsv({filePath: fileName});
132
+ expect(json).toBeDefined();
133
+ expect(json.length).toBeGreaterThan(0);
134
+ expect(json[0]?.question).toBeDefined();
135
+
136
+ // Clean up
137
+ await cleanupFile(fileName);
138
+ });
139
+ });
140
+
141
+ describe("Data conversion methods", () => {
142
+ it("should convert SearchSet to Polars DataFrame", async () => {
143
+ const df = await testSearchSet.toPolars();
144
+
145
+ expect(df).toBeDefined();
146
+ expect(df.height).toBeGreaterThan(0);
147
+ expect(df.width).toBeGreaterThan(0);
148
+
149
+ console.log(
150
+ `Successfully converted ${testSearchSet.searches.length} searches to Polars DataFrame`
151
+ );
152
+ console.log(`DataFrame shape: ${df.height} rows x ${df.width} columns`);
153
+ });
154
+ });
155
+
156
+ describe("Round-trip export/import - JSON", () => {
157
+ it("should verify exported JSON can be reimported", async () => {
158
+ const uniqueId = randomUUID();
159
+ const fileName = `${TEST_DIR}/test-roundtrip-${uniqueId}.json`;
160
+ await testSearchSet.writeToJson(fileName);
161
+
162
+ // Import the JSON file back
163
+ const reimportedSearchSet = await SearchSet.fromFilePath(fileName);
164
+
165
+ expect(reimportedSearchSet).toBeDefined();
166
+ expect(reimportedSearchSet.searches.length).toBe(
167
+ testSearchSet.searches.length
168
+ );
169
+
170
+ console.log(
171
+ `Successfully round-tripped ${reimportedSearchSet.searches.length} searches through JSON`
172
+ );
173
+
174
+ // Clean up
175
+ await cleanupFile(fileName);
176
+ });
177
+
178
+ it("should produce identical JSON files after save -> load -> save cycle", async () => {
179
+ const fs = await import("node:fs/promises");
180
+ const uniqueId = randomUUID();
181
+ const file1 = `${TEST_DIR}/test-roundtrip-1-${uniqueId}.json`;
182
+ const file2 = `${TEST_DIR}/test-roundtrip-2-${uniqueId}.json`;
183
+
184
+ try {
185
+ // Save original searches
186
+ await testSearchSet.writeToJson(file1);
187
+
188
+ // Load and verify
189
+ const reloadedSearchSet = await SearchSet.fromFilePath(file1);
190
+ expect(reloadedSearchSet.searches.length).toBe(
191
+ testSearchSet.searches.length
192
+ );
193
+
194
+ // Save again
195
+ await reloadedSearchSet.writeToJson(file2);
196
+
197
+ // Compare the two JSON files
198
+ const json1Content = await fs.readFile(file1, "utf-8");
199
+ const json2Content = await fs.readFile(file2, "utf-8");
200
+
201
+ const json1 = JSON.parse(json1Content);
202
+ const json2 = JSON.parse(json2Content);
203
+
204
+ // Verify they have the same structure and content
205
+ expect(json1.length).toBe(json2.length);
206
+ expect(json1).toEqual(json2);
207
+
208
+ console.log(
209
+ `✓ Both JSON files are identical (${json1.length} searches)`
210
+ );
211
+ } finally {
212
+ // Clean up both files
213
+ await cleanupFile(file1);
214
+ await cleanupFile(file2);
215
+ }
216
+ });
217
+ });
218
+
219
+ describe("Round-trip export/import - CSV", () => {
220
+ it("should verify exported CSV can be reimported", async () => {
221
+ const uniqueId = randomUUID();
222
+ const fileName = `${TEST_DIR}/test-roundtrip-${uniqueId}.csv`;
223
+ await testSearchSet.writeToCsv(fileName);
224
+
225
+ // Import the CSV file back
226
+ const reimportedSearchSet = await SearchSet.fromFilePath(fileName);
227
+
228
+ expect(reimportedSearchSet).toBeDefined();
229
+ expect(reimportedSearchSet.searches.length).toBe(
230
+ testSearchSet.searches.length
231
+ );
232
+
233
+ console.log(
234
+ `Successfully round-tripped ${reimportedSearchSet.searches.length} searches through CSV`
235
+ );
236
+
237
+ // Clean up
238
+ await cleanupFile(fileName);
239
+ });
240
+
241
+ it("should produce identical CSV files after save -> load -> save cycle", async () => {
242
+ const fs = await import("node:fs/promises");
243
+ const uniqueId = randomUUID();
244
+ const file1 = `${TEST_DIR}/test-roundtrip-1-${uniqueId}.csv`;
245
+ const file2 = `${TEST_DIR}/test-roundtrip-2-${uniqueId}.csv`;
246
+
247
+ try {
248
+ // Save original searches
249
+ await testSearchSet.writeToCsv(file1);
250
+
251
+ // Load and verify
252
+ const reloadedSearchSet = await SearchSet.fromFilePath(file1);
253
+ expect(reloadedSearchSet.searches.length).toBe(
254
+ testSearchSet.searches.length
255
+ );
256
+
257
+ // Save again
258
+ await reloadedSearchSet.writeToCsv(file2);
259
+
260
+ // Compare the two CSV files
261
+ const csv1Content = await fs.readFile(file1, "utf-8");
262
+ const csv2Content = await fs.readFile(file2, "utf-8");
263
+
264
+ // Verify they have the same content
265
+ expect(csv1Content).toBe(csv2Content);
266
+
267
+ console.log(`✓ Both CSV files are identical`);
268
+ } finally {
269
+ // Clean up both files
270
+ await cleanupFile(file1);
271
+ await cleanupFile(file2);
272
+ }
273
+ });
274
+ });
275
+
276
+ describe("SearchSet with various search parameters", () => {
277
+ it("should handle searches with complex filter parameters", async () => {
278
+ const complexSearches = [
279
+ new Search({
280
+ question: "climate change",
281
+ nResults: 50,
282
+ algorithm: "hybrid-1",
283
+ brandSafety: "safe",
284
+ language: "en",
285
+ country: "US",
286
+ sector: "Technology",
287
+ mustInclude: ["renewable", "energy"],
288
+ mustExclude: ["fossil"],
289
+ }),
290
+ new Search({
291
+ question: "renewable energy",
292
+ nResults: 30,
293
+ expansions: ["solar power", "wind energy", "hydroelectric"],
294
+ minSimilarity: 0.7,
295
+ }),
296
+ ];
297
+
298
+ const complexSearchSet = new SearchSet(complexSearches);
299
+ const uniqueId = randomUUID();
300
+ const fileName = `${TEST_DIR}/complex-searches-${uniqueId}.json`;
301
+
302
+ await complexSearchSet.writeToJson(fileName);
303
+
304
+ // Verify round-trip
305
+ const reimported = await SearchSet.fromFilePath(fileName);
306
+ expect(reimported.searches.length).toBe(complexSearches.length);
307
+ expect(reimported.searches[0]?.question).toBe("climate change");
308
+ expect(reimported.searches[0]?.mustInclude).toEqual([
309
+ "renewable",
310
+ "energy",
311
+ ]);
312
+
313
+ console.log(
314
+ `✓ Successfully handled complex search parameters (${reimported.searches.length} searches)`
315
+ );
316
+
317
+ // Clean up
318
+ await cleanupFile(fileName);
319
+ });
320
+ });
321
+ });
@@ -0,0 +1,122 @@
1
+ import {
2
+ exportJson,
3
+ exportCsv,
4
+ exportNdjson,
5
+ importCsv,
6
+ importJson,
7
+ } from "../utils/file";
8
+ import {Search} from "./search";
9
+ import {z} from "zod";
10
+ import {isBrowser} from "../utils";
11
+
12
+ /**
13
+ * A set of searches.
14
+ */
15
+
16
+ const searchSetParams = z.union([
17
+ z.object({
18
+ searches: z.array(z.instanceof(Search)),
19
+ }),
20
+ z.array(z.instanceof(Search)),
21
+ ]);
22
+
23
+ export type SearchSetParams = z.infer<typeof searchSetParams>;
24
+
25
+ export class SearchSet {
26
+ public searches: Search[] = [];
27
+ public index: number;
28
+
29
+ constructor(params: SearchSetParams) {
30
+ // Handle both object and array forms
31
+ if (Array.isArray(params)) {
32
+ this.searches = params;
33
+ } else {
34
+ this.searches = params.searches;
35
+ }
36
+
37
+ this.index = 0;
38
+ }
39
+
40
+ /**
41
+ * Creates a SearchSet from a JSON file asynchronously
42
+ * @param filePath Path to the JSON file
43
+ * @returns Promise<SearchSet>
44
+ */
45
+ static async fromFilePath(filePath: string): Promise<SearchSet> {
46
+ if (filePath.endsWith(".json")) {
47
+ const searches = await importJson({filePath: filePath});
48
+ return new SearchSet(searches.map((search: any) => new Search(search)));
49
+ } else if (filePath.endsWith(".csv")) {
50
+ const searches = await importCsv({filePath: filePath});
51
+ return new SearchSet(searches.map((search: any) => new Search(search)));
52
+ } else throw new Error("Unsupported file type");
53
+ }
54
+
55
+ /**
56
+ * Creates a SearchSet from a File asynchronously
57
+ * @param file File to read from
58
+ * @returns Promise<SearchSet>
59
+ */
60
+
61
+ static async fromFile(file: File): Promise<SearchSet> {
62
+ if (file.name.endsWith(".json")) {
63
+ const searches = await importJson({file: file});
64
+ return new SearchSet(searches.map((search: any) => new Search(search)));
65
+ } else if (file.name.endsWith(".csv")) {
66
+ const searches = await importCsv({file: file});
67
+ return new SearchSet(searches.map((search: any) => new Search(search)));
68
+ } else throw new Error("Unsupported file type");
69
+ }
70
+
71
+ /**
72
+ * Returns the next search in the set.
73
+ * @returns The next search in the set, or undefined if there are no more searches.
74
+ */
75
+ public next = (): Search | null => {
76
+ this.index++;
77
+ const search = this.searches[this.index];
78
+ if (!search) {
79
+ return null;
80
+ }
81
+ return search;
82
+ };
83
+
84
+ /**
85
+ * Adds a search to the set.
86
+ * @param search The search to add.
87
+ */
88
+ public add = (search: Search) => {
89
+ this.searches.push(search);
90
+ };
91
+
92
+ /**
93
+ * Removes a search from the set.
94
+ * @param index The index of the search to remove.
95
+ */
96
+ public remove = (index: number) => {
97
+ this.searches.splice(index, 1);
98
+ };
99
+
100
+ public toPolars = async () => {
101
+ if (isBrowser()) {
102
+ throw new Error("toPolars is not supported in the browser");
103
+ } else {
104
+ const pl = await import("nodejs-polars");
105
+ const df = pl.DataFrame(this.searches);
106
+ return df;
107
+ }
108
+ };
109
+
110
+ // Export to file or download
111
+ public writeToCsv = async (fileName: string) => {
112
+ await exportCsv(this.searches, fileName);
113
+ };
114
+
115
+ public writeToJson = async (fileName: string) => {
116
+ await exportJson(this.searches, fileName);
117
+ };
118
+
119
+ public writeToNdjson = async (fileName: string) => {
120
+ await exportNdjson(this.searches, fileName);
121
+ };
122
+ }