@brianbuie/node-kit 0.6.0 → 0.8.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.
package/README.md CHANGED
@@ -4,110 +4,563 @@
4
4
 
5
5
  Basic tools for quick node.js projects
6
6
 
7
- ## Installing
7
+ # Installing
8
8
 
9
9
  ```
10
10
  npm add @brianbuie/node-kit
11
11
  ```
12
12
 
13
13
  ```js
14
- import { thing } from '@brianbuie/node-kit';
14
+ import { Fetcher, Log } from '@brianbuie/node-kit';
15
15
  ```
16
16
 
17
- ## Features
17
+ # API
18
18
 
19
- ### Fetcher
19
+ <!--#region ts2md-api-merged-here-->
20
+
21
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
22
+
23
+ # Classes
24
+
25
+ | |
26
+ | --- |
27
+ | [Cache](#class-cache) |
28
+ | [Dir](#class-dir) |
29
+ | [Fetcher](#class-fetcher) |
30
+ | [File](#class-file) |
31
+ | [FileType](#class-filetype) |
32
+ | [FileTypeCsv](#class-filetypecsv) |
33
+ | [FileTypeJson](#class-filetypejson) |
34
+ | [FileTypeNdjson](#class-filetypendjson) |
35
+ | [Log](#class-log) |
36
+ | [TempDir](#class-tempdir) |
37
+ | [TypeWriter](#class-typewriter) |
38
+
39
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
40
+
41
+ ---
42
+
43
+ ## Class: Cache
44
+
45
+ Save data to a local file with an expiration.
46
+ Fresh/stale data is returned with a flag for if it's fresh or not,
47
+ so stale data can still be used if needed.
20
48
 
21
49
  ```ts
22
- import { Fetcher } from '@brianbuie/node-kit';
50
+ export class Cache<T> {
51
+ file;
52
+ ttl;
53
+ constructor(key: string, ttl: number | Duration, initialData?: T)
54
+ write(data: T)
55
+ read(): [
56
+ T | undefined,
57
+ boolean
58
+ ]
59
+ }
60
+ ```
61
+
62
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
23
63
 
24
- // All requests will include Authorization header
25
- const api = new Fetcher({
26
- base: 'https://www.example.com',
27
- headers: {
28
- Authorization: `Bearer ${process.env.EXAMPLE_SECRET}`,
29
- },
30
- });
64
+ ---
65
+ ## Class: Dir
31
66
 
32
- // GET https://www.example.com/route
33
- // returns [Response, Request]
34
- const [res] = await api.fetch('/route');
67
+ Reference to a specific directory with helpful methods for resolving filepaths,
68
+ sanitizing filenames, and saving files.
69
+
70
+ ```ts
71
+ export class Dir {
72
+ path;
73
+ constructor(_path: string)
74
+ create()
75
+ dir(subPath: string)
76
+ sanitize(name: string)
77
+ filepath(base: string)
78
+ file(base: string)
79
+ }
80
+ ```
35
81
 
36
- // GET https://www.example.com/other-route
37
- // returns [string, Response, Request]
38
- const [text] = await api.fetchText('/other-route');
82
+ <details>
39
83
 
40
- // GET https://www.example.com/thing?page=1
41
- // returns [Thing, Response, Request]
42
- const [data] = await api.fetchJson<Thing>('/thing', { query: { page: 1 } });
84
+ <summary>Class Dir Details</summary>
43
85
 
44
- // POST https://www.example.com/thing (data is sent as JSON in body)
45
- // returns [Thing, Response, Request]
46
- const [result] = await api.fetchJson<Thing>('/thing', { data: { example: 1 } });
86
+ ### Constructor
87
+
88
+ ```ts
89
+ constructor(_path: string)
47
90
  ```
48
91
 
49
- ### Jwt
92
+ Argument Details
50
93
 
51
- Save a JSON Web Token in memory and reuse it throughout the process.
94
+ + **path**
95
+ + can be relative to workspace or absolute
52
96
 
53
- ```js
54
- import { Jwt, Fetcher } from '@brianbuie/node-kit';
97
+ ### Method dir
55
98
 
56
- const apiJwt = new Jwt({
57
- payload: {
58
- example: 'value',
59
- },
60
- options: {
61
- algorithm: 'HS256',
62
- },
63
- seconds: 60,
64
- key: process.env.JWT_KEY,
65
- });
99
+ Create a new Dir inside the current Dir
66
100
 
67
- const api = new Fetcher({
68
- base: 'https://example.com',
69
- headers: {
70
- Authorization: `Bearer ${apiJwt.token}`,
71
- },
72
- });
101
+ ```ts
102
+ dir(subPath: string)
73
103
  ```
74
104
 
75
- > TODO: expiration is not checked again when provided in a header
105
+ Argument Details
76
106
 
77
- ### Log
107
+ + **subPath**
108
+ + to create in current Dir
78
109
 
79
- Chalk output in development, structured JSON when running in gcloud
110
+ Example
80
111
 
81
- ```js
82
- import { Log } from '@brianbuie/node-kit';
112
+ ```ts
113
+ const folder = new Dir('example');
114
+ // folder.path = './example'
115
+ const child = folder.subDir('path/to/dir');
116
+ // child.path = './example/path/to/dir'
117
+ ```
118
+
119
+ ### Method filepath
120
+
121
+ ```ts
122
+ filepath(base: string)
123
+ ```
83
124
 
84
- Log.info('message', { other: 'details' });
125
+ Argument Details
85
126
 
86
- // Print in development, or if process.env.DEBUG or --debug argument is present
87
- Log.debug('message', Response);
127
+ + **base**
128
+ + The file name with extension
88
129
 
89
- // Log details and throw
90
- Log.error('Something happened', details, moreDetails);
130
+ Example
131
+
132
+ ```ts
133
+ const folder = new Dir('example');
134
+ const filepath = folder.resolve('file.json');
135
+ // 'example/file.json'
91
136
  ```
92
137
 
93
- ### snapshot
138
+ </details>
94
139
 
95
- Gets all enumerable and non-enumerable properties, so they can be included in JSON.stringify. Helpful for built-in objects, like Error, Request, Response, Headers, Map, etc.
140
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
96
141
 
97
- ```js
98
- fs.writeFileSync('result.json', JSON.stringify(snapshot(response), null, 2));
142
+ ---
143
+ ## Class: Fetcher
144
+
145
+ Fetcher provides a quick way to set up a basic API connection
146
+ with options applied to every request.
147
+ Includes basic methods for requesting and parsing responses
148
+
149
+ ```ts
150
+ export class Fetcher {
151
+ defaultOptions;
152
+ constructor(opts: FetchOptions = {})
153
+ buildUrl(route: Route, opts: FetchOptions = {}): [
154
+ URL,
155
+ string
156
+ ]
157
+ buildHeaders(route: Route, opts: FetchOptions = {})
158
+ buildRequest(route: Route, opts: FetchOptions = {}): [
159
+ Request,
160
+ FetchOptions,
161
+ string
162
+ ]
163
+ async fetch(route: Route, opts: FetchOptions = {}): Promise<[
164
+ Response,
165
+ Request
166
+ ]>
167
+ async fetchText(route: Route, opts: FetchOptions = {}): Promise<[
168
+ string,
169
+ Response,
170
+ Request
171
+ ]>
172
+ async fetchJson<T>(route: Route, opts: FetchOptions = {}): Promise<[
173
+ T,
174
+ Response,
175
+ Request
176
+ ]>
177
+ }
178
+ ```
179
+
180
+ See also: [FetchOptions](#type-fetchoptions), [Route](#type-route)
181
+
182
+ <details>
183
+
184
+ <summary>Class Fetcher Details</summary>
185
+
186
+ ### Method buildHeaders
187
+
188
+ Merges options to get headers. Useful when extending the Fetcher class to add custom auth.
189
+
190
+ ```ts
191
+ buildHeaders(route: Route, opts: FetchOptions = {})
192
+ ```
193
+ See also: [FetchOptions](#type-fetchoptions), [Route](#type-route)
194
+
195
+ ### Method buildRequest
196
+
197
+ Builds request, merging defaultOptions and provided options.
198
+ Includes Abort signal for timeout
199
+
200
+ ```ts
201
+ buildRequest(route: Route, opts: FetchOptions = {}): [
202
+ Request,
203
+ FetchOptions,
204
+ string
205
+ ]
206
+ ```
207
+ See also: [FetchOptions](#type-fetchoptions), [Route](#type-route)
208
+
209
+ ### Method buildUrl
210
+
211
+ Build URL with URLSearchParams if query is provided.
212
+ Also returns domain, to help with cookies
213
+
214
+ ```ts
215
+ buildUrl(route: Route, opts: FetchOptions = {}): [
216
+ URL,
217
+ string
218
+ ]
219
+ ```
220
+ See also: [FetchOptions](#type-fetchoptions), [Route](#type-route)
221
+
222
+ ### Method fetch
223
+
224
+ Builds and performs the request, merging provided options with defaultOptions.
225
+ If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body.
226
+ Retries on local or network error, with increasing backoff.
227
+
228
+ ```ts
229
+ async fetch(route: Route, opts: FetchOptions = {}): Promise<[
230
+ Response,
231
+ Request
232
+ ]>
233
+ ```
234
+ See also: [FetchOptions](#type-fetchoptions), [Route](#type-route)
235
+
236
+ </details>
237
+
238
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
239
+
240
+ ---
241
+ ## Class: File
242
+
243
+ WARNING: API will change!
244
+
245
+ ```ts
246
+ export class File {
247
+ path;
248
+ constructor(filepath: string)
249
+ get exists()
250
+ delete()
251
+ read()
252
+ write(contents: string)
253
+ async streamFrom(...options: Parameters<(typeof Readable)["from"]>)
254
+ append(lines: string | string[])
255
+ lines()
256
+ static get FileType()
257
+ json<T>(contents?: T)
258
+ static get json()
259
+ ndjson<T extends object>(lines?: T | T[])
260
+ static get ndjson()
261
+ async csv<T extends object>(rows?: T[], keys?: (keyof T)[])
262
+ static get csv()
263
+ }
264
+ ```
265
+
266
+ See also: [FileType](#class-filetype)
267
+
268
+ <details>
269
+
270
+ <summary>Class File Details</summary>
271
+
272
+ ### Method append
273
+
274
+ creates file if it doesn't exist, appends string or array of strings as new lines.
275
+ File always ends with '\n', so contents don't need to be read before appending
276
+
277
+ ```ts
278
+ append(lines: string | string[])
279
+ ```
280
+
281
+ ### Method lines
282
+
283
+ ```ts
284
+ lines()
285
+ ```
286
+
287
+ Returns
288
+
289
+ lines as strings, removes trailing '\n'
290
+
291
+ </details>
292
+
293
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
294
+
295
+ ---
296
+ ## Class: FileType
297
+
298
+ ```ts
299
+ export class FileType<T = string> {
300
+ file;
301
+ constructor(filepath: string, contents?: T)
302
+ get exists()
303
+ delete()
304
+ get path()
305
+ }
306
+ ```
307
+
308
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
309
+
310
+ ---
311
+ ## Class: FileTypeCsv
312
+
313
+ ```ts
314
+ export class FileTypeCsv<Row extends object> extends FileType {
315
+ constructor(filepath: string)
316
+ async write(rows: Row[], keys?: Key<Row>[])
317
+ async read()
318
+ }
319
+ ```
320
+
321
+ See also: [FileType](#class-filetype)
322
+
323
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
324
+
325
+ ---
326
+ ## Class: FileTypeJson
327
+
328
+ ```ts
329
+ export class FileTypeJson<T> extends FileType {
330
+ constructor(filepath: string, contents?: T)
331
+ read()
332
+ write(contents: T)
333
+ }
334
+ ```
335
+
336
+ See also: [FileType](#class-filetype)
337
+
338
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
339
+
340
+ ---
341
+ ## Class: FileTypeNdjson
342
+
343
+ ```ts
344
+ export class FileTypeNdjson<T extends object> extends FileType {
345
+ constructor(filepath: string, lines?: T | T[])
346
+ append(lines: T | T[])
347
+ lines()
348
+ }
349
+ ```
350
+
351
+ See also: [FileType](#class-filetype)
352
+
353
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
354
+
355
+ ---
356
+ ## Class: Log
357
+
358
+ ```ts
359
+ export class Log {
360
+ static isTest = process.env.npm_package_name === "@brianbuie/node-kit" && process.env.npm_lifecycle_event === "test";
361
+ static #toGcloud(entry: Entry)
362
+ static #toConsole(entry: Entry, color: ChalkInstance)
363
+ static #log(options: Options, ...input: unknown[])
364
+ static prepare(...input: unknown[]): {
365
+ message?: string;
366
+ details: unknown[];
367
+ }
368
+ static error(...input: unknown[])
369
+ static warn(...input: unknown[])
370
+ static notice(...input: unknown[])
371
+ static info(...input: unknown[])
372
+ static debug(...input: unknown[])
373
+ }
374
+ ```
375
+
376
+ <details>
377
+
378
+ <summary>Class Log Details</summary>
379
+
380
+ ### Method
381
+
382
+ Gcloud parses JSON in stdout
383
+
384
+ ```ts
385
+ static #toGcloud(entry: Entry)
386
+ ```
387
+
388
+ ### Method
389
+
390
+ Includes colors and better inspection for logging during dev
391
+
392
+ ```ts
393
+ static #toConsole(entry: Entry, color: ChalkInstance)
394
+ ```
395
+
396
+ ### Method error
397
+
398
+ Logs error details before throwing
399
+
400
+ ```ts
401
+ static error(...input: unknown[])
402
+ ```
403
+
404
+ ### Method prepare
405
+
406
+ Handle first argument being a string or an object with a 'message' prop
407
+ Also snapshots special objects (eg Error, Response) to keep props in later JSON.stringify output
408
+
409
+ ```ts
410
+ static prepare(...input: unknown[]): {
411
+ message?: string;
412
+ details: unknown[];
413
+ }
414
+ ```
415
+
416
+ </details>
417
+
418
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
419
+
420
+ ---
421
+ ## Class: TempDir
422
+
423
+ Extends Dir class with method to `clear()` contents
424
+
425
+ ```ts
426
+ export class TempDir extends Dir {
427
+ dir(subPath: string)
428
+ clear()
429
+ }
430
+ ```
431
+
432
+ See also: [Dir](#class-dir)
433
+
434
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
435
+
436
+ ---
437
+ ## Class: TypeWriter
438
+
439
+ ```ts
440
+ export class TypeWriter {
441
+ moduleName;
442
+ input = qt.jsonInputForTargetLanguage("typescript");
443
+ outDir;
444
+ qtSettings;
445
+ constructor(moduleName: string, settings: {
446
+ outDir?: string;
447
+ } & Partial<qt.Options> = {})
448
+ async addMember(name: string, _samples: any[])
449
+ async toString()
450
+ async toFile()
451
+ }
452
+ ```
453
+
454
+ <details>
455
+
456
+ <summary>Class TypeWriter Details</summary>
457
+
458
+ ### Method toString
459
+
460
+ function toString() { [native code] }
461
+
462
+ ```ts
463
+ async toString()
464
+ ```
465
+
466
+ </details>
467
+
468
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
469
+
470
+ ---
471
+ # Functions
472
+
473
+ | |
474
+ | --- |
475
+ | [snapshot](#function-snapshot) |
476
+ | [timeout](#function-timeout) |
477
+
478
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
479
+
480
+ ---
481
+
482
+ ## Function: snapshot
483
+
484
+ Allows special objects (Error, Headers, Set) to be included in JSON.stringify output
485
+ functions are removed
486
+
487
+ ```ts
488
+ export function snapshot(i: unknown, max = 50, depth = 0): any
489
+ ```
490
+
491
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
492
+
493
+ ---
494
+ ## Function: timeout
495
+
496
+ ```ts
497
+ export async function timeout(ms: number)
498
+ ```
499
+
500
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
501
+
502
+ ---
503
+ # Types
504
+
505
+ | |
506
+ | --- |
507
+ | [FetchOptions](#type-fetchoptions) |
508
+ | [Query](#type-query) |
509
+ | [Route](#type-route) |
510
+
511
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
512
+
513
+ ---
514
+
515
+ ## Type: FetchOptions
516
+
517
+ ```ts
518
+ export type FetchOptions = RequestInit & {
519
+ base?: string;
520
+ query?: Query;
521
+ headers?: Record<string, string>;
522
+ data?: any;
523
+ timeout?: number;
524
+ retries?: number;
525
+ retryDelay?: number;
526
+ }
99
527
  ```
100
528
 
101
- ## Publishing changes to this package
529
+ See also: [Query](#type-query), [timeout](#function-timeout)
530
+
531
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
102
532
 
103
- Commit all changes, then run:
533
+ ---
534
+ ## Type: Query
104
535
 
536
+ ```ts
537
+ export type Query = Record<string, QueryVal | QueryVal[]>
105
538
  ```
106
- npm version [patch|minor|major] [-m "custom commit message"]
539
+
540
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
541
+
542
+ ---
543
+ ## Type: Route
544
+
545
+ ```ts
546
+ export type Route = string | URL
107
547
  ```
108
548
 
109
- - Bumps version in `package.json`
110
- - Runs tests (`"preversion"` script in `package.json`)
111
- - Creates new commit, tagged with version
112
- - Pushes commit and tags to github (`"postversion"` script in `package.json`)
113
- - The new tag will trigger github action to publish to npm (`.github/actions/publish.yml`)
549
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
550
+
551
+ ---
552
+ # Variables
553
+
554
+ ## Variable: temp
555
+
556
+ ```ts
557
+ temp = new TempDir(".temp")
558
+ ```
559
+
560
+ See also: [TempDir](#class-tempdir)
561
+
562
+ Links: [API](#api), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
563
+
564
+ ---
565
+
566
+ <!--#endregion ts2md-api-merged-here-->
package/dist/Cache.d.ts CHANGED
@@ -1,21 +1,16 @@
1
+ import { type Duration } from 'date-fns';
2
+ /**
3
+ * Save data to a local file with an expiration.
4
+ * Fresh/stale data is returned with a flag for if it's fresh or not,
5
+ * so stale data can still be used if needed.
6
+ */
1
7
  export declare class Cache<T> {
2
- file: {
3
- read(): {
4
- createdAt: number;
5
- value: T;
6
- } | undefined;
7
- write(contents: {
8
- createdAt: number;
9
- value: T;
10
- }): void;
11
- file: import("./File.js").File;
12
- get exists(): boolean;
13
- delete(): void;
14
- get path(): string;
15
- };
16
- ttl: number;
17
- refresh: () => T | Promise<T>;
18
- constructor(key: string, ttl: number, refresh: () => T | Promise<T>);
19
- read(): Promise<T>;
20
- write(): Promise<T>;
8
+ file: import("./File.js").FileTypeJson<{
9
+ savedAt: string;
10
+ data: T;
11
+ }>;
12
+ ttl: Duration;
13
+ constructor(key: string, ttl: number | Duration, initialData?: T);
14
+ write(data: T): void;
15
+ read(): [T | undefined, boolean];
21
16
  }
package/dist/Cache.js CHANGED
@@ -1,23 +1,26 @@
1
+ import { isAfter, add } from 'date-fns';
1
2
  import { temp } from './Dir.js';
2
3
  const cacheDir = temp.dir('cache');
4
+ /**
5
+ * Save data to a local file with an expiration.
6
+ * Fresh/stale data is returned with a flag for if it's fresh or not,
7
+ * so stale data can still be used if needed.
8
+ */
3
9
  export class Cache {
4
10
  file;
5
11
  ttl;
6
- refresh;
7
- constructor(key, ttl, refresh) {
12
+ constructor(key, ttl, initialData) {
8
13
  this.file = cacheDir.file(key).json();
9
- this.ttl = ttl;
10
- this.refresh = refresh;
14
+ this.ttl = typeof ttl === 'number' ? { minutes: ttl } : ttl;
15
+ if (initialData)
16
+ this.write(initialData);
11
17
  }
12
- async read() {
13
- const { createdAt, value } = this.file.read() || {};
14
- if (value && createdAt && createdAt + this.ttl > Date.now())
15
- return value;
16
- return this.write();
18
+ write(data) {
19
+ this.file.write({ savedAt: new Date().toUTCString(), data });
17
20
  }
18
- async write() {
19
- const value = await this.refresh();
20
- this.file.write({ createdAt: Date.now(), value });
21
- return value;
21
+ read() {
22
+ const { savedAt, data } = this.file.read() || {};
23
+ const isFresh = Boolean(savedAt && isAfter(add(savedAt, this.ttl), new Date()));
24
+ return [data, isFresh];
22
25
  }
23
26
  }
package/dist/Dir.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { File } from './File.js';
2
2
  /**
3
3
  * Reference to a specific directory with helpful methods for resolving filepaths,
4
- * sanitizing filenames, and saving files
4
+ * sanitizing filenames, and saving files.
5
5
  */
6
6
  export declare class Dir {
7
7
  path: string;