@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 +519 -66
- package/dist/Cache.d.ts +14 -19
- package/dist/Cache.js +16 -13
- package/dist/Dir.d.ts +1 -1
- package/dist/Dir.js +2 -2
- package/dist/Fetcher.d.ts +6 -6
- package/dist/Fetcher.js +6 -6
- package/dist/File.d.ts +13 -13
- package/dist/File.js +17 -15
- package/{src/_index.ts → dist/index.d.ts} +1 -2
- package/dist/{_index.js → index.js} +1 -2
- package/package.json +7 -6
- package/src/Cache.ts +16 -13
- package/src/Dir.ts +2 -2
- package/src/Fetcher.ts +6 -6
- package/src/File.test.ts +15 -2
- package/src/File.ts +21 -19
- package/{dist/_index.d.ts → src/index.ts} +1 -2
- package/dist/Jwt.d.ts +0 -15
- package/dist/Jwt.js +0 -29
- package/src/Jwt.test.ts +0 -22
- package/src/Jwt.ts +0 -47
package/dist/Dir.js
CHANGED
|
@@ -4,7 +4,7 @@ import sanitizeFilename from 'sanitize-filename';
|
|
|
4
4
|
import { File } from './File.js';
|
|
5
5
|
/**
|
|
6
6
|
* Reference to a specific directory with helpful methods for resolving filepaths,
|
|
7
|
-
* sanitizing filenames, and saving files
|
|
7
|
+
* sanitizing filenames, and saving files.
|
|
8
8
|
*/
|
|
9
9
|
export class Dir {
|
|
10
10
|
path;
|
|
@@ -30,7 +30,7 @@ export class Dir {
|
|
|
30
30
|
return new Dir(path.resolve(this.path, subPath));
|
|
31
31
|
}
|
|
32
32
|
sanitize(name) {
|
|
33
|
-
return sanitizeFilename(name.replace('https://', '').replace('www.', ''), { replacement: '_' });
|
|
33
|
+
return sanitizeFilename(name.replace('https://', '').replace('www.', ''), { replacement: '_' }).slice(-200);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* @param base - The file name with extension
|
package/dist/Fetcher.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type FetchOptions = RequestInit & {
|
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* Fetcher provides a quick way to set up a basic API connection
|
|
15
|
-
* with options applied to every request
|
|
15
|
+
* with options applied to every request.
|
|
16
16
|
* Includes basic methods for requesting and parsing responses
|
|
17
17
|
*/
|
|
18
18
|
export declare class Fetcher {
|
|
@@ -40,7 +40,7 @@ export declare class Fetcher {
|
|
|
40
40
|
};
|
|
41
41
|
constructor(opts?: FetchOptions);
|
|
42
42
|
/**
|
|
43
|
-
* Build URL with URLSearchParams if query is provided
|
|
43
|
+
* Build URL with URLSearchParams if query is provided.
|
|
44
44
|
* Also returns domain, to help with cookies
|
|
45
45
|
*/
|
|
46
46
|
buildUrl(route: Route, opts?: FetchOptions): [URL, string];
|
|
@@ -49,14 +49,14 @@ export declare class Fetcher {
|
|
|
49
49
|
*/
|
|
50
50
|
buildHeaders(route: Route, opts?: FetchOptions): HeadersInit & Record<string, string>;
|
|
51
51
|
/**
|
|
52
|
-
* Builds request, merging defaultOptions and provided options
|
|
52
|
+
* Builds request, merging defaultOptions and provided options.
|
|
53
53
|
* Includes Abort signal for timeout
|
|
54
54
|
*/
|
|
55
55
|
buildRequest(route: Route, opts?: FetchOptions): [Request, FetchOptions, string];
|
|
56
56
|
/**
|
|
57
|
-
* Builds and performs the request, merging provided options with defaultOptions
|
|
58
|
-
* If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body
|
|
59
|
-
* Retries on local or network error, with increasing backoff
|
|
57
|
+
* Builds and performs the request, merging provided options with defaultOptions.
|
|
58
|
+
* If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body.
|
|
59
|
+
* Retries on local or network error, with increasing backoff.
|
|
60
60
|
*/
|
|
61
61
|
fetch(route: Route, opts?: FetchOptions): Promise<[Response, Request]>;
|
|
62
62
|
fetchText(route: Route, opts?: FetchOptions): Promise<[string, Response, Request]>;
|
package/dist/Fetcher.js
CHANGED
|
@@ -2,7 +2,7 @@ import { merge } from 'lodash-es';
|
|
|
2
2
|
import extractDomain from 'extract-domain';
|
|
3
3
|
/**
|
|
4
4
|
* Fetcher provides a quick way to set up a basic API connection
|
|
5
|
-
* with options applied to every request
|
|
5
|
+
* with options applied to every request.
|
|
6
6
|
* Includes basic methods for requesting and parsing responses
|
|
7
7
|
*/
|
|
8
8
|
export class Fetcher {
|
|
@@ -16,7 +16,7 @@ export class Fetcher {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Build URL with URLSearchParams if query is provided
|
|
19
|
+
* Build URL with URLSearchParams if query is provided.
|
|
20
20
|
* Also returns domain, to help with cookies
|
|
21
21
|
*/
|
|
22
22
|
buildUrl(route, opts = {}) {
|
|
@@ -47,7 +47,7 @@ export class Fetcher {
|
|
|
47
47
|
return headers || {};
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Builds request, merging defaultOptions and provided options
|
|
50
|
+
* Builds request, merging defaultOptions and provided options.
|
|
51
51
|
* Includes Abort signal for timeout
|
|
52
52
|
*/
|
|
53
53
|
buildRequest(route, opts = {}) {
|
|
@@ -67,9 +67,9 @@ export class Fetcher {
|
|
|
67
67
|
return [req, mergedOptions, domain];
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* Builds and performs the request, merging provided options with defaultOptions
|
|
71
|
-
* If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body
|
|
72
|
-
* Retries on local or network error, with increasing backoff
|
|
70
|
+
* Builds and performs the request, merging provided options with defaultOptions.
|
|
71
|
+
* If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body.
|
|
72
|
+
* Retries on local or network error, with increasing backoff.
|
|
73
73
|
*/
|
|
74
74
|
async fetch(route, opts = {}) {
|
|
75
75
|
const [_req, options] = this.buildRequest(route, opts);
|
package/dist/File.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
2
|
/**
|
|
3
3
|
* WARNING: API will change!
|
|
4
4
|
*/
|
|
@@ -6,10 +6,10 @@ export declare class File {
|
|
|
6
6
|
path: string;
|
|
7
7
|
constructor(filepath: string);
|
|
8
8
|
get exists(): boolean;
|
|
9
|
-
createWriteStream(options?: Parameters<typeof fs.createWriteStream>[1]): fs.WriteStream;
|
|
10
9
|
delete(): void;
|
|
11
10
|
read(): string | undefined;
|
|
12
11
|
write(contents: string): void;
|
|
12
|
+
streamFrom(...options: Parameters<(typeof Readable)['from']>): Promise<void>;
|
|
13
13
|
/**
|
|
14
14
|
* creates file if it doesn't exist, appends string or array of strings as new lines.
|
|
15
15
|
* File always ends with '\n', so contents don't need to be read before appending
|
|
@@ -19,33 +19,33 @@ export declare class File {
|
|
|
19
19
|
* @returns lines as strings, removes trailing '\n'
|
|
20
20
|
*/
|
|
21
21
|
lines(): string[];
|
|
22
|
-
static get
|
|
23
|
-
json<T>(contents?: T):
|
|
24
|
-
static get json(): typeof
|
|
25
|
-
ndjson<T extends object>(lines?: T | T[]):
|
|
26
|
-
static get ndjson(): typeof
|
|
27
|
-
csv<T extends object>(rows?: T[], keys?: (keyof T)[]): Promise<
|
|
28
|
-
static get csv(): typeof
|
|
22
|
+
static get FileType(): typeof FileType;
|
|
23
|
+
json<T>(contents?: T): FileTypeJson<T>;
|
|
24
|
+
static get json(): typeof FileTypeJson;
|
|
25
|
+
ndjson<T extends object>(lines?: T | T[]): FileTypeNdjson<T>;
|
|
26
|
+
static get ndjson(): typeof FileTypeNdjson;
|
|
27
|
+
csv<T extends object>(rows?: T[], keys?: (keyof T)[]): Promise<FileTypeCsv<T>>;
|
|
28
|
+
static get csv(): typeof FileTypeCsv;
|
|
29
29
|
}
|
|
30
|
-
declare class
|
|
30
|
+
export declare class FileType<T = string> {
|
|
31
31
|
file: File;
|
|
32
32
|
constructor(filepath: string, contents?: T);
|
|
33
33
|
get exists(): boolean;
|
|
34
34
|
delete(): void;
|
|
35
35
|
get path(): string;
|
|
36
36
|
}
|
|
37
|
-
declare class
|
|
37
|
+
export declare class FileTypeJson<T> extends FileType {
|
|
38
38
|
constructor(filepath: string, contents?: T);
|
|
39
39
|
read(): T | undefined;
|
|
40
40
|
write(contents: T): void;
|
|
41
41
|
}
|
|
42
|
-
declare class
|
|
42
|
+
export declare class FileTypeNdjson<T extends object> extends FileType {
|
|
43
43
|
constructor(filepath: string, lines?: T | T[]);
|
|
44
44
|
append(lines: T | T[]): void;
|
|
45
45
|
lines(): T[];
|
|
46
46
|
}
|
|
47
47
|
type Key<T extends object> = keyof T;
|
|
48
|
-
declare class
|
|
48
|
+
export declare class FileTypeCsv<Row extends object> extends FileType {
|
|
49
49
|
constructor(filepath: string);
|
|
50
50
|
write(rows: Row[], keys?: Key<Row>[]): Promise<void>;
|
|
51
51
|
read(): Promise<Row[]>;
|
package/dist/File.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { finished } from 'node:stream/promises';
|
|
3
5
|
import { writeToString, parseString } from 'fast-csv';
|
|
4
6
|
import { snapshot } from './snapshot.js';
|
|
5
7
|
/**
|
|
@@ -13,9 +15,6 @@ export class File {
|
|
|
13
15
|
get exists() {
|
|
14
16
|
return fs.existsSync(this.path);
|
|
15
17
|
}
|
|
16
|
-
createWriteStream(options = {}) {
|
|
17
|
-
return fs.createWriteStream(this.path, options);
|
|
18
|
-
}
|
|
19
18
|
delete() {
|
|
20
19
|
fs.rmSync(this.path, { force: true });
|
|
21
20
|
}
|
|
@@ -26,6 +25,9 @@ export class File {
|
|
|
26
25
|
fs.mkdirSync(path.parse(this.path).dir, { recursive: true });
|
|
27
26
|
fs.writeFileSync(this.path, contents);
|
|
28
27
|
}
|
|
28
|
+
async streamFrom(...options) {
|
|
29
|
+
return finished(Readable.from(...options).pipe(fs.createWriteStream(this.path)));
|
|
30
|
+
}
|
|
29
31
|
/**
|
|
30
32
|
* creates file if it doesn't exist, appends string or array of strings as new lines.
|
|
31
33
|
* File always ends with '\n', so contents don't need to be read before appending
|
|
@@ -43,32 +45,32 @@ export class File {
|
|
|
43
45
|
const contents = (this.read() || '').split('\n');
|
|
44
46
|
return contents.slice(0, contents.length - 1);
|
|
45
47
|
}
|
|
46
|
-
static get
|
|
47
|
-
return
|
|
48
|
+
static get FileType() {
|
|
49
|
+
return FileType;
|
|
48
50
|
}
|
|
49
51
|
json(contents) {
|
|
50
|
-
return new
|
|
52
|
+
return new FileTypeJson(this.path, contents);
|
|
51
53
|
}
|
|
52
54
|
static get json() {
|
|
53
|
-
return
|
|
55
|
+
return FileTypeJson;
|
|
54
56
|
}
|
|
55
57
|
ndjson(lines) {
|
|
56
|
-
return new
|
|
58
|
+
return new FileTypeNdjson(this.path, lines);
|
|
57
59
|
}
|
|
58
60
|
static get ndjson() {
|
|
59
|
-
return
|
|
61
|
+
return FileTypeNdjson;
|
|
60
62
|
}
|
|
61
63
|
async csv(rows, keys) {
|
|
62
|
-
const csvFile = new
|
|
64
|
+
const csvFile = new FileTypeCsv(this.path);
|
|
63
65
|
if (rows)
|
|
64
66
|
await csvFile.write(rows, keys);
|
|
65
67
|
return csvFile;
|
|
66
68
|
}
|
|
67
69
|
static get csv() {
|
|
68
|
-
return
|
|
70
|
+
return FileTypeCsv;
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
|
-
class
|
|
73
|
+
export class FileType {
|
|
72
74
|
file;
|
|
73
75
|
constructor(filepath, contents) {
|
|
74
76
|
this.file = new File(filepath);
|
|
@@ -89,7 +91,7 @@ class Adaptor {
|
|
|
89
91
|
return this.file.path;
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
|
-
class
|
|
94
|
+
export class FileTypeJson extends FileType {
|
|
93
95
|
constructor(filepath, contents) {
|
|
94
96
|
super(filepath.endsWith('.json') ? filepath : filepath + '.json');
|
|
95
97
|
if (contents)
|
|
@@ -103,7 +105,7 @@ class JsonFile extends Adaptor {
|
|
|
103
105
|
this.file.write(JSON.stringify(snapshot(contents), null, 2));
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
|
-
class
|
|
108
|
+
export class FileTypeNdjson extends FileType {
|
|
107
109
|
constructor(filepath, lines) {
|
|
108
110
|
super(filepath.endsWith('.ndjson') ? filepath : filepath + '.ndjson');
|
|
109
111
|
if (lines)
|
|
@@ -116,7 +118,7 @@ class NdjsonFile extends Adaptor {
|
|
|
116
118
|
return this.file.lines().map((l) => JSON.parse(l));
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
|
-
class
|
|
121
|
+
export class FileTypeCsv extends FileType {
|
|
120
122
|
constructor(filepath) {
|
|
121
123
|
super(filepath.endsWith('.csv') ? filepath : filepath + '.csv');
|
|
122
124
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { Dir, TempDir, temp } from './Dir.js';
|
|
2
2
|
export { Cache } from './Cache.js';
|
|
3
3
|
export { Fetcher, type Route, type Query, type FetchOptions } from './Fetcher.js';
|
|
4
|
-
export { File } from './File.js';
|
|
5
|
-
export { Jwt } from './Jwt.js';
|
|
4
|
+
export { File, FileType, FileTypeJson, FileTypeNdjson, FileTypeCsv } from './File.js';
|
|
6
5
|
export { Log } from './Log.js';
|
|
7
6
|
export { snapshot } from './snapshot.js';
|
|
8
7
|
export { timeout } from './timeout.js';
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { Dir, TempDir, temp } from './Dir.js';
|
|
2
2
|
export { Cache } from './Cache.js';
|
|
3
3
|
export { Fetcher } from './Fetcher.js';
|
|
4
|
-
export { File } from './File.js';
|
|
5
|
-
export { Jwt } from './Jwt.js';
|
|
4
|
+
export { File, FileType, FileTypeJson, FileTypeNdjson, FileTypeCsv } from './File.js';
|
|
6
5
|
export { Log } from './Log.js';
|
|
7
6
|
export { snapshot } from './snapshot.js';
|
|
8
7
|
export { timeout } from './timeout.js';
|
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brianbuie/node-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/brianbuie/node-kit.git"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"docs": "node ./node_modules/ts2md/out/src/ts2md.js --firstHeadingLevel=1",
|
|
10
11
|
"test": "tsc && node --test \"dist/**/*.test.js\" --quiet",
|
|
11
|
-
"preversion": "npm test",
|
|
12
|
+
"preversion": "npm test && npm run docs",
|
|
12
13
|
"postversion": "git push --follow-tags"
|
|
13
14
|
},
|
|
14
15
|
"type": "module",
|
|
15
16
|
"exports": {
|
|
16
17
|
".": {
|
|
17
|
-
"types": "./dist/
|
|
18
|
-
"default": "./dist/
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
19
20
|
}
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
@@ -29,17 +30,17 @@
|
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"chalk": "^5.6.2",
|
|
33
|
+
"date-fns": "^4.1.0",
|
|
32
34
|
"extract-domain": "^5.0.2",
|
|
33
35
|
"fast-csv": "^5.0.5",
|
|
34
|
-
"jsonwebtoken": "^9.0.2",
|
|
35
36
|
"lodash-es": "^4.17.21",
|
|
36
37
|
"quicktype-core": "^23.2.6",
|
|
37
38
|
"sanitize-filename": "^1.6.3"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@types/jsonwebtoken": "^9.0.10",
|
|
41
41
|
"@types/lodash-es": "^4.17.12",
|
|
42
42
|
"@types/node": "^24.9.1",
|
|
43
|
+
"ts2md": "^0.2.8",
|
|
43
44
|
"typescript": "^5.9.3"
|
|
44
45
|
}
|
|
45
46
|
}
|
package/src/Cache.ts
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
|
+
import { type Duration, isAfter, add } from 'date-fns';
|
|
1
2
|
import { temp } from './Dir.js';
|
|
2
3
|
|
|
3
4
|
const cacheDir = temp.dir('cache');
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Save data to a local file with an expiration.
|
|
8
|
+
* Fresh/stale data is returned with a flag for if it's fresh or not,
|
|
9
|
+
* so stale data can still be used if needed.
|
|
10
|
+
*/
|
|
5
11
|
export class Cache<T> {
|
|
6
12
|
file;
|
|
7
13
|
ttl;
|
|
8
|
-
refresh;
|
|
9
14
|
|
|
10
|
-
constructor(key: string, ttl: number
|
|
11
|
-
this.file = cacheDir.file(key).json<{
|
|
12
|
-
this.ttl = ttl;
|
|
13
|
-
this.
|
|
15
|
+
constructor(key: string, ttl: number | Duration, initialData?: T) {
|
|
16
|
+
this.file = cacheDir.file(key).json<{ savedAt: string; data: T }>();
|
|
17
|
+
this.ttl = typeof ttl === 'number' ? { minutes: ttl } : ttl;
|
|
18
|
+
if (initialData) this.write(initialData);
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (value && createdAt && createdAt + this.ttl > Date.now()) return value;
|
|
19
|
-
return this.write();
|
|
21
|
+
write(data: T) {
|
|
22
|
+
this.file.write({ savedAt: new Date().toUTCString(), data });
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
return
|
|
25
|
+
read(): [T | undefined, boolean] {
|
|
26
|
+
const { savedAt, data } = this.file.read() || {};
|
|
27
|
+
const isFresh = Boolean(savedAt && isAfter(add(savedAt, this.ttl), new Date()));
|
|
28
|
+
return [data, isFresh];
|
|
26
29
|
}
|
|
27
30
|
}
|
package/src/Dir.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { File } from './File.js';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Reference to a specific directory with helpful methods for resolving filepaths,
|
|
8
|
-
* sanitizing filenames, and saving files
|
|
8
|
+
* sanitizing filenames, and saving files.
|
|
9
9
|
*/
|
|
10
10
|
export class Dir {
|
|
11
11
|
path;
|
|
@@ -35,7 +35,7 @@ export class Dir {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
sanitize(name: string) {
|
|
38
|
-
return sanitizeFilename(name.replace('https://', '').replace('www.', ''), { replacement: '_' });
|
|
38
|
+
return sanitizeFilename(name.replace('https://', '').replace('www.', ''), { replacement: '_' }).slice(-200);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
package/src/Fetcher.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type FetchOptions = RequestInit & {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Fetcher provides a quick way to set up a basic API connection
|
|
21
|
-
* with options applied to every request
|
|
21
|
+
* with options applied to every request.
|
|
22
22
|
* Includes basic methods for requesting and parsing responses
|
|
23
23
|
*/
|
|
24
24
|
export class Fetcher {
|
|
@@ -34,7 +34,7 @@ export class Fetcher {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Build URL with URLSearchParams if query is provided
|
|
37
|
+
* Build URL with URLSearchParams if query is provided.
|
|
38
38
|
* Also returns domain, to help with cookies
|
|
39
39
|
*/
|
|
40
40
|
buildUrl(route: Route, opts: FetchOptions = {}): [URL, string] {
|
|
@@ -65,7 +65,7 @@ export class Fetcher {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Builds request, merging defaultOptions and provided options
|
|
68
|
+
* Builds request, merging defaultOptions and provided options.
|
|
69
69
|
* Includes Abort signal for timeout
|
|
70
70
|
*/
|
|
71
71
|
buildRequest(route: Route, opts: FetchOptions = {}): [Request, FetchOptions, string] {
|
|
@@ -86,9 +86,9 @@ export class Fetcher {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* Builds and performs the request, merging provided options with defaultOptions
|
|
90
|
-
* If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body
|
|
91
|
-
* Retries on local or network error, with increasing backoff
|
|
89
|
+
* Builds and performs the request, merging provided options with defaultOptions.
|
|
90
|
+
* If `opts.data` is provided, method is updated to POST, content-type json, data is stringified in the body.
|
|
91
|
+
* Retries on local or network error, with increasing backoff.
|
|
92
92
|
*/
|
|
93
93
|
async fetch(route: Route, opts: FetchOptions = {}): Promise<[Response, Request]> {
|
|
94
94
|
const [_req, options] = this.buildRequest(route, opts);
|
package/src/File.test.ts
CHANGED
|
@@ -14,9 +14,9 @@ const thing = {
|
|
|
14
14
|
e: null,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
describe('
|
|
17
|
+
describe('FileType', () => {
|
|
18
18
|
it('Creates instances', () => {
|
|
19
|
-
const test1 = new File.
|
|
19
|
+
const test1 = new File.FileType(testDir.filepath('test1.txt'));
|
|
20
20
|
assert(test1.file.path.includes('test1.txt'));
|
|
21
21
|
const base = 'test2';
|
|
22
22
|
const eg1 = new File.json(testDir.filepath(base));
|
|
@@ -35,6 +35,19 @@ describe('FileAdaptor', () => {
|
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
describe('File', () => {
|
|
39
|
+
it('Handles request body as stream input', async () => {
|
|
40
|
+
const res = await fetch('https://testingbot.com/free-online-tools/random-avatar/300');
|
|
41
|
+
const img = testDir.file('image.jpg');
|
|
42
|
+
if (res.body) {
|
|
43
|
+
await img.streamFrom(res.body);
|
|
44
|
+
assert(img.exists);
|
|
45
|
+
} else {
|
|
46
|
+
assert(false, 'No response body');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
38
51
|
describe('File.ndjson', () => {
|
|
39
52
|
it('Appends new lines correctly', () => {
|
|
40
53
|
const file = testDir.file('appends-lines').ndjson();
|
package/src/File.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { finished } from 'node:stream/promises';
|
|
3
5
|
import { writeToString, parseString } from 'fast-csv';
|
|
4
6
|
import { snapshot } from './snapshot.js';
|
|
5
7
|
|
|
@@ -17,10 +19,6 @@ export class File {
|
|
|
17
19
|
return fs.existsSync(this.path);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
createWriteStream(options: Parameters<typeof fs.createWriteStream>[1] = {}) {
|
|
21
|
-
return fs.createWriteStream(this.path, options);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
22
|
delete() {
|
|
25
23
|
fs.rmSync(this.path, { force: true });
|
|
26
24
|
}
|
|
@@ -34,6 +32,10 @@ export class File {
|
|
|
34
32
|
fs.writeFileSync(this.path, contents);
|
|
35
33
|
}
|
|
36
34
|
|
|
35
|
+
async streamFrom(...options: Parameters<(typeof Readable)['from']>) {
|
|
36
|
+
return finished(Readable.from(...options).pipe(fs.createWriteStream(this.path)));
|
|
37
|
+
}
|
|
38
|
+
|
|
37
39
|
/**
|
|
38
40
|
* creates file if it doesn't exist, appends string or array of strings as new lines.
|
|
39
41
|
* File always ends with '\n', so contents don't need to be read before appending
|
|
@@ -52,38 +54,38 @@ export class File {
|
|
|
52
54
|
return contents.slice(0, contents.length - 1);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
static get
|
|
56
|
-
return
|
|
57
|
+
static get FileType() {
|
|
58
|
+
return FileType;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
json<T>(contents?: T) {
|
|
60
|
-
return new
|
|
62
|
+
return new FileTypeJson<T>(this.path, contents);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
static get json() {
|
|
64
|
-
return
|
|
66
|
+
return FileTypeJson;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
ndjson<T extends object>(lines?: T | T[]) {
|
|
68
|
-
return new
|
|
70
|
+
return new FileTypeNdjson<T>(this.path, lines);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
static get ndjson() {
|
|
72
|
-
return
|
|
74
|
+
return FileTypeNdjson;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
async csv<T extends object>(rows?: T[], keys?: (keyof T)[]) {
|
|
76
|
-
const csvFile = new
|
|
78
|
+
const csvFile = new FileTypeCsv<T>(this.path);
|
|
77
79
|
if (rows) await csvFile.write(rows, keys);
|
|
78
80
|
return csvFile;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
static get csv() {
|
|
82
|
-
return
|
|
84
|
+
return FileTypeCsv;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
class
|
|
88
|
+
export class FileType<T = string> {
|
|
87
89
|
file;
|
|
88
90
|
|
|
89
91
|
constructor(filepath: string, contents?: T) {
|
|
@@ -109,7 +111,7 @@ class Adaptor<T = string> {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
class
|
|
114
|
+
export class FileTypeJson<T> extends FileType {
|
|
113
115
|
constructor(filepath: string, contents?: T) {
|
|
114
116
|
super(filepath.endsWith('.json') ? filepath : filepath + '.json');
|
|
115
117
|
if (contents) this.write(contents);
|
|
@@ -125,7 +127,7 @@ class JsonFile<T> extends Adaptor {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
class
|
|
130
|
+
export class FileTypeNdjson<T extends object> extends FileType {
|
|
129
131
|
constructor(filepath: string, lines?: T | T[]) {
|
|
130
132
|
super(filepath.endsWith('.ndjson') ? filepath : filepath + '.ndjson');
|
|
131
133
|
if (lines) this.append(lines);
|
|
@@ -133,7 +135,7 @@ class NdjsonFile<T extends object> extends Adaptor {
|
|
|
133
135
|
|
|
134
136
|
append(lines: T | T[]) {
|
|
135
137
|
this.file.append(
|
|
136
|
-
Array.isArray(lines) ? lines.map((l) => JSON.stringify(snapshot(l))) : JSON.stringify(snapshot(lines))
|
|
138
|
+
Array.isArray(lines) ? lines.map((l) => JSON.stringify(snapshot(l))) : JSON.stringify(snapshot(lines)),
|
|
137
139
|
);
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -144,7 +146,7 @@ class NdjsonFile<T extends object> extends Adaptor {
|
|
|
144
146
|
|
|
145
147
|
type Key<T extends object> = keyof T;
|
|
146
148
|
|
|
147
|
-
class
|
|
149
|
+
export class FileTypeCsv<Row extends object> extends FileType {
|
|
148
150
|
constructor(filepath: string) {
|
|
149
151
|
super(filepath.endsWith('.csv') ? filepath : filepath + '.csv');
|
|
150
152
|
}
|
|
@@ -186,8 +188,8 @@ class CsvFile<Row extends object> extends Adaptor {
|
|
|
186
188
|
...all,
|
|
187
189
|
[key]: parseVal(val as string),
|
|
188
190
|
}),
|
|
189
|
-
{} as Row
|
|
190
|
-
)
|
|
191
|
+
{} as Row,
|
|
192
|
+
),
|
|
191
193
|
);
|
|
192
194
|
});
|
|
193
195
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { Dir, TempDir, temp } from './Dir.js';
|
|
2
2
|
export { Cache } from './Cache.js';
|
|
3
3
|
export { Fetcher, type Route, type Query, type FetchOptions } from './Fetcher.js';
|
|
4
|
-
export { File } from './File.js';
|
|
5
|
-
export { Jwt } from './Jwt.js';
|
|
4
|
+
export { File, FileType, FileTypeJson, FileTypeNdjson, FileTypeCsv } from './File.js';
|
|
6
5
|
export { Log } from './Log.js';
|
|
7
6
|
export { snapshot } from './snapshot.js';
|
|
8
7
|
export { timeout } from './timeout.js';
|
package/dist/Jwt.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type JwtPayload, type SignOptions } from 'jsonwebtoken';
|
|
2
|
-
type JwtConfig = {
|
|
3
|
-
payload: JwtPayload;
|
|
4
|
-
options: SignOptions;
|
|
5
|
-
seconds: number;
|
|
6
|
-
key: string;
|
|
7
|
-
};
|
|
8
|
-
export declare class Jwt {
|
|
9
|
-
#private;
|
|
10
|
-
config: JwtConfig;
|
|
11
|
-
constructor(config: JwtConfig);
|
|
12
|
-
get now(): number;
|
|
13
|
-
get token(): string;
|
|
14
|
-
}
|
|
15
|
-
export {};
|
package/dist/Jwt.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { default as jsonwebtoken } from 'jsonwebtoken';
|
|
2
|
-
import { merge } from 'lodash-es';
|
|
3
|
-
export class Jwt {
|
|
4
|
-
config;
|
|
5
|
-
#saved;
|
|
6
|
-
constructor(config) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
this.#createToken();
|
|
9
|
-
}
|
|
10
|
-
get now() {
|
|
11
|
-
return Math.floor(Date.now() / 1000);
|
|
12
|
-
}
|
|
13
|
-
#createToken() {
|
|
14
|
-
const exp = this.now + this.config.seconds;
|
|
15
|
-
const payload = merge({
|
|
16
|
-
iat: this.now,
|
|
17
|
-
exp,
|
|
18
|
-
}, this.config.payload);
|
|
19
|
-
const token = jsonwebtoken.sign(payload, this.config.key, this.config.options);
|
|
20
|
-
this.#saved = { token, exp };
|
|
21
|
-
return token;
|
|
22
|
-
}
|
|
23
|
-
get token() {
|
|
24
|
-
if (this.#saved && this.#saved.exp > this.now) {
|
|
25
|
-
return this.#saved.token;
|
|
26
|
-
}
|
|
27
|
-
return this.#createToken();
|
|
28
|
-
}
|
|
29
|
-
}
|
package/src/Jwt.test.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import jsonwebtoken from 'jsonwebtoken';
|
|
4
|
-
import { Jwt } from './Jwt.js';
|
|
5
|
-
|
|
6
|
-
describe('Jwt', () => {
|
|
7
|
-
it('Creates a valid JWT', () => {
|
|
8
|
-
const key = 'test';
|
|
9
|
-
const jwt = new Jwt({
|
|
10
|
-
payload: {
|
|
11
|
-
example: 'value',
|
|
12
|
-
},
|
|
13
|
-
options: {
|
|
14
|
-
algorithm: 'HS256',
|
|
15
|
-
},
|
|
16
|
-
seconds: 60,
|
|
17
|
-
key,
|
|
18
|
-
});
|
|
19
|
-
const result = jsonwebtoken.verify(jwt.token, key);
|
|
20
|
-
assert(typeof result !== 'string' && result.example === 'value');
|
|
21
|
-
});
|
|
22
|
-
});
|