@brianbuie/node-kit 0.2.5 → 0.4.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/.dist/Cache.d.ts +20 -0
- package/.dist/Cache.js +23 -0
- package/.dist/Dir.d.ts +4 -9
- package/.dist/Dir.js +6 -19
- package/.dist/Fetcher.js +1 -1
- package/.dist/File.d.ts +38 -0
- package/.dist/File.js +96 -0
- package/.dist/Jwt.d.ts +7 -8
- package/.dist/Jwt.js +12 -18
- package/.dist/_index.d.ts +3 -0
- package/.dist/_index.js +3 -0
- package/.dist/timeout.d.ts +1 -0
- package/.dist/timeout.js +5 -0
- package/package.json +4 -4
- package/src/Cache.ts +27 -0
- package/src/Dir.test.ts +10 -4
- package/src/Dir.ts +6 -17
- package/src/Fetcher.ts +1 -1
- package/src/File.test.ts +66 -0
- package/src/File.ts +117 -0
- package/src/Jwt.ts +17 -26
- package/src/_index.ts +3 -0
- package/src/timeout.test.ts +11 -0
- package/src/timeout.ts +5 -0
package/.dist/Cache.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
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
|
+
get path(): string;
|
|
14
|
+
};
|
|
15
|
+
ttl: number;
|
|
16
|
+
refresh: () => T | Promise<T>;
|
|
17
|
+
constructor(key: string, ttl: number, refresh: () => T | Promise<T>);
|
|
18
|
+
read(): Promise<T>;
|
|
19
|
+
write(): Promise<T>;
|
|
20
|
+
}
|
package/.dist/Cache.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { temp } from './Dir.js';
|
|
2
|
+
const cacheDir = temp.subDir('cache');
|
|
3
|
+
export class Cache {
|
|
4
|
+
file;
|
|
5
|
+
ttl;
|
|
6
|
+
refresh;
|
|
7
|
+
constructor(key, ttl, refresh) {
|
|
8
|
+
this.file = cacheDir.file(key).json();
|
|
9
|
+
this.ttl = ttl;
|
|
10
|
+
this.refresh = refresh;
|
|
11
|
+
}
|
|
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();
|
|
17
|
+
}
|
|
18
|
+
async write() {
|
|
19
|
+
const value = await this.refresh();
|
|
20
|
+
this.file.write({ createdAt: Date.now(), value });
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/.dist/Dir.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { File } from './File.js';
|
|
1
2
|
/**
|
|
2
3
|
* Reference to a specific directory with helpful methods for resolving filepaths,
|
|
3
4
|
* sanitizing filenames, and saving files
|
|
@@ -8,7 +9,7 @@ export declare class Dir {
|
|
|
8
9
|
* @param path can be relative to workspace or absolute
|
|
9
10
|
*/
|
|
10
11
|
constructor(_path: string);
|
|
11
|
-
|
|
12
|
+
create(): void;
|
|
12
13
|
/**
|
|
13
14
|
* Create a new Dir inside the current Dir
|
|
14
15
|
* @param subPath to create in current Dir
|
|
@@ -27,14 +28,8 @@ export declare class Dir {
|
|
|
27
28
|
* const filepath = folder.resolve('file.json');
|
|
28
29
|
* // 'example/file.json'
|
|
29
30
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* Save something in this Dir
|
|
33
|
-
* @param base filename with extension. `.json` will be used if base doesn't include an ext.
|
|
34
|
-
* @param contents `string`, or `any` if it's a json file
|
|
35
|
-
* @returns the filepath of the saved file
|
|
36
|
-
*/
|
|
37
|
-
save(base: string, contents: any): string;
|
|
31
|
+
filepath(base: string): string;
|
|
32
|
+
file(base: string): File;
|
|
38
33
|
}
|
|
39
34
|
/**
|
|
40
35
|
* Extends Dir class with method to `clear()` contents
|
package/.dist/Dir.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import sanitizeFilename from 'sanitize-filename';
|
|
4
|
-
import {
|
|
4
|
+
import { File } from './File.js';
|
|
5
5
|
/**
|
|
6
6
|
* Reference to a specific directory with helpful methods for resolving filepaths,
|
|
7
7
|
* sanitizing filenames, and saving files
|
|
@@ -14,7 +14,7 @@ export class Dir {
|
|
|
14
14
|
constructor(_path) {
|
|
15
15
|
this.path = _path;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
create() {
|
|
18
18
|
fs.mkdirSync(this.path, { recursive: true });
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -39,25 +39,11 @@ export class Dir {
|
|
|
39
39
|
* const filepath = folder.resolve('file.json');
|
|
40
40
|
* // 'example/file.json'
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
filepath(base) {
|
|
43
43
|
return path.resolve(this.path, this.sanitize(base));
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* @param base filename with extension. `.json` will be used if base doesn't include an ext.
|
|
48
|
-
* @param contents `string`, or `any` if it's a json file
|
|
49
|
-
* @returns the filepath of the saved file
|
|
50
|
-
*/
|
|
51
|
-
save(base, contents) {
|
|
52
|
-
if (typeof contents !== 'string') {
|
|
53
|
-
if (!/\.json$/.test(base))
|
|
54
|
-
base += '.json';
|
|
55
|
-
contents = JSON.stringify(snapshot(contents), null, 2);
|
|
56
|
-
}
|
|
57
|
-
const filepath = this.resolve(base);
|
|
58
|
-
this.make();
|
|
59
|
-
fs.writeFileSync(filepath, contents);
|
|
60
|
-
return filepath;
|
|
45
|
+
file(base) {
|
|
46
|
+
return new File(this.filepath(base));
|
|
61
47
|
}
|
|
62
48
|
}
|
|
63
49
|
/**
|
|
@@ -69,6 +55,7 @@ export class TempDir extends Dir {
|
|
|
69
55
|
}
|
|
70
56
|
clear() {
|
|
71
57
|
fs.rmSync(this.path, { recursive: true, force: true });
|
|
58
|
+
this.create();
|
|
72
59
|
}
|
|
73
60
|
}
|
|
74
61
|
/**
|
package/.dist/Fetcher.js
CHANGED
package/.dist/File.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare class File {
|
|
2
|
+
path: string;
|
|
3
|
+
constructor(filepath: string);
|
|
4
|
+
get exists(): boolean;
|
|
5
|
+
read(): string | undefined;
|
|
6
|
+
write(contents: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* creates file if it doesn't exist, appends string or array of strings as new lines.
|
|
9
|
+
* File always ends with '\n', so contents don't need to be read before appending
|
|
10
|
+
*/
|
|
11
|
+
append(lines: string | string[]): void;
|
|
12
|
+
/**
|
|
13
|
+
* @returns lines as strings, removes trailing '\n'
|
|
14
|
+
*/
|
|
15
|
+
lines(): string[];
|
|
16
|
+
static get Adaptor(): typeof FileAdaptor;
|
|
17
|
+
json<T>(contents?: T): JsonFile<T>;
|
|
18
|
+
static get json(): typeof JsonFile;
|
|
19
|
+
ndjson<T>(contents?: T): NdjsonFile<T>;
|
|
20
|
+
static get ndjson(): typeof NdjsonFile;
|
|
21
|
+
}
|
|
22
|
+
declare class FileAdaptor<T = string> {
|
|
23
|
+
file: File;
|
|
24
|
+
constructor(filepath: string, contents?: T);
|
|
25
|
+
get exists(): boolean;
|
|
26
|
+
get path(): string;
|
|
27
|
+
}
|
|
28
|
+
declare class JsonFile<T> extends FileAdaptor {
|
|
29
|
+
constructor(filepath: string, contents?: T);
|
|
30
|
+
read(): T | undefined;
|
|
31
|
+
write(contents: T): void;
|
|
32
|
+
}
|
|
33
|
+
declare class NdjsonFile<T> extends FileAdaptor {
|
|
34
|
+
constructor(filepath: string, contents?: T);
|
|
35
|
+
append(lines: T | T[]): void;
|
|
36
|
+
lines(): T[];
|
|
37
|
+
}
|
|
38
|
+
export {};
|
package/.dist/File.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { snapshot } from './snapshot.js';
|
|
4
|
+
export class File {
|
|
5
|
+
path;
|
|
6
|
+
constructor(filepath) {
|
|
7
|
+
this.path = filepath;
|
|
8
|
+
}
|
|
9
|
+
get exists() {
|
|
10
|
+
return fs.existsSync(this.path);
|
|
11
|
+
}
|
|
12
|
+
read() {
|
|
13
|
+
return this.exists ? fs.readFileSync(this.path, 'utf8') : undefined;
|
|
14
|
+
}
|
|
15
|
+
write(contents) {
|
|
16
|
+
fs.mkdirSync(path.parse(this.path).dir, { recursive: true });
|
|
17
|
+
fs.writeFileSync(this.path, contents);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* creates file if it doesn't exist, appends string or array of strings as new lines.
|
|
21
|
+
* File always ends with '\n', so contents don't need to be read before appending
|
|
22
|
+
*/
|
|
23
|
+
append(lines) {
|
|
24
|
+
if (!this.exists)
|
|
25
|
+
this.write('');
|
|
26
|
+
const contents = Array.isArray(lines) ? lines.join('\n') : lines;
|
|
27
|
+
fs.appendFileSync(this.path, contents + '\n');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @returns lines as strings, removes trailing '\n'
|
|
31
|
+
*/
|
|
32
|
+
lines() {
|
|
33
|
+
const contents = (this.read() || '').split('\n');
|
|
34
|
+
return contents.slice(0, contents.length - 1);
|
|
35
|
+
}
|
|
36
|
+
static get Adaptor() {
|
|
37
|
+
return FileAdaptor;
|
|
38
|
+
}
|
|
39
|
+
json(contents) {
|
|
40
|
+
return new JsonFile(this.path, contents);
|
|
41
|
+
}
|
|
42
|
+
static get json() {
|
|
43
|
+
return JsonFile;
|
|
44
|
+
}
|
|
45
|
+
ndjson(contents) {
|
|
46
|
+
return new NdjsonFile(this.path, contents);
|
|
47
|
+
}
|
|
48
|
+
static get ndjson() {
|
|
49
|
+
return NdjsonFile;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
class FileAdaptor {
|
|
53
|
+
file;
|
|
54
|
+
constructor(filepath, contents) {
|
|
55
|
+
this.file = new File(filepath);
|
|
56
|
+
if (contents) {
|
|
57
|
+
if (typeof contents !== 'string') {
|
|
58
|
+
throw new Error('File contents must be a string');
|
|
59
|
+
}
|
|
60
|
+
this.file.write(contents);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
get exists() {
|
|
64
|
+
return this.file.exists;
|
|
65
|
+
}
|
|
66
|
+
get path() {
|
|
67
|
+
return this.file.path;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
class JsonFile extends FileAdaptor {
|
|
71
|
+
constructor(filepath, contents) {
|
|
72
|
+
super(filepath.endsWith('.json') ? filepath : filepath + '.json');
|
|
73
|
+
if (contents)
|
|
74
|
+
this.write(contents);
|
|
75
|
+
}
|
|
76
|
+
read() {
|
|
77
|
+
const contents = this.file.read();
|
|
78
|
+
return contents ? JSON.parse(contents) : undefined;
|
|
79
|
+
}
|
|
80
|
+
write(contents) {
|
|
81
|
+
this.file.write(JSON.stringify(snapshot(contents), null, 2));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
class NdjsonFile extends FileAdaptor {
|
|
85
|
+
constructor(filepath, contents) {
|
|
86
|
+
super(filepath.endsWith('.ndjson') ? filepath : filepath + '.ndjson');
|
|
87
|
+
if (contents)
|
|
88
|
+
this.append(contents);
|
|
89
|
+
}
|
|
90
|
+
append(lines) {
|
|
91
|
+
this.file.append(Array.isArray(lines) ? lines.map((l) => JSON.stringify(snapshot(l))) : JSON.stringify(snapshot(lines)));
|
|
92
|
+
}
|
|
93
|
+
lines() {
|
|
94
|
+
return this.file.lines().map((l) => JSON.parse(l));
|
|
95
|
+
}
|
|
96
|
+
}
|
package/.dist/Jwt.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { type JwtPayload, type SignOptions } from 'jsonwebtoken';
|
|
2
|
-
|
|
3
|
-
#private;
|
|
2
|
+
type JwtConfig = {
|
|
4
3
|
payload: JwtPayload;
|
|
5
4
|
options: SignOptions;
|
|
6
5
|
seconds: number;
|
|
7
6
|
key: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
7
|
+
};
|
|
8
|
+
export declare class Jwt {
|
|
9
|
+
#private;
|
|
10
|
+
config: JwtConfig;
|
|
11
|
+
constructor(config: JwtConfig);
|
|
14
12
|
get now(): number;
|
|
15
13
|
get token(): string;
|
|
16
14
|
}
|
|
15
|
+
export {};
|
package/.dist/Jwt.js
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
import { default as jsonwebtoken } from 'jsonwebtoken';
|
|
2
2
|
import { merge } from 'lodash-es';
|
|
3
3
|
export class Jwt {
|
|
4
|
-
|
|
5
|
-
options;
|
|
6
|
-
seconds;
|
|
7
|
-
key;
|
|
4
|
+
config;
|
|
8
5
|
#saved;
|
|
9
|
-
constructor(
|
|
10
|
-
this.
|
|
11
|
-
this.options = options;
|
|
12
|
-
this.seconds = seconds;
|
|
13
|
-
this.key = key;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
14
8
|
this.#createToken();
|
|
15
9
|
}
|
|
16
10
|
get now() {
|
|
17
11
|
return Math.floor(Date.now() / 1000);
|
|
18
12
|
}
|
|
19
|
-
get token() {
|
|
20
|
-
if (this.#saved && this.#saved.exp > this.now) {
|
|
21
|
-
return this.#saved.token;
|
|
22
|
-
}
|
|
23
|
-
return this.#createToken();
|
|
24
|
-
}
|
|
25
13
|
#createToken() {
|
|
26
|
-
const exp = this.now + this.seconds;
|
|
14
|
+
const exp = this.now + this.config.seconds;
|
|
27
15
|
const payload = merge({
|
|
28
16
|
iat: this.now,
|
|
29
17
|
exp,
|
|
30
|
-
}, this.payload);
|
|
31
|
-
const token = jsonwebtoken.sign(payload, this.key, this.options);
|
|
18
|
+
}, this.config.payload);
|
|
19
|
+
const token = jsonwebtoken.sign(payload, this.config.key, this.config.options);
|
|
32
20
|
this.#saved = { token, exp };
|
|
33
21
|
return token;
|
|
34
22
|
}
|
|
23
|
+
get token() {
|
|
24
|
+
if (this.#saved && this.#saved.exp > this.now) {
|
|
25
|
+
return this.#saved.token;
|
|
26
|
+
}
|
|
27
|
+
return this.#createToken();
|
|
28
|
+
}
|
|
35
29
|
}
|
package/.dist/_index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { Dir, TempDir, temp } from './Dir.js';
|
|
2
|
+
export { Cache } from './Cache.js';
|
|
2
3
|
export { Fetcher, type Route, Query, FetchOptions } from './Fetcher.js';
|
|
4
|
+
export { File } from './File.js';
|
|
3
5
|
export { Jwt } from './Jwt.js';
|
|
4
6
|
export { Log } from './Log.js';
|
|
5
7
|
export { snapshot } from './snapshot.js';
|
|
8
|
+
export { timeout } from './timeout.js';
|
|
6
9
|
export { TypeWriter } from './TypeWriter.js';
|
package/.dist/_index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { Dir, TempDir, temp } from './Dir.js';
|
|
2
|
+
export { Cache } from './Cache.js';
|
|
2
3
|
export { Fetcher } from './Fetcher.js';
|
|
4
|
+
export { File } from './File.js';
|
|
3
5
|
export { Jwt } from './Jwt.js';
|
|
4
6
|
export { Log } from './Log.js';
|
|
5
7
|
export { snapshot } from './snapshot.js';
|
|
8
|
+
export { timeout } from './timeout.js';
|
|
6
9
|
export { TypeWriter } from './TypeWriter.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function timeout(ms: number): Promise<unknown>;
|
package/.dist/timeout.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brianbuie/node-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,9 +25,6 @@
|
|
|
25
25
|
"node": ">=24"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@types/jsonwebtoken": "^9.0.10",
|
|
29
|
-
"@types/lodash-es": "^4.17.12",
|
|
30
|
-
"@types/node": "^24.9.1",
|
|
31
28
|
"chalk": "^5.6.2",
|
|
32
29
|
"extract-domain": "^5.0.2",
|
|
33
30
|
"jsonwebtoken": "^9.0.2",
|
|
@@ -36,6 +33,9 @@
|
|
|
36
33
|
"sanitize-filename": "^1.6.3"
|
|
37
34
|
},
|
|
38
35
|
"devDependencies": {
|
|
36
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
37
|
+
"@types/lodash-es": "^4.17.12",
|
|
38
|
+
"@types/node": "^24.9.1",
|
|
39
39
|
"typescript": "^5.9.3"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/Cache.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { temp } from './Dir.js';
|
|
2
|
+
|
|
3
|
+
const cacheDir = temp.subDir('cache');
|
|
4
|
+
|
|
5
|
+
export class Cache<T> {
|
|
6
|
+
file;
|
|
7
|
+
ttl;
|
|
8
|
+
refresh;
|
|
9
|
+
|
|
10
|
+
constructor(key: string, ttl: number, refresh: () => T | Promise<T>) {
|
|
11
|
+
this.file = cacheDir.file(key).json<{ createdAt: number; value: T }>();
|
|
12
|
+
this.ttl = ttl;
|
|
13
|
+
this.refresh = refresh;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async read() {
|
|
17
|
+
const { createdAt, value } = this.file.read() || {};
|
|
18
|
+
if (value && createdAt && createdAt + this.ttl > Date.now()) return value;
|
|
19
|
+
return this.write();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async write() {
|
|
23
|
+
const value = await this.refresh();
|
|
24
|
+
this.file.write({ createdAt: Date.now(), value });
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/Dir.test.ts
CHANGED
|
@@ -3,18 +3,18 @@ import assert from 'node:assert';
|
|
|
3
3
|
import { Dir, TempDir, temp } from './Dir.js';
|
|
4
4
|
|
|
5
5
|
describe('Dir', () => {
|
|
6
|
-
const
|
|
6
|
+
const testDir = temp.subDir('dir-test');
|
|
7
7
|
|
|
8
8
|
it('Sanitizes filenames', () => {
|
|
9
|
-
const name =
|
|
9
|
+
const name = testDir.sanitize(':/something/else.json');
|
|
10
10
|
assert(!name.includes('/'));
|
|
11
11
|
assert(!name.includes(':'));
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('Creates sub directories', () => {
|
|
15
15
|
const subPath = 'sub/dir';
|
|
16
|
-
const sub =
|
|
17
|
-
assert(sub.path.includes(
|
|
16
|
+
const sub = testDir.subDir(subPath);
|
|
17
|
+
assert(sub.path.includes(testDir.path));
|
|
18
18
|
assert(sub.path.includes(subPath));
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -22,4 +22,10 @@ describe('Dir', () => {
|
|
|
22
22
|
const sub = temp.subDir('example');
|
|
23
23
|
assert(sub instanceof TempDir);
|
|
24
24
|
});
|
|
25
|
+
|
|
26
|
+
it('Resolves filenames in folder', () => {
|
|
27
|
+
const txt = temp.filepath('test.txt');
|
|
28
|
+
assert(txt.includes(temp.path));
|
|
29
|
+
assert(txt.includes('test.txt'));
|
|
30
|
+
});
|
|
25
31
|
});
|
package/src/Dir.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import sanitizeFilename from 'sanitize-filename';
|
|
4
|
+
import { File } from './File.js';
|
|
4
5
|
import { snapshot } from './snapshot.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -17,7 +18,7 @@ export class Dir {
|
|
|
17
18
|
this.path = _path;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
create() {
|
|
21
22
|
fs.mkdirSync(this.path, { recursive: true });
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -45,25 +46,12 @@ export class Dir {
|
|
|
45
46
|
* const filepath = folder.resolve('file.json');
|
|
46
47
|
* // 'example/file.json'
|
|
47
48
|
*/
|
|
48
|
-
|
|
49
|
+
filepath(base: string) {
|
|
49
50
|
return path.resolve(this.path, this.sanitize(base));
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* @param base filename with extension. `.json` will be used if base doesn't include an ext.
|
|
55
|
-
* @param contents `string`, or `any` if it's a json file
|
|
56
|
-
* @returns the filepath of the saved file
|
|
57
|
-
*/
|
|
58
|
-
save(base: string, contents: any) {
|
|
59
|
-
if (typeof contents !== 'string') {
|
|
60
|
-
if (!/\.json$/.test(base)) base += '.json';
|
|
61
|
-
contents = JSON.stringify(snapshot(contents), null, 2);
|
|
62
|
-
}
|
|
63
|
-
const filepath = this.resolve(base);
|
|
64
|
-
this.make();
|
|
65
|
-
fs.writeFileSync(filepath, contents);
|
|
66
|
-
return filepath;
|
|
53
|
+
file(base: string) {
|
|
54
|
+
return new File(this.filepath(base));
|
|
67
55
|
}
|
|
68
56
|
}
|
|
69
57
|
|
|
@@ -77,6 +65,7 @@ export class TempDir extends Dir {
|
|
|
77
65
|
|
|
78
66
|
clear() {
|
|
79
67
|
fs.rmSync(this.path, { recursive: true, force: true });
|
|
68
|
+
this.create();
|
|
80
69
|
}
|
|
81
70
|
}
|
|
82
71
|
|
package/src/Fetcher.ts
CHANGED
package/src/File.test.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { isEqual } from 'lodash-es';
|
|
4
|
+
import { temp } from './Dir.js';
|
|
5
|
+
import { File } from './File.js';
|
|
6
|
+
|
|
7
|
+
const testDir = temp.subDir('file-test');
|
|
8
|
+
testDir.clear();
|
|
9
|
+
|
|
10
|
+
const thing = {
|
|
11
|
+
a: 'string',
|
|
12
|
+
b: 2,
|
|
13
|
+
c: true,
|
|
14
|
+
d: null,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('FileAdaptor', () => {
|
|
18
|
+
it('Instances can be created', () => {
|
|
19
|
+
const test1 = new File.Adaptor(testDir.filepath('test1.txt'));
|
|
20
|
+
assert(test1.file.path.includes('test1.txt'));
|
|
21
|
+
|
|
22
|
+
const base = 'test2';
|
|
23
|
+
const eg1 = new File.json(testDir.filepath(base));
|
|
24
|
+
const eg2 = testDir.file(base).json();
|
|
25
|
+
const eg3File = new File(testDir.filepath(base));
|
|
26
|
+
const eg3 = eg3File.json();
|
|
27
|
+
assert(eg1.path === eg2.path && eg2.path === eg3.path);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('File.ndjson', () => {
|
|
32
|
+
it('Appends new lines correctly', () => {
|
|
33
|
+
const file = testDir.file('empty-lines').ndjson();
|
|
34
|
+
file.append([thing, thing]);
|
|
35
|
+
assert(file.lines().length === 2);
|
|
36
|
+
file.append(thing);
|
|
37
|
+
assert(file.lines().length === 3);
|
|
38
|
+
file.lines().forEach((line) => {
|
|
39
|
+
assert(isEqual(line, thing));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('Adds file extension when needed', () => {
|
|
44
|
+
const test = testDir.file('test').ndjson();
|
|
45
|
+
assert(test.path.includes(testDir.path));
|
|
46
|
+
assert(test.path.includes('.ndjson'));
|
|
47
|
+
const test2 = testDir.file('test2').ndjson();
|
|
48
|
+
assert(!test2.path.includes('.ndjson.ndjson'));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('File.json', () => {
|
|
53
|
+
it('Saves data as json', () => {
|
|
54
|
+
const file = testDir.file('jsonfile-data').json(thing);
|
|
55
|
+
assert(isEqual(file.read(), thing));
|
|
56
|
+
file.write(thing);
|
|
57
|
+
assert(isEqual(file.read(), thing));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('Does not create file when reading', () => {
|
|
61
|
+
const file = testDir.file('test123').json();
|
|
62
|
+
const contents = file.read();
|
|
63
|
+
assert(contents === undefined);
|
|
64
|
+
assert(!file.exists);
|
|
65
|
+
});
|
|
66
|
+
});
|
package/src/File.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { snapshot } from './snapshot.js';
|
|
4
|
+
|
|
5
|
+
export class File {
|
|
6
|
+
path;
|
|
7
|
+
|
|
8
|
+
constructor(filepath: string) {
|
|
9
|
+
this.path = filepath;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get exists() {
|
|
13
|
+
return fs.existsSync(this.path);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
read() {
|
|
17
|
+
return this.exists ? fs.readFileSync(this.path, 'utf8') : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
write(contents: string) {
|
|
21
|
+
fs.mkdirSync(path.parse(this.path).dir, { recursive: true });
|
|
22
|
+
fs.writeFileSync(this.path, contents);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* creates file if it doesn't exist, appends string or array of strings as new lines.
|
|
27
|
+
* File always ends with '\n', so contents don't need to be read before appending
|
|
28
|
+
*/
|
|
29
|
+
append(lines: string | string[]) {
|
|
30
|
+
if (!this.exists) this.write('');
|
|
31
|
+
const contents = Array.isArray(lines) ? lines.join('\n') : lines;
|
|
32
|
+
fs.appendFileSync(this.path, contents + '\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @returns lines as strings, removes trailing '\n'
|
|
37
|
+
*/
|
|
38
|
+
lines() {
|
|
39
|
+
const contents = (this.read() || '').split('\n');
|
|
40
|
+
return contents.slice(0, contents.length - 1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static get Adaptor() {
|
|
44
|
+
return FileAdaptor;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
json<T>(contents?: T) {
|
|
48
|
+
return new JsonFile<T>(this.path, contents);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static get json() {
|
|
52
|
+
return JsonFile;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ndjson<T>(contents?: T) {
|
|
56
|
+
return new NdjsonFile<T>(this.path, contents);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static get ndjson() {
|
|
60
|
+
return NdjsonFile;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class FileAdaptor<T = string> {
|
|
65
|
+
file;
|
|
66
|
+
|
|
67
|
+
constructor(filepath: string, contents?: T) {
|
|
68
|
+
this.file = new File(filepath);
|
|
69
|
+
if (contents) {
|
|
70
|
+
if (typeof contents !== 'string') {
|
|
71
|
+
throw new Error('File contents must be a string');
|
|
72
|
+
}
|
|
73
|
+
this.file.write(contents);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get exists() {
|
|
78
|
+
return this.file.exists;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get path() {
|
|
82
|
+
return this.file.path;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class JsonFile<T> extends FileAdaptor {
|
|
87
|
+
constructor(filepath: string, contents?: T) {
|
|
88
|
+
super(filepath.endsWith('.json') ? filepath : filepath + '.json');
|
|
89
|
+
if (contents) this.write(contents);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
read() {
|
|
93
|
+
const contents = this.file.read();
|
|
94
|
+
return contents ? (JSON.parse(contents) as T) : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
write(contents: T) {
|
|
98
|
+
this.file.write(JSON.stringify(snapshot(contents), null, 2));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class NdjsonFile<T> extends FileAdaptor {
|
|
103
|
+
constructor(filepath: string, contents?: T) {
|
|
104
|
+
super(filepath.endsWith('.ndjson') ? filepath : filepath + '.ndjson');
|
|
105
|
+
if (contents) this.append(contents);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
append(lines: T | T[]) {
|
|
109
|
+
this.file.append(
|
|
110
|
+
Array.isArray(lines) ? lines.map((l) => JSON.stringify(snapshot(l))) : JSON.stringify(snapshot(lines))
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lines() {
|
|
115
|
+
return this.file.lines().map((l) => JSON.parse(l) as T);
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/Jwt.ts
CHANGED
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
import { default as jsonwebtoken, type JwtPayload, type SignOptions } from 'jsonwebtoken';
|
|
2
2
|
import { merge } from 'lodash-es';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
type JwtConfig = {
|
|
5
5
|
payload: JwtPayload;
|
|
6
6
|
options: SignOptions;
|
|
7
7
|
seconds: number;
|
|
8
8
|
key: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class Jwt {
|
|
12
|
+
config;
|
|
9
13
|
#saved?: {
|
|
10
14
|
exp: number;
|
|
11
15
|
token: string;
|
|
12
16
|
};
|
|
13
17
|
|
|
14
|
-
constructor({
|
|
15
|
-
|
|
16
|
-
options,
|
|
17
|
-
seconds,
|
|
18
|
-
key,
|
|
19
|
-
}: {
|
|
20
|
-
payload: JwtPayload;
|
|
21
|
-
options: SignOptions;
|
|
22
|
-
seconds: number;
|
|
23
|
-
key: string;
|
|
24
|
-
}) {
|
|
25
|
-
this.payload = payload;
|
|
26
|
-
this.options = options;
|
|
27
|
-
this.seconds = seconds;
|
|
28
|
-
this.key = key;
|
|
18
|
+
constructor(config: JwtConfig) {
|
|
19
|
+
this.config = config;
|
|
29
20
|
this.#createToken();
|
|
30
21
|
}
|
|
31
22
|
|
|
@@ -33,24 +24,24 @@ export class Jwt {
|
|
|
33
24
|
return Math.floor(Date.now() / 1000);
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
get token() {
|
|
37
|
-
if (this.#saved && this.#saved.exp > this.now) {
|
|
38
|
-
return this.#saved.token;
|
|
39
|
-
}
|
|
40
|
-
return this.#createToken();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
27
|
#createToken() {
|
|
44
|
-
const exp = this.now + this.seconds;
|
|
28
|
+
const exp = this.now + this.config.seconds;
|
|
45
29
|
const payload: JwtPayload = merge(
|
|
46
30
|
{
|
|
47
31
|
iat: this.now,
|
|
48
32
|
exp,
|
|
49
33
|
},
|
|
50
|
-
this.payload
|
|
34
|
+
this.config.payload
|
|
51
35
|
);
|
|
52
|
-
const token = jsonwebtoken.sign(payload, this.key, this.options);
|
|
36
|
+
const token = jsonwebtoken.sign(payload, this.config.key, this.config.options);
|
|
53
37
|
this.#saved = { token, exp };
|
|
54
38
|
return token;
|
|
55
39
|
}
|
|
40
|
+
|
|
41
|
+
get token() {
|
|
42
|
+
if (this.#saved && this.#saved.exp > this.now) {
|
|
43
|
+
return this.#saved.token;
|
|
44
|
+
}
|
|
45
|
+
return this.#createToken();
|
|
46
|
+
}
|
|
56
47
|
}
|
package/src/_index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { Dir, TempDir, temp } from './Dir.js';
|
|
2
|
+
export { Cache } from './Cache.js';
|
|
2
3
|
export { Fetcher, type Route, Query, FetchOptions } from './Fetcher.js';
|
|
4
|
+
export { File } from './File.js';
|
|
3
5
|
export { Jwt } from './Jwt.js';
|
|
4
6
|
export { Log } from './Log.js';
|
|
5
7
|
export { snapshot } from './snapshot.js';
|
|
8
|
+
export { timeout } from './timeout.js';
|
|
6
9
|
export { TypeWriter } from './TypeWriter.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { timeout } from './timeout.js';
|
|
4
|
+
|
|
5
|
+
describe('timeout', () => {
|
|
6
|
+
it('Waits correct amount of time', async () => {
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
await timeout(500);
|
|
9
|
+
assert(Date.now() - start >= 500);
|
|
10
|
+
});
|
|
11
|
+
});
|