utilium 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/misc.d.ts +3 -0
- package/dist/misc.js +33 -0
- package/dist/numbers.d.ts +3 -0
- package/dist/numbers.js +13 -0
- package/dist/objects.d.ts +81 -0
- package/dist/objects.js +178 -0
- package/dist/random.d.ts +5 -0
- package/dist/random.js +23 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.js +1 -0
- package/package.json +37 -0
- package/readme.md +3 -0
- package/src/index.ts +5 -0
- package/src/misc.ts +36 -0
- package/src/numbers.ts +15 -0
- package/src/objects.ts +253 -0
- package/src/random.ts +27 -0
- package/src/types.ts +76 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/misc.d.ts
ADDED
package/dist/misc.js
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
export function wait(time) {
|
2
|
+
return new Promise(resolve => setTimeout(resolve, time));
|
3
|
+
}
|
4
|
+
export const greekLetterNames = [
|
5
|
+
'Alpha',
|
6
|
+
'Beta',
|
7
|
+
'Gamma',
|
8
|
+
'Delta',
|
9
|
+
'Epsilon',
|
10
|
+
'Zeta',
|
11
|
+
'Eta',
|
12
|
+
'Theta',
|
13
|
+
'Iota',
|
14
|
+
'Kappa',
|
15
|
+
'Lambda',
|
16
|
+
'Mu',
|
17
|
+
'Nu',
|
18
|
+
'Xi',
|
19
|
+
'Omicron',
|
20
|
+
'Pi',
|
21
|
+
'Rho',
|
22
|
+
'Sigma',
|
23
|
+
'Tau',
|
24
|
+
'Upsilon',
|
25
|
+
'Phi',
|
26
|
+
'Chi',
|
27
|
+
'Psi',
|
28
|
+
'Omega',
|
29
|
+
];
|
30
|
+
const hexRegex = /^[0-9a-f-.]+$/;
|
31
|
+
export function isHex(str) {
|
32
|
+
return hexRegex.test(str);
|
33
|
+
}
|
package/dist/numbers.js
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
export function range(min, max) {
|
2
|
+
const a = [];
|
3
|
+
for (let i = min; i < max; i++) {
|
4
|
+
a.push(i);
|
5
|
+
}
|
6
|
+
return a;
|
7
|
+
}
|
8
|
+
export function toDegrees(radians) {
|
9
|
+
return (radians * 180) / Math.PI;
|
10
|
+
}
|
11
|
+
export function toRadians(degrees) {
|
12
|
+
return (degrees / 180) * Math.PI;
|
13
|
+
}
|
@@ -0,0 +1,81 @@
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
2
|
+
export declare function filterObject<T extends object, K extends keyof T>(object: T, ...keys: K[]): Omit<T, K>;
|
3
|
+
import type * as FS from 'fs';
|
4
|
+
export declare function isJSON(str: string): boolean;
|
5
|
+
export declare abstract class FileMap<V> implements Map<string, V> {
|
6
|
+
protected readonly path: string;
|
7
|
+
protected fs: typeof FS;
|
8
|
+
get [Symbol.toStringTag](): string;
|
9
|
+
constructor(path: string, fs?: typeof FS);
|
10
|
+
protected abstract readonly _map: Map<string, V>;
|
11
|
+
abstract clear(): void;
|
12
|
+
abstract delete(key: string): boolean;
|
13
|
+
abstract get(key: string): V;
|
14
|
+
abstract has(key: string): boolean;
|
15
|
+
abstract set(key: string, value: V): this;
|
16
|
+
get size(): number;
|
17
|
+
get [Symbol.iterator](): any;
|
18
|
+
get keys(): typeof this._map.keys;
|
19
|
+
get values(): typeof this._map.values;
|
20
|
+
get entries(): typeof this._map.entries;
|
21
|
+
get forEach(): typeof this._map.forEach;
|
22
|
+
}
|
23
|
+
export type JSONObject<Key extends string | number | symbol = string> = {
|
24
|
+
[K in Key]: JSONValue;
|
25
|
+
};
|
26
|
+
export type JSONValue<Key extends string | number | symbol = string> = string | number | boolean | JSONObject<Key> | Array<JSONValue>;
|
27
|
+
export interface JSONFileMapOptions {
|
28
|
+
/**
|
29
|
+
* Should an invalid JSON file be overwritten
|
30
|
+
*/
|
31
|
+
overwrite_invalid_json: boolean;
|
32
|
+
/**
|
33
|
+
*
|
34
|
+
*/
|
35
|
+
fs: typeof FS;
|
36
|
+
}
|
37
|
+
/**
|
38
|
+
* A Map overlaying a JSON file
|
39
|
+
*/
|
40
|
+
export declare class JSONFileMap<T extends JSONValue = JSONValue> extends FileMap<T> {
|
41
|
+
readonly options: JSONFileMapOptions;
|
42
|
+
get [Symbol.toStringTag](): 'JSONFileMap';
|
43
|
+
constructor(path: string, options: JSONFileMapOptions);
|
44
|
+
get _map(): Map<string, T>;
|
45
|
+
_write(map: Map<string, T>): void;
|
46
|
+
clear(): void;
|
47
|
+
delete(key: string): boolean;
|
48
|
+
get<U extends T>(key: string): U;
|
49
|
+
has(key: string): boolean;
|
50
|
+
set(key: string, value: T): this;
|
51
|
+
}
|
52
|
+
export interface FolderMapOptions {
|
53
|
+
/**
|
54
|
+
* Suffix to append to keys to resolve file names
|
55
|
+
*/
|
56
|
+
suffix: string;
|
57
|
+
fs?: typeof FS;
|
58
|
+
}
|
59
|
+
/**
|
60
|
+
* A Map overlaying a folder
|
61
|
+
*/
|
62
|
+
export declare class FolderMap extends FileMap<string> {
|
63
|
+
readonly options: Partial<FolderMapOptions>;
|
64
|
+
get [Symbol.toStringTag](): 'FolderMap';
|
65
|
+
constructor(path: string, options: Partial<FolderMapOptions>);
|
66
|
+
protected get _names(): string[];
|
67
|
+
protected _join(path: string): string;
|
68
|
+
protected get _map(): Map<string, string>;
|
69
|
+
clear(): void;
|
70
|
+
delete(key: string): boolean;
|
71
|
+
get(key: string): string;
|
72
|
+
has(key: string): boolean;
|
73
|
+
set(key: string, value: string): this;
|
74
|
+
}
|
75
|
+
export declare function resolveConstructors(object: object): string[];
|
76
|
+
/**
|
77
|
+
* Gets a random int, r, with the probability P(r) = (base)**r
|
78
|
+
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
79
|
+
* @param probability the probability
|
80
|
+
*/
|
81
|
+
export declare function getRandomIntWithRecursiveProbability(probability?: number): number;
|
package/dist/objects.js
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
export function filterObject(object, ...keys) {
|
2
|
+
const entries = Object.entries(object);
|
3
|
+
return Object.fromEntries(entries.filter(([key]) => keys.includes(key)));
|
4
|
+
}
|
5
|
+
let _fs;
|
6
|
+
try {
|
7
|
+
_fs = await import('fs');
|
8
|
+
}
|
9
|
+
catch (e) {
|
10
|
+
_fs = null;
|
11
|
+
}
|
12
|
+
export function isJSON(str) {
|
13
|
+
try {
|
14
|
+
JSON.parse(str);
|
15
|
+
return true;
|
16
|
+
}
|
17
|
+
catch (e) {
|
18
|
+
return false;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
export class FileMap {
|
22
|
+
get [Symbol.toStringTag]() {
|
23
|
+
return 'FileMap';
|
24
|
+
}
|
25
|
+
constructor(path, fs = _fs) {
|
26
|
+
this.path = path;
|
27
|
+
this.fs = fs;
|
28
|
+
if (!path) {
|
29
|
+
throw new ReferenceError('No path specified');
|
30
|
+
}
|
31
|
+
if (!fs) {
|
32
|
+
throw new ReferenceError('No filesystem API');
|
33
|
+
}
|
34
|
+
}
|
35
|
+
get size() {
|
36
|
+
return this._map.size;
|
37
|
+
}
|
38
|
+
get [Symbol.iterator]() {
|
39
|
+
return this._map[Symbol.iterator].bind(this._map);
|
40
|
+
}
|
41
|
+
get keys() {
|
42
|
+
return this._map.keys.bind(this._map);
|
43
|
+
}
|
44
|
+
get values() {
|
45
|
+
return this._map.values.bind(this._map);
|
46
|
+
}
|
47
|
+
get entries() {
|
48
|
+
return this._map.entries.bind(this._map);
|
49
|
+
}
|
50
|
+
get forEach() {
|
51
|
+
return this._map.forEach.bind(this._map);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
/**
|
55
|
+
* A Map overlaying a JSON file
|
56
|
+
*/
|
57
|
+
export class JSONFileMap extends FileMap {
|
58
|
+
get [Symbol.toStringTag]() {
|
59
|
+
return 'JSONFileMap';
|
60
|
+
}
|
61
|
+
constructor(path, options) {
|
62
|
+
super(path, options.fs);
|
63
|
+
this.options = options;
|
64
|
+
if (!this.fs.existsSync(path)) {
|
65
|
+
this.fs.writeFileSync(path, '{}');
|
66
|
+
}
|
67
|
+
}
|
68
|
+
get _map() {
|
69
|
+
const content = this.fs.readFileSync(this.path, 'utf8');
|
70
|
+
if (!isJSON(content)) {
|
71
|
+
if (!this.options.overwrite_invalid_json) {
|
72
|
+
throw new SyntaxError('Invalid JSON file: ' + this.path);
|
73
|
+
}
|
74
|
+
console.warn('Invalid JSON file (overwriting): ' + this.path);
|
75
|
+
this.clear();
|
76
|
+
return new Map();
|
77
|
+
}
|
78
|
+
return new Map(Object.entries(JSON.parse(content)));
|
79
|
+
}
|
80
|
+
_write(map) {
|
81
|
+
if (!this.fs.existsSync(this.path)) {
|
82
|
+
this.fs.writeFileSync(this.path, '{}');
|
83
|
+
}
|
84
|
+
const content = JSON.stringify(Object.fromEntries(map));
|
85
|
+
this.fs.writeFileSync(this.path, content);
|
86
|
+
}
|
87
|
+
clear() {
|
88
|
+
this.fs.writeFileSync(this.path, '{}');
|
89
|
+
}
|
90
|
+
delete(key) {
|
91
|
+
const map = this._map;
|
92
|
+
const rt = map.delete(key);
|
93
|
+
this._write(map);
|
94
|
+
return rt;
|
95
|
+
}
|
96
|
+
get(key) {
|
97
|
+
return this._map.get(key);
|
98
|
+
}
|
99
|
+
has(key) {
|
100
|
+
return this._map.has(key);
|
101
|
+
}
|
102
|
+
set(key, value) {
|
103
|
+
const map = this._map;
|
104
|
+
map.set(key, value);
|
105
|
+
this._write(map);
|
106
|
+
return this;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
/**
|
110
|
+
* A Map overlaying a folder
|
111
|
+
*/
|
112
|
+
export class FolderMap extends FileMap {
|
113
|
+
get [Symbol.toStringTag]() {
|
114
|
+
return 'FolderMap';
|
115
|
+
}
|
116
|
+
constructor(path, options) {
|
117
|
+
super(path, options.fs);
|
118
|
+
this.options = options;
|
119
|
+
}
|
120
|
+
get _names() {
|
121
|
+
return this.fs
|
122
|
+
.readdirSync(this.path)
|
123
|
+
.filter(p => p.endsWith(this.options.suffix))
|
124
|
+
.map(p => p.slice(0, -this.options.suffix.length));
|
125
|
+
}
|
126
|
+
_join(path) {
|
127
|
+
return `${this.path}/${path}${this.options.suffix}`;
|
128
|
+
}
|
129
|
+
get _map() {
|
130
|
+
const entries = [];
|
131
|
+
for (const name of this._names) {
|
132
|
+
const content = this.fs.readFileSync(this._join(name), 'utf8');
|
133
|
+
entries.push([name, content]);
|
134
|
+
}
|
135
|
+
return new Map(entries);
|
136
|
+
}
|
137
|
+
clear() {
|
138
|
+
for (const name of this._names) {
|
139
|
+
this.fs.unlinkSync(this._join(name));
|
140
|
+
}
|
141
|
+
}
|
142
|
+
delete(key) {
|
143
|
+
if (!this.has(key)) {
|
144
|
+
return false;
|
145
|
+
}
|
146
|
+
this.fs.unlinkSync(this._join(key));
|
147
|
+
return true;
|
148
|
+
}
|
149
|
+
get(key) {
|
150
|
+
if (this.has(key)) {
|
151
|
+
return this.fs.readFileSync(this._join(key), 'utf8');
|
152
|
+
}
|
153
|
+
}
|
154
|
+
has(key) {
|
155
|
+
return this._names.includes(key);
|
156
|
+
}
|
157
|
+
set(key, value) {
|
158
|
+
this.fs.writeFileSync(this._join(key), value);
|
159
|
+
return this;
|
160
|
+
}
|
161
|
+
}
|
162
|
+
export function resolveConstructors(object) {
|
163
|
+
const constructors = [];
|
164
|
+
let prototype = object;
|
165
|
+
while (prototype && !['Function', 'Object'].includes(prototype.constructor.name)) {
|
166
|
+
prototype = Object.getPrototypeOf(prototype);
|
167
|
+
constructors.push(prototype.constructor.name);
|
168
|
+
}
|
169
|
+
return constructors;
|
170
|
+
}
|
171
|
+
/**
|
172
|
+
* Gets a random int, r, with the probability P(r) = (base)**r
|
173
|
+
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
174
|
+
* @param probability the probability
|
175
|
+
*/
|
176
|
+
export function getRandomIntWithRecursiveProbability(probability = 0.5) {
|
177
|
+
return -Math.floor(Math.log(Math.random()) / Math.log(1 / probability));
|
178
|
+
}
|
package/dist/random.d.ts
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
export declare function randomFloat(min?: number, max?: number): number;
|
2
|
+
export declare function randomHex(length?: number): string;
|
3
|
+
export declare function randomBoolean(): boolean;
|
4
|
+
export declare function randomBinaryString(length?: number): string;
|
5
|
+
export declare function randomInt(min?: number, max?: number): number;
|
package/dist/random.js
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
export function randomFloat(min = 0, max = 1) {
|
2
|
+
return Math.random() * (max - min) + min;
|
3
|
+
}
|
4
|
+
export function randomHex(length = 1) {
|
5
|
+
let s = '';
|
6
|
+
for (let i = 0; i < length; i++) {
|
7
|
+
s += Math.floor(Math.random() * 16).toString(16);
|
8
|
+
}
|
9
|
+
return s;
|
10
|
+
}
|
11
|
+
export function randomBoolean() {
|
12
|
+
return !!Math.round(Math.random());
|
13
|
+
}
|
14
|
+
export function randomBinaryString(length = 1) {
|
15
|
+
let b = '';
|
16
|
+
for (let i = 0; i < length; i++) {
|
17
|
+
b += Math.round(Math.random());
|
18
|
+
}
|
19
|
+
return b;
|
20
|
+
}
|
21
|
+
export function randomInt(min = 0, max = 1) {
|
22
|
+
return Math.round(Math.random() * (max - min) + min);
|
23
|
+
}
|
package/dist/types.d.ts
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
/**
|
2
|
+
* Expands the type T (for intellisense and debugging)
|
3
|
+
* @see https://stackoverflow.com/a/69288824/17637456
|
4
|
+
*/
|
5
|
+
export type Expand<T> = T extends (...args: infer A) => infer R ? (...args: Expand<A>) => Expand<R> : T extends infer O ? {
|
6
|
+
[K in keyof O]: O[K];
|
7
|
+
} : never;
|
8
|
+
/**
|
9
|
+
* Recursivly expands the type T (for intellisense and debugging)
|
10
|
+
* @see https://stackoverflow.com/a/69288824/17637456
|
11
|
+
*/
|
12
|
+
export type ExpandRecursively<T> = T extends (...args: infer A) => infer R ? (...args: ExpandRecursively<A>) => ExpandRecursively<R> : T extends object ? T extends infer O ? {
|
13
|
+
[K in keyof O]: ExpandRecursively<O[K]>;
|
14
|
+
} : never : T;
|
15
|
+
/**
|
16
|
+
* Extracts an object with properties assignable to P from an object T
|
17
|
+
* @see https://stackoverflow.com/a/71532723/17637456
|
18
|
+
*/
|
19
|
+
export type ExtractProperties<T, P> = {
|
20
|
+
[K in keyof T as T[K] extends infer Prop ? (Prop extends P ? K : never) : never]: T[K];
|
21
|
+
};
|
22
|
+
/**
|
23
|
+
* Extract the keys of T which are required
|
24
|
+
* @see https://stackoverflow.com/a/55247867/17637456
|
25
|
+
*/
|
26
|
+
export type RequiredKeys<T> = {
|
27
|
+
[K in keyof T]-?: {} extends {
|
28
|
+
[P in K]: T[K];
|
29
|
+
} ? never : K;
|
30
|
+
}[keyof T];
|
31
|
+
/**
|
32
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
33
|
+
*/
|
34
|
+
export type RequiredProperties<T extends object, K extends keyof T = keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
35
|
+
/**
|
36
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
37
|
+
*/
|
38
|
+
export type DeepRequired<T> = {
|
39
|
+
[K in keyof T]: Required<DeepRequired<T[K]>>;
|
40
|
+
};
|
41
|
+
/**
|
42
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
43
|
+
*/
|
44
|
+
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
45
|
+
/**
|
46
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
47
|
+
*/
|
48
|
+
export type NestedKeys<T extends object> = {
|
49
|
+
[P in keyof T & (string | number)]: T[P] extends Date ? `${P}` : T[P] extends Record<string, unknown> ? `${P}` | `${P}.${NestedKeys<T[P]>}` : `${P}`;
|
50
|
+
}[keyof T & (string | number)];
|
51
|
+
/**
|
52
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
53
|
+
*/
|
54
|
+
export type PartialRecursive<T> = {
|
55
|
+
[P in keyof T]?: T[P] extends (infer U)[] ? PartialRecursive<U>[] : T[P] extends object | undefined ? PartialRecursive<T[P]> : T[P];
|
56
|
+
};
|
57
|
+
/**
|
58
|
+
* Get the keys of a union of objects
|
59
|
+
* @see https://stackoverflow.com/a/65805753/17637456
|
60
|
+
*/
|
61
|
+
export type UnionKeys<T> = T extends T ? keyof T : never;
|
62
|
+
type StrictUnionHelper<T, TAll> = T extends unknown ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
|
63
|
+
/**
|
64
|
+
* @see https://stackoverflow.com/a/65805753/17637456
|
65
|
+
*/
|
66
|
+
export type StrictUnion<T> = Expand<StrictUnionHelper<T, T>>;
|
67
|
+
export {};
|
package/dist/types.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "utilium",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "Utilies",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"types": "dist/index.d.ts",
|
7
|
+
"type": "module",
|
8
|
+
"scripts": {
|
9
|
+
"format:check": "prettier --check .",
|
10
|
+
"format": "prettier --write .",
|
11
|
+
"lint": "eslint src && tsc --noEmit",
|
12
|
+
"build": "tsc -p tsconfig.json",
|
13
|
+
"build:docs": "typedoc src/index.ts --out docs",
|
14
|
+
"prepublishOnly": "npm run build"
|
15
|
+
},
|
16
|
+
"repository": {
|
17
|
+
"type": "git",
|
18
|
+
"url": "git+https://github.com/dr-vortex/utilium.git"
|
19
|
+
},
|
20
|
+
"author": "James P. <jp@drvortex.dev> (https://drvortex.dev)",
|
21
|
+
"license": "MIT",
|
22
|
+
"bugs": {
|
23
|
+
"url": "https://github.com/dr-vortex/utilium/issues"
|
24
|
+
},
|
25
|
+
"homepage": "https://github.com/dr-vortex/utilium#readme",
|
26
|
+
"devDependencies": {
|
27
|
+
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
28
|
+
"@typescript-eslint/parser": "^6.2.0",
|
29
|
+
"eslint": "^8.45.0",
|
30
|
+
"prettier": "^3.2.5",
|
31
|
+
"typedoc": "^0.24.8",
|
32
|
+
"typescript": "^5.1.6"
|
33
|
+
},
|
34
|
+
"dependencies": {
|
35
|
+
"@types/node": "^20.12.7"
|
36
|
+
}
|
37
|
+
}
|
package/readme.md
ADDED
package/src/index.ts
ADDED
package/src/misc.ts
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
export function wait(time: number) {
|
2
|
+
return new Promise(resolve => setTimeout(resolve, time));
|
3
|
+
}
|
4
|
+
|
5
|
+
export const greekLetterNames = [
|
6
|
+
'Alpha',
|
7
|
+
'Beta',
|
8
|
+
'Gamma',
|
9
|
+
'Delta',
|
10
|
+
'Epsilon',
|
11
|
+
'Zeta',
|
12
|
+
'Eta',
|
13
|
+
'Theta',
|
14
|
+
'Iota',
|
15
|
+
'Kappa',
|
16
|
+
'Lambda',
|
17
|
+
'Mu',
|
18
|
+
'Nu',
|
19
|
+
'Xi',
|
20
|
+
'Omicron',
|
21
|
+
'Pi',
|
22
|
+
'Rho',
|
23
|
+
'Sigma',
|
24
|
+
'Tau',
|
25
|
+
'Upsilon',
|
26
|
+
'Phi',
|
27
|
+
'Chi',
|
28
|
+
'Psi',
|
29
|
+
'Omega',
|
30
|
+
];
|
31
|
+
|
32
|
+
const hexRegex = /^[0-9a-f-.]+$/;
|
33
|
+
|
34
|
+
export function isHex(str: string) {
|
35
|
+
return hexRegex.test(str);
|
36
|
+
}
|
package/src/numbers.ts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
export function range(min: number, max: number): number[] {
|
2
|
+
const a = [];
|
3
|
+
for (let i = min; i < max; i++) {
|
4
|
+
a.push(i);
|
5
|
+
}
|
6
|
+
return a;
|
7
|
+
}
|
8
|
+
|
9
|
+
export function toDegrees(radians: number): number {
|
10
|
+
return (radians * 180) / Math.PI;
|
11
|
+
}
|
12
|
+
|
13
|
+
export function toRadians(degrees: number): number {
|
14
|
+
return (degrees / 180) * Math.PI;
|
15
|
+
}
|
package/src/objects.ts
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
export function filterObject<T extends object, K extends keyof T>(object: T, ...keys: K[]): Omit<T, K> {
|
2
|
+
const entries = <[K, T[K]][]>Object.entries(object);
|
3
|
+
return <Omit<T, K>>(<unknown>Object.fromEntries(entries.filter(([key]) => keys.includes(key))));
|
4
|
+
}
|
5
|
+
|
6
|
+
import type * as FS from 'fs';
|
7
|
+
|
8
|
+
let _fs: typeof FS;
|
9
|
+
try {
|
10
|
+
_fs = await import('fs');
|
11
|
+
} catch (e) {
|
12
|
+
_fs = null;
|
13
|
+
}
|
14
|
+
|
15
|
+
export function isJSON(str: string) {
|
16
|
+
try {
|
17
|
+
JSON.parse(str);
|
18
|
+
return true;
|
19
|
+
} catch (e) {
|
20
|
+
return false;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
export abstract class FileMap<V> implements Map<string, V> {
|
25
|
+
public get [Symbol.toStringTag](): string {
|
26
|
+
return 'FileMap';
|
27
|
+
}
|
28
|
+
|
29
|
+
public constructor(
|
30
|
+
protected readonly path: string,
|
31
|
+
protected fs: typeof FS = _fs
|
32
|
+
) {
|
33
|
+
if (!path) {
|
34
|
+
throw new ReferenceError('No path specified');
|
35
|
+
}
|
36
|
+
|
37
|
+
if (!fs) {
|
38
|
+
throw new ReferenceError('No filesystem API');
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
protected abstract readonly _map: Map<string, V>;
|
43
|
+
|
44
|
+
public abstract clear(): void;
|
45
|
+
|
46
|
+
public abstract delete(key: string): boolean;
|
47
|
+
|
48
|
+
public abstract get(key: string): V;
|
49
|
+
|
50
|
+
public abstract has(key: string): boolean;
|
51
|
+
|
52
|
+
public abstract set(key: string, value: V): this;
|
53
|
+
|
54
|
+
public get size() {
|
55
|
+
return this._map.size;
|
56
|
+
}
|
57
|
+
|
58
|
+
public get [Symbol.iterator]() {
|
59
|
+
return this._map[Symbol.iterator].bind(this._map);
|
60
|
+
}
|
61
|
+
|
62
|
+
public get keys(): typeof this._map.keys {
|
63
|
+
return this._map.keys.bind(this._map);
|
64
|
+
}
|
65
|
+
|
66
|
+
public get values(): typeof this._map.values {
|
67
|
+
return this._map.values.bind(this._map);
|
68
|
+
}
|
69
|
+
|
70
|
+
public get entries(): typeof this._map.entries {
|
71
|
+
return this._map.entries.bind(this._map);
|
72
|
+
}
|
73
|
+
|
74
|
+
public get forEach(): typeof this._map.forEach {
|
75
|
+
return this._map.forEach.bind(this._map);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
export type JSONObject<Key extends string | number | symbol = string> = { [K in Key]: JSONValue };
|
80
|
+
|
81
|
+
export type JSONValue<Key extends string | number | symbol = string> = string | number | boolean | JSONObject<Key> | Array<JSONValue>;
|
82
|
+
|
83
|
+
export interface JSONFileMapOptions {
|
84
|
+
/**
|
85
|
+
* Should an invalid JSON file be overwritten
|
86
|
+
*/
|
87
|
+
overwrite_invalid_json: boolean;
|
88
|
+
|
89
|
+
/**
|
90
|
+
*
|
91
|
+
*/
|
92
|
+
fs: typeof FS;
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* A Map overlaying a JSON file
|
97
|
+
*/
|
98
|
+
export class JSONFileMap<T extends JSONValue = JSONValue> extends FileMap<T> {
|
99
|
+
public get [Symbol.toStringTag](): 'JSONFileMap' {
|
100
|
+
return 'JSONFileMap';
|
101
|
+
}
|
102
|
+
|
103
|
+
public constructor(
|
104
|
+
path: string,
|
105
|
+
public readonly options: JSONFileMapOptions
|
106
|
+
) {
|
107
|
+
super(path, options.fs);
|
108
|
+
if (!this.fs.existsSync(path)) {
|
109
|
+
this.fs.writeFileSync(path, '{}');
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
public get _map(): Map<string, T> {
|
114
|
+
const content = this.fs.readFileSync(this.path, 'utf8');
|
115
|
+
if (!isJSON(content)) {
|
116
|
+
if (!this.options.overwrite_invalid_json) {
|
117
|
+
throw new SyntaxError('Invalid JSON file: ' + this.path);
|
118
|
+
}
|
119
|
+
console.warn('Invalid JSON file (overwriting): ' + this.path);
|
120
|
+
this.clear();
|
121
|
+
return new Map();
|
122
|
+
}
|
123
|
+
return new Map(Object.entries(JSON.parse(content)));
|
124
|
+
}
|
125
|
+
|
126
|
+
public _write(map: Map<string, T>) {
|
127
|
+
if (!this.fs.existsSync(this.path)) {
|
128
|
+
this.fs.writeFileSync(this.path, '{}');
|
129
|
+
}
|
130
|
+
const content = JSON.stringify(Object.fromEntries(map));
|
131
|
+
this.fs.writeFileSync(this.path, content);
|
132
|
+
}
|
133
|
+
|
134
|
+
public clear() {
|
135
|
+
this.fs.writeFileSync(this.path, '{}');
|
136
|
+
}
|
137
|
+
|
138
|
+
public delete(key: string): boolean {
|
139
|
+
const map = this._map;
|
140
|
+
const rt = map.delete(key);
|
141
|
+
this._write(map);
|
142
|
+
return rt;
|
143
|
+
}
|
144
|
+
|
145
|
+
public get<U extends T>(key: string): U {
|
146
|
+
return this._map.get(key) as U;
|
147
|
+
}
|
148
|
+
|
149
|
+
public has(key: string): boolean {
|
150
|
+
return this._map.has(key);
|
151
|
+
}
|
152
|
+
|
153
|
+
public set(key: string, value: T): this {
|
154
|
+
const map = this._map;
|
155
|
+
map.set(key, value);
|
156
|
+
this._write(map);
|
157
|
+
return this;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
export interface FolderMapOptions {
|
162
|
+
/**
|
163
|
+
* Suffix to append to keys to resolve file names
|
164
|
+
*/
|
165
|
+
suffix: string;
|
166
|
+
|
167
|
+
fs?: typeof FS;
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* A Map overlaying a folder
|
172
|
+
*/
|
173
|
+
export class FolderMap extends FileMap<string> {
|
174
|
+
public get [Symbol.toStringTag](): 'FolderMap' {
|
175
|
+
return 'FolderMap';
|
176
|
+
}
|
177
|
+
|
178
|
+
public constructor(
|
179
|
+
path: string,
|
180
|
+
public readonly options: Partial<FolderMapOptions>
|
181
|
+
) {
|
182
|
+
super(path, options.fs);
|
183
|
+
}
|
184
|
+
|
185
|
+
protected get _names(): string[] {
|
186
|
+
return this.fs
|
187
|
+
.readdirSync(this.path)
|
188
|
+
.filter(p => p.endsWith(this.options.suffix))
|
189
|
+
.map(p => p.slice(0, -this.options.suffix.length));
|
190
|
+
}
|
191
|
+
|
192
|
+
protected _join(path: string): string {
|
193
|
+
return `${this.path}/${path}${this.options.suffix}`;
|
194
|
+
}
|
195
|
+
|
196
|
+
protected get _map(): Map<string, string> {
|
197
|
+
const entries = [];
|
198
|
+
for (const name of this._names) {
|
199
|
+
const content = this.fs.readFileSync(this._join(name), 'utf8');
|
200
|
+
entries.push([name, content]);
|
201
|
+
}
|
202
|
+
return new Map(entries);
|
203
|
+
}
|
204
|
+
|
205
|
+
public clear(): void {
|
206
|
+
for (const name of this._names) {
|
207
|
+
this.fs.unlinkSync(this._join(name));
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
public delete(key: string): boolean {
|
212
|
+
if (!this.has(key)) {
|
213
|
+
return false;
|
214
|
+
}
|
215
|
+
|
216
|
+
this.fs.unlinkSync(this._join(key));
|
217
|
+
return true;
|
218
|
+
}
|
219
|
+
|
220
|
+
public get(key: string): string {
|
221
|
+
if (this.has(key)) {
|
222
|
+
return this.fs.readFileSync(this._join(key), 'utf8');
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
public has(key: string): boolean {
|
227
|
+
return this._names.includes(key);
|
228
|
+
}
|
229
|
+
|
230
|
+
public set(key: string, value: string): this {
|
231
|
+
this.fs.writeFileSync(this._join(key), value);
|
232
|
+
return this;
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
export function resolveConstructors(object: object): string[] {
|
237
|
+
const constructors = [];
|
238
|
+
let prototype = object;
|
239
|
+
while (prototype && !['Function', 'Object'].includes(prototype.constructor.name)) {
|
240
|
+
prototype = Object.getPrototypeOf(prototype);
|
241
|
+
constructors.push(prototype.constructor.name);
|
242
|
+
}
|
243
|
+
return constructors;
|
244
|
+
}
|
245
|
+
|
246
|
+
/**
|
247
|
+
* Gets a random int, r, with the probability P(r) = (base)**r
|
248
|
+
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
249
|
+
* @param probability the probability
|
250
|
+
*/
|
251
|
+
export function getRandomIntWithRecursiveProbability(probability = 0.5): number {
|
252
|
+
return -Math.floor(Math.log(Math.random()) / Math.log(1 / probability));
|
253
|
+
}
|
package/src/random.ts
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
export function randomFloat(min = 0, max = 1): number {
|
2
|
+
return Math.random() * (max - min) + min;
|
3
|
+
}
|
4
|
+
|
5
|
+
export function randomHex(length = 1): string {
|
6
|
+
let s = '';
|
7
|
+
for (let i = 0; i < length; i++) {
|
8
|
+
s += Math.floor(Math.random() * 16).toString(16);
|
9
|
+
}
|
10
|
+
return s;
|
11
|
+
}
|
12
|
+
|
13
|
+
export function randomBoolean(): boolean {
|
14
|
+
return !!Math.round(Math.random());
|
15
|
+
}
|
16
|
+
|
17
|
+
export function randomBinaryString(length = 1): string {
|
18
|
+
let b = '';
|
19
|
+
for (let i = 0; i < length; i++) {
|
20
|
+
b += Math.round(Math.random());
|
21
|
+
}
|
22
|
+
return b;
|
23
|
+
}
|
24
|
+
|
25
|
+
export function randomInt(min = 0, max = 1): number {
|
26
|
+
return Math.round(Math.random() * (max - min) + min);
|
27
|
+
}
|
package/src/types.ts
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
/**
|
2
|
+
* Expands the type T (for intellisense and debugging)
|
3
|
+
* @see https://stackoverflow.com/a/69288824/17637456
|
4
|
+
*/
|
5
|
+
export type Expand<T> = T extends (...args: infer A) => infer R ? (...args: Expand<A>) => Expand<R> : T extends infer O ? { [K in keyof O]: O[K] } : never;
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Recursivly expands the type T (for intellisense and debugging)
|
9
|
+
* @see https://stackoverflow.com/a/69288824/17637456
|
10
|
+
*/
|
11
|
+
export type ExpandRecursively<T> = T extends (...args: infer A) => infer R
|
12
|
+
? (...args: ExpandRecursively<A>) => ExpandRecursively<R>
|
13
|
+
: T extends object
|
14
|
+
? T extends infer O
|
15
|
+
? { [K in keyof O]: ExpandRecursively<O[K]> }
|
16
|
+
: never
|
17
|
+
: T;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Extracts an object with properties assignable to P from an object T
|
21
|
+
* @see https://stackoverflow.com/a/71532723/17637456
|
22
|
+
*/
|
23
|
+
export type ExtractProperties<T, P> = {
|
24
|
+
[K in keyof T as T[K] extends infer Prop ? (Prop extends P ? K : never) : never]: T[K];
|
25
|
+
};
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Extract the keys of T which are required
|
29
|
+
* @see https://stackoverflow.com/a/55247867/17637456
|
30
|
+
*/
|
31
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
32
|
+
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K }[keyof T];
|
33
|
+
|
34
|
+
/**
|
35
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
36
|
+
*/
|
37
|
+
export type RequiredProperties<T extends object, K extends keyof T = keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
38
|
+
|
39
|
+
/**
|
40
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
41
|
+
*/
|
42
|
+
export type DeepRequired<T> = {
|
43
|
+
[K in keyof T]: Required<DeepRequired<T[K]>>;
|
44
|
+
};
|
45
|
+
|
46
|
+
/**
|
47
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
48
|
+
*/
|
49
|
+
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
50
|
+
|
51
|
+
/**
|
52
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
53
|
+
*/
|
54
|
+
export type NestedKeys<T extends object> = {
|
55
|
+
[P in keyof T & (string | number)]: T[P] extends Date ? `${P}` : T[P] extends Record<string, unknown> ? `${P}` | `${P}.${NestedKeys<T[P]>}` : `${P}`;
|
56
|
+
}[keyof T & (string | number)];
|
57
|
+
|
58
|
+
/**
|
59
|
+
* @see https://dev.to/tmhao2005/ts-useful-advanced-types-3k5e
|
60
|
+
*/
|
61
|
+
export type PartialRecursive<T> = {
|
62
|
+
[P in keyof T]?: T[P] extends (infer U)[] ? PartialRecursive<U>[] : T[P] extends object | undefined ? PartialRecursive<T[P]> : T[P];
|
63
|
+
};
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Get the keys of a union of objects
|
67
|
+
* @see https://stackoverflow.com/a/65805753/17637456
|
68
|
+
*/
|
69
|
+
export type UnionKeys<T> = T extends T ? keyof T : never;
|
70
|
+
|
71
|
+
type StrictUnionHelper<T, TAll> = T extends unknown ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
|
72
|
+
|
73
|
+
/**
|
74
|
+
* @see https://stackoverflow.com/a/65805753/17637456
|
75
|
+
*/
|
76
|
+
export type StrictUnion<T> = Expand<StrictUnionHelper<T, T>>;
|