@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.
@@ -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
- make(): void;
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
- resolve(base: string): string;
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 { snapshot } from './snapshot.js';
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
- make() {
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
- resolve(base) {
42
+ filepath(base) {
43
43
  return path.resolve(this.path, this.sanitize(base));
44
44
  }
45
- /**
46
- * Save something in this Dir
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
@@ -10,7 +10,7 @@ export class Fetcher {
10
10
  constructor(opts = {}) {
11
11
  const defaultOptions = {
12
12
  timeout: 60000,
13
- retries: 3,
13
+ retries: 0,
14
14
  retryDelay: 3000,
15
15
  };
16
16
  this.defaultOptions = merge(defaultOptions, opts);
@@ -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
- export declare class Jwt {
3
- #private;
2
+ type JwtConfig = {
4
3
  payload: JwtPayload;
5
4
  options: SignOptions;
6
5
  seconds: number;
7
6
  key: string;
8
- constructor({ payload, options, seconds, key, }: {
9
- payload: JwtPayload;
10
- options: SignOptions;
11
- seconds: number;
12
- key: string;
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
- payload;
5
- options;
6
- seconds;
7
- key;
4
+ config;
8
5
  #saved;
9
- constructor({ payload, options, seconds, key, }) {
10
- this.payload = payload;
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>;
@@ -0,0 +1,5 @@
1
+ export async function timeout(ms) {
2
+ return new Promise((resolve) => {
3
+ setTimeout(resolve, ms);
4
+ });
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brianbuie/node-kit",
3
- "version": "0.2.5",
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 test = new Dir('test');
6
+ const testDir = temp.subDir('dir-test');
7
7
 
8
8
  it('Sanitizes filenames', () => {
9
- const name = test.sanitize(':/something/else.json');
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 = test.subDir(subPath);
17
- assert(sub.path.includes(test.path));
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
- make() {
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
- resolve(base: string) {
49
+ filepath(base: string) {
49
50
  return path.resolve(this.path, this.sanitize(base));
50
51
  }
51
52
 
52
- /**
53
- * Save something in this Dir
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
@@ -27,7 +27,7 @@ export class Fetcher {
27
27
  constructor(opts: FetchOptions = {}) {
28
28
  const defaultOptions = {
29
29
  timeout: 60000,
30
- retries: 3,
30
+ retries: 0,
31
31
  retryDelay: 3000,
32
32
  };
33
33
  this.defaultOptions = merge(defaultOptions, opts);
@@ -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
- export class Jwt {
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
- payload,
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
+ });
package/src/timeout.ts ADDED
@@ -0,0 +1,5 @@
1
+ export async function timeout(ms: number) {
2
+ return new Promise((resolve) => {
3
+ setTimeout(resolve, ms);
4
+ });
5
+ }