trendsearch 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dobroslav Radosavljevic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,551 @@
1
+ # trendsearch ๐Ÿ“ˆ
2
+
3
+ Modern Google Trends SDK for Node.js and Bun, built with native `fetch`, strict Zod validation, and a production-friendly client API.
4
+
5
+ ## โœจ Highlights
6
+
7
+ - ๐Ÿ”’ Strict schema validation by default (Zod-backed)
8
+ - ๐Ÿง  Full TypeScript-first API and exported inferred types
9
+ - โšก Native `fetch` transport (Node 20+ and Bun)
10
+ - ๐Ÿงฑ ESM-only package contract
11
+ - ๐Ÿ›ก๏ธ Built-in retry/backoff + rate limiting (`p-retry` + `p-queue`)
12
+ - ๐Ÿช Optional cookie persistence support
13
+ - ๐Ÿ–ฅ๏ธ First-class `trendsearch` CLI for every endpoint
14
+ - ๐ŸŒ Stable Google Trends API endpoints + experimental RPC/picker endpoints
15
+ - ๐Ÿงช Deterministic fixture contracts + optional live endpoint tests
16
+
17
+ ## ๐Ÿ“ฆ Install
18
+
19
+ ```bash
20
+ bun add trendsearch
21
+ # or
22
+ npm install trendsearch
23
+ ```
24
+
25
+ ## โœ… Runtime Contract
26
+
27
+ - ๐ŸŸข Node.js `>=20`
28
+ - ๐ŸŸข Bun `>=1.3.9`
29
+ - ๐ŸŸข ESM-only package
30
+ - ๐Ÿ”ด CommonJS `require("trendsearch")` is intentionally unsupported
31
+
32
+ If you are in a CJS project, use dynamic import:
33
+
34
+ ```js
35
+ const trendsearch = await import("trendsearch");
36
+ ```
37
+
38
+ ## ๐Ÿš€ Quick Start
39
+
40
+ ```ts
41
+ import { interestOverTime } from "trendsearch";
42
+
43
+ const result = await interestOverTime({
44
+ keywords: ["typescript"],
45
+ geo: "US",
46
+ time: "today 3-m",
47
+ });
48
+
49
+ console.log(result.data.timeline.length);
50
+ ```
51
+
52
+ ## ๐Ÿ–ฅ๏ธ CLI
53
+
54
+ `trendsearch` ships with a production-ready CLI that wraps all stable and
55
+ experimental endpoints.
56
+
57
+ ```bash
58
+ trendsearch autocomplete typescript --output json
59
+ trendsearch explore typescript --geo US --time "today 3-m" --output pretty
60
+ trendsearch experimental trending-now --geo US --language en --hours 24
61
+ ```
62
+
63
+ ### CLI Output Modes
64
+
65
+ - `--output pretty` (human-friendly, default in TTY)
66
+ - `--output json` (single JSON envelope, default outside TTY)
67
+ - `--output jsonl` (JSON per line)
68
+
69
+ Success envelope:
70
+
71
+ ```json
72
+ {
73
+ "ok": true,
74
+ "endpoint": "autocomplete",
75
+ "request": { "keyword": "typescript" },
76
+ "data": { "topics": [] },
77
+ "meta": {
78
+ "command": "autocomplete",
79
+ "durationMs": 120,
80
+ "timestamp": "2026-02-14T00:00:00.000Z",
81
+ "output": "json"
82
+ }
83
+ }
84
+ ```
85
+
86
+ Error envelope (`json`/`jsonl`):
87
+
88
+ ```json
89
+ {
90
+ "ok": false,
91
+ "error": {
92
+ "code": "TRANSPORT_ERROR",
93
+ "message": "Request failed",
94
+ "details": {},
95
+ "exitCode": 5
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### CLI Config / Wizard / Completion
101
+
102
+ ```bash
103
+ trendsearch config set output json
104
+ trendsearch config list
105
+ trendsearch wizard
106
+ trendsearch completion bash
107
+ ```
108
+
109
+ Config precedence:
110
+
111
+ `flags > env > persisted config > defaults`
112
+
113
+ Supported env vars include:
114
+
115
+ - `TRENDSEARCH_OUTPUT`
116
+ - `TRENDSEARCH_HL`
117
+ - `TRENDSEARCH_TZ`
118
+ - `TRENDSEARCH_BASE_URL`
119
+ - `TRENDSEARCH_TIMEOUT_MS`
120
+ - `TRENDSEARCH_MAX_RETRIES`
121
+ - `TRENDSEARCH_RETRY_BASE_DELAY_MS`
122
+ - `TRENDSEARCH_RETRY_MAX_DELAY_MS`
123
+ - `TRENDSEARCH_MAX_CONCURRENT`
124
+ - `TRENDSEARCH_MIN_DELAY_MS`
125
+ - `TRENDSEARCH_USER_AGENT`
126
+
127
+ ## ๐Ÿงญ API Surface
128
+
129
+ ### Stable Endpoints
130
+
131
+ - `autocomplete`
132
+ - `explore`
133
+ - `interestOverTime`
134
+ - `interestByRegion`
135
+ - `relatedQueries`
136
+ - `relatedTopics`
137
+ - `trendingNow`
138
+ - `trendingArticles`
139
+ - `dailyTrends` (legacy compatibility)
140
+ - `realTimeTrends` (legacy compatibility)
141
+
142
+ ### Experimental Endpoints
143
+
144
+ - `experimental.trendingNow`
145
+ - `experimental.trendingArticles`
146
+ - `experimental.geoPicker`
147
+ - `experimental.categoryPicker`
148
+
149
+ โš ๏ธ Experimental endpoints are semver-minor unstable because Google can change internal RPC payloads.
150
+
151
+ โ„น๏ธ `dailyTrends` and `realTimeTrends` are kept for compatibility and may throw `EndpointUnavailableError` if Google retires those legacy routes.
152
+
153
+ ## ๐Ÿงฐ Client Configuration
154
+
155
+ Use `createClient` when you want shared runtime defaults and transport controls:
156
+
157
+ ```ts
158
+ import { MemoryCookieStore, createClient } from "trendsearch";
159
+
160
+ const client = createClient({
161
+ timeoutMs: 15_000,
162
+ baseUrl: "https://trends.google.com",
163
+ hl: "en-US",
164
+ tz: 240,
165
+ retries: {
166
+ maxRetries: 3,
167
+ baseDelayMs: 500,
168
+ maxDelayMs: 8_000,
169
+ },
170
+ rateLimit: {
171
+ maxConcurrent: 1,
172
+ minDelayMs: 1_000,
173
+ },
174
+ userAgent:
175
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
176
+ cookieStore: new MemoryCookieStore(),
177
+ proxyHook: async ({ url, init }) => ({ url, init }),
178
+ });
179
+ ```
180
+
181
+ ### Default Client Values
182
+
183
+ - `timeoutMs`: `15000`
184
+ - `baseUrl`: `https://trends.google.com`
185
+ - `hl`: `en-US`
186
+ - `tz`: host timezone offset (`new Date().getTimezoneOffset()`)
187
+ - `retries.maxRetries`: `3`
188
+ - `retries.baseDelayMs`: `500`
189
+ - `retries.maxDelayMs`: `8000`
190
+ - `rateLimit.maxConcurrent`: `1`
191
+ - `rateLimit.minDelayMs`: `1000`
192
+
193
+ ## ๐Ÿงช Request/Response Pattern
194
+
195
+ All endpoint calls return:
196
+
197
+ ```ts
198
+ {
199
+ data: ..., // normalized typed payload
200
+ raw?: ... // included only when debugRawResponse=true
201
+ }
202
+ ```
203
+
204
+ Enable raw payload diagnostics per request:
205
+
206
+ ```ts
207
+ const result = await client.explore(
208
+ { keywords: ["typescript"], geo: "US" },
209
+ { debugRawResponse: true }
210
+ );
211
+
212
+ console.log(result.raw);
213
+ ```
214
+
215
+ ## ๐Ÿ“š Endpoint Usage
216
+
217
+ ### `autocomplete`
218
+
219
+ ```ts
220
+ import { autocomplete } from "trendsearch";
221
+
222
+ const result = await autocomplete({ keyword: "typescri" });
223
+ console.log(result.data.topics);
224
+ ```
225
+
226
+ Input:
227
+
228
+ - `keyword` (required)
229
+ - `hl`, `tz` (optional)
230
+
231
+ Output:
232
+
233
+ - `data.topics`: topic list (`mid`, `title`, `type`)
234
+
235
+ ### `explore`
236
+
237
+ ```ts
238
+ import { explore } from "trendsearch";
239
+
240
+ const result = await explore({
241
+ keywords: ["typescript", "javascript"],
242
+ geo: "US",
243
+ time: "today 12-m",
244
+ category: 0,
245
+ property: "",
246
+ });
247
+
248
+ console.log(result.data.widgets);
249
+ ```
250
+
251
+ Input:
252
+
253
+ - `keywords` (required, array)
254
+ - `geo` (string or string[])
255
+ - `time`, `category`, `property`, `hl`, `tz`
256
+
257
+ Output:
258
+
259
+ - `data.widgets`: exploration widgets
260
+ - `data.comparisonItem`: normalized comparison items used in `req`
261
+
262
+ ### `interestOverTime`
263
+
264
+ ```ts
265
+ import { interestOverTime } from "trendsearch";
266
+
267
+ const result = await interestOverTime({
268
+ keywords: ["typescript"],
269
+ geo: "US",
270
+ time: "today 3-m",
271
+ });
272
+
273
+ console.log(result.data.timeline);
274
+ ```
275
+
276
+ Output:
277
+
278
+ - `data.timeline`: timeline points (`time`, `value`, `formattedTime`, `isPartial`, ...)
279
+
280
+ ### `interestByRegion`
281
+
282
+ ```ts
283
+ import { interestByRegion } from "trendsearch";
284
+
285
+ const result = await interestByRegion({
286
+ keywords: ["typescript"],
287
+ geo: "US",
288
+ resolution: "REGION",
289
+ });
290
+
291
+ console.log(result.data.regions);
292
+ ```
293
+
294
+ Output:
295
+
296
+ - `data.regions`: geo map entries (`geoCode`, `geoName`, `value`, ...)
297
+
298
+ ### `relatedQueries`
299
+
300
+ ```ts
301
+ import { relatedQueries } from "trendsearch";
302
+
303
+ const result = await relatedQueries({
304
+ keywords: ["typescript"],
305
+ geo: "US",
306
+ });
307
+
308
+ console.log(result.data.top);
309
+ console.log(result.data.rising);
310
+ ```
311
+
312
+ Output:
313
+
314
+ - `data.top`
315
+ - `data.rising`
316
+ - `value` can be `number | string` (`"Breakout"`-style upstream values are preserved)
317
+
318
+ ### `relatedTopics`
319
+
320
+ ```ts
321
+ import { relatedTopics } from "trendsearch";
322
+
323
+ const result = await relatedTopics({
324
+ keywords: ["typescript"],
325
+ geo: "US",
326
+ });
327
+
328
+ console.log(result.data.top);
329
+ console.log(result.data.rising);
330
+ ```
331
+
332
+ Output:
333
+
334
+ - `data.top`
335
+ - `data.rising`
336
+
337
+ ### `dailyTrends`
338
+
339
+ ```ts
340
+ import { dailyTrends } from "trendsearch";
341
+
342
+ const result = await dailyTrends({
343
+ geo: "US",
344
+ category: "all",
345
+ });
346
+
347
+ console.log(result.data.days);
348
+ console.log(result.data.trends);
349
+ ```
350
+
351
+ Input:
352
+
353
+ - `geo` (required)
354
+ - `category`, `date`, `ns`, `hl`, `tz`
355
+
356
+ Output:
357
+
358
+ - `data.days`: day-grouped payload
359
+ - `data.trends`: flattened trend list
360
+
361
+ ### `realTimeTrends`
362
+
363
+ ```ts
364
+ import { realTimeTrends } from "trendsearch";
365
+
366
+ const result = await realTimeTrends({
367
+ geo: "US",
368
+ category: "all",
369
+ });
370
+
371
+ console.log(result.data.stories);
372
+ ```
373
+
374
+ Input:
375
+
376
+ - `geo` (required)
377
+ - `category`, `fi`, `fs`, `ri`, `rs`, `sort`, `hl`, `tz`
378
+
379
+ Output:
380
+
381
+ - `data.stories`: story summaries
382
+
383
+ ### `trendingNow`
384
+
385
+ ```ts
386
+ import { trendingNow } from "trendsearch";
387
+
388
+ const result = await trendingNow({
389
+ geo: "US",
390
+ language: "en",
391
+ hours: 24,
392
+ });
393
+
394
+ console.log(result.data.items[0]?.articleKeys);
395
+ ```
396
+
397
+ ### `trendingArticles`
398
+
399
+ ```ts
400
+ import { trendingArticles } from "trendsearch";
401
+
402
+ const result = await trendingArticles({
403
+ articleKeys: [[1, "en", "US"]],
404
+ articleCount: 5,
405
+ });
406
+
407
+ console.log(result.data.articles);
408
+ ```
409
+
410
+ ## ๐Ÿงช Experimental Endpoint Usage
411
+
412
+ `experimental.trendingNow` and `experimental.trendingArticles` are aliases of the stable root methods and remain available for backward compatibility.
413
+
414
+ ### `experimental.geoPicker`
415
+
416
+ ```ts
417
+ import { experimental } from "trendsearch";
418
+
419
+ const result = await experimental.geoPicker({ hl: "en-US" });
420
+ console.log(result.data.items);
421
+ ```
422
+
423
+ ### `experimental.categoryPicker`
424
+
425
+ ```ts
426
+ import { experimental } from "trendsearch";
427
+
428
+ const result = await experimental.categoryPicker({ hl: "en-US" });
429
+ console.log(result.data.items);
430
+ ```
431
+
432
+ ## ๐Ÿงพ Schemas and Types
433
+
434
+ All request/response schemas and inferred types are exported:
435
+
436
+ ```ts
437
+ import {
438
+ schemas,
439
+ type InterestOverTimeRequest,
440
+ type InterestOverTimeResponse,
441
+ } from "trendsearch";
442
+
443
+ const req: InterestOverTimeRequest = {
444
+ keywords: ["typescript"],
445
+ };
446
+
447
+ const parsed = schemas.interestOverTimeResponseSchema.parse(rawPayload);
448
+ ```
449
+
450
+ `schemas` includes stable + experimental schema exports, plus `z`.
451
+
452
+ ## ๐Ÿšจ Errors
453
+
454
+ Typed errors:
455
+
456
+ - `TrendSearchError`
457
+ - `TransportError`
458
+ - `RateLimitError`
459
+ - `SchemaValidationError`
460
+ - `EndpointUnavailableError`
461
+ - `UnexpectedResponseError`
462
+
463
+ Example:
464
+
465
+ ```ts
466
+ import {
467
+ EndpointUnavailableError,
468
+ RateLimitError,
469
+ SchemaValidationError,
470
+ } from "trendsearch";
471
+
472
+ try {
473
+ await interestOverTime({ keywords: ["typescript"] });
474
+ } catch (error) {
475
+ if (error instanceof RateLimitError) {
476
+ console.error("Rate limited:", error.status);
477
+ }
478
+
479
+ if (error instanceof SchemaValidationError) {
480
+ console.error("Schema drift:", error.issues);
481
+ }
482
+
483
+ if (error instanceof EndpointUnavailableError) {
484
+ console.error("Legacy endpoint unavailable:", error.replacements);
485
+ }
486
+ }
487
+ ```
488
+
489
+ ## ๐Ÿงช Testing and Quality Gates
490
+
491
+ Run tests:
492
+
493
+ ```bash
494
+ bun run test:unit
495
+ bun run test:contracts
496
+ bun run test:all
497
+ TRENDSEARCH_LIVE=1 bun run test:live
498
+ ```
499
+
500
+ Package checks:
501
+
502
+ ```bash
503
+ bun run build
504
+ bun run check:package
505
+ bun run check:pack
506
+ bun run test:consumer
507
+ ```
508
+
509
+ Full local gate:
510
+
511
+ ```bash
512
+ bun run check:all
513
+ ```
514
+
515
+ ## ๐Ÿ“ผ Fixture Workflow
516
+
517
+ Record/update fixtures from live endpoints:
518
+
519
+ ```bash
520
+ bun run fixtures:record
521
+ ```
522
+
523
+ Fixtures are stored under:
524
+
525
+ - `tests/fixtures/raw/*`
526
+
527
+ Contract tests use those fixtures for deterministic CI.
528
+
529
+ ## ๐Ÿค– CI Notes
530
+
531
+ - `CI` workflow runs deterministic tests and package quality checks.
532
+ - `Live Endpoints` workflow (`.github/workflows/live-endpoints.yml`) runs nightly + manual and executes `test:live`.
533
+
534
+ ## ๐Ÿ” Migration
535
+
536
+ Migrating from `google-trends-api`?
537
+
538
+ ๐Ÿ‘‰ See `MIGRATION.md` for method mapping and before/after examples.
539
+
540
+ ## ๐Ÿ› ๏ธ Development Scripts
541
+
542
+ - `bun run dev` - watch build
543
+ - `bun run build` - build dist
544
+ - `bun run typecheck` - TypeScript checks
545
+ - `bun run lint` - format/lint checks
546
+ - `bun run format` - format/fix
547
+ - `bun run changeset` - add release note
548
+
549
+ ## ๐Ÿ“„ License
550
+
551
+ MIT
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCli } from "../dist/cli.mjs";
4
+
5
+ const exitCode = await runCli({ argv: process.argv });
6
+ process.exitCode = exitCode;
package/dist/cli.d.mts ADDED
@@ -0,0 +1,43 @@
1
+ import { a as TrendSearchClient, n as CreateClientConfig } from "./public-types-B6pmbT6Z.mjs";
2
+ import { Command } from "commander";
3
+
4
+ //#region src/cli/output.d.ts
5
+ interface WritableLike {
6
+ write: (chunk: string) => unknown;
7
+ isTTY?: boolean;
8
+ }
9
+ interface CliIo {
10
+ stdout: WritableLike;
11
+ stderr: WritableLike;
12
+ }
13
+ //#endregion
14
+ //#region src/cli/config.d.ts
15
+ interface CliConfigStore {
16
+ all: () => Record<string, unknown>;
17
+ get: (key: string) => unknown;
18
+ set: (key: string, value: unknown) => void;
19
+ delete: (key: string) => void;
20
+ clear: () => void;
21
+ }
22
+ //#endregion
23
+ //#region src/cli/program.d.ts
24
+ interface CreateProgramOptions {
25
+ io: CliIo;
26
+ env: Record<string, string | undefined>;
27
+ configStore: CliConfigStore;
28
+ stdin: NodeJS.ReadableStream;
29
+ createClient?: (config?: CreateClientConfig) => TrendSearchClient;
30
+ }
31
+ //#endregion
32
+ //#region src/cli/main.d.ts
33
+ interface RunCliOptions {
34
+ argv?: string[];
35
+ io?: CliIo;
36
+ env?: Record<string, string | undefined>;
37
+ stdin?: NodeJS.ReadableStream;
38
+ createClient?: CreateProgramOptions["createClient"];
39
+ configStore?: CreateProgramOptions["configStore"];
40
+ }
41
+ declare const runCli: (options?: RunCliOptions) => Promise<number>;
42
+ //#endregion
43
+ export { RunCliOptions, runCli };