utilium 0.3.3 → 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/internal/primitives.d.ts +21 -0
- package/dist/internal/primitives.js +13 -0
- package/dist/internal/struct.d.ts +48 -0
- package/dist/internal/struct.js +11 -0
- package/dist/objects.d.ts +10 -9
- package/dist/objects.js +5 -12
- package/dist/random.d.ts +6 -0
- package/dist/random.js +8 -0
- package/dist/string.d.ts +4 -0
- package/dist/struct.d.ts +52 -54
- package/dist/struct.js +34 -46
- package/dist/types.d.ts +28 -22
- package/package.json +1 -1
- package/src/internal/primitives.ts +32 -0
- package/src/internal/struct.ts +71 -0
- package/src/objects.ts +17 -19
- package/src/random.ts +9 -0
- package/src/string.ts +12 -2
- package/src/struct.ts +54 -108
- package/src/types.ts +30 -12
@@ -0,0 +1,21 @@
|
|
1
|
+
type BitsToBytes = {
|
2
|
+
'8': 1;
|
3
|
+
'16': 2;
|
4
|
+
'32': 4;
|
5
|
+
'64': 8;
|
6
|
+
};
|
7
|
+
export type Size<T extends string> = T extends `${'int' | 'uint' | 'float'}${infer bits}` ? (bits extends keyof BitsToBytes ? BitsToBytes[bits] : never) : never;
|
8
|
+
export type Type = `${'int' | 'uint'}${8 | 16 | 32 | 64}` | `float${32 | 64}`;
|
9
|
+
export type Valid = Type | Capitalize<Type> | 'char';
|
10
|
+
export declare const types: ("int8" | "int16" | "int32" | "int64" | "uint8" | "uint16" | "uint32" | "uint64" | "float32" | "float64")[];
|
11
|
+
export declare const valids: ("int8" | "int16" | "int32" | "int64" | "uint8" | "uint16" | "uint32" | "uint64" | "float32" | "float64" | "Int8" | "Int16" | "Int32" | "Int64" | "Uint8" | "Uint16" | "Uint32" | "Uint64" | "Float32" | "Float64" | "char")[];
|
12
|
+
export declare const regex: RegExp;
|
13
|
+
export type Normalize<T extends Valid> = T extends 'char' ? 'uint8' : Uncapitalize<T>;
|
14
|
+
export declare function normalize<T extends Valid>(type: T): Normalize<T>;
|
15
|
+
export declare function isType(type: {
|
16
|
+
toString(): string;
|
17
|
+
}): type is Type;
|
18
|
+
export declare function isValid(type: {
|
19
|
+
toString(): string;
|
20
|
+
}): type is Valid;
|
21
|
+
export {};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { capitalize } from '../string.js';
|
2
|
+
export const types = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float32', 'float64'];
|
3
|
+
export const valids = [...types, ...types.map(t => capitalize(t)), 'char'];
|
4
|
+
export const regex = /^(u?int)(8|16|32|64)|(float)(32|64)$/i;
|
5
|
+
export function normalize(type) {
|
6
|
+
return (type == 'char' ? 'uint8' : type.toLowerCase());
|
7
|
+
}
|
8
|
+
export function isType(type) {
|
9
|
+
return regex.test(type.toString());
|
10
|
+
}
|
11
|
+
export function isValid(type) {
|
12
|
+
return type == 'char' || regex.test(type.toString().toLowerCase());
|
13
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { ClassLike } from '../types.js';
|
2
|
+
import * as primitive from './primitives.js';
|
3
|
+
export interface MemberInit {
|
4
|
+
name: string;
|
5
|
+
type: string | ClassLike;
|
6
|
+
length?: number;
|
7
|
+
}
|
8
|
+
export declare const init: unique symbol;
|
9
|
+
export type init = typeof init;
|
10
|
+
/**
|
11
|
+
* Options for struct initialization
|
12
|
+
*/
|
13
|
+
export interface Options {
|
14
|
+
align: number;
|
15
|
+
bigEndian: boolean;
|
16
|
+
}
|
17
|
+
export interface Member {
|
18
|
+
type: primitive.Type | Static;
|
19
|
+
offset: number;
|
20
|
+
length?: number;
|
21
|
+
}
|
22
|
+
export interface Metadata {
|
23
|
+
options: Partial<Options>;
|
24
|
+
members: Map<string, Member>;
|
25
|
+
size: number;
|
26
|
+
}
|
27
|
+
export declare const metadata: unique symbol;
|
28
|
+
export type metadata = typeof metadata;
|
29
|
+
export interface Static<T extends Metadata = Metadata> {
|
30
|
+
[metadata]: T;
|
31
|
+
new (): Instance;
|
32
|
+
prototype: Instance;
|
33
|
+
}
|
34
|
+
export interface StaticLike<T extends Metadata = Metadata> extends ClassLike {
|
35
|
+
[metadata]?: T;
|
36
|
+
[init]?: MemberInit[];
|
37
|
+
}
|
38
|
+
export declare function isStatic<T extends Metadata = Metadata>(arg: unknown): arg is Static<T>;
|
39
|
+
export interface Instance<T extends Metadata = Metadata> {
|
40
|
+
constructor: Static<T>;
|
41
|
+
}
|
42
|
+
export interface InstanceLike<T extends Metadata = Metadata> {
|
43
|
+
constructor: StaticLike<T>;
|
44
|
+
}
|
45
|
+
export declare function isInstance<T extends Metadata = Metadata>(arg: unknown): arg is Instance<T>;
|
46
|
+
export declare function isStruct<T extends Metadata = Metadata>(arg: unknown): arg is Instance<T> | Static<T>;
|
47
|
+
export type Like<T extends Metadata = Metadata> = InstanceLike<T> | StaticLike<T>;
|
48
|
+
export type Size<T extends primitive.Valid | StaticLike | InstanceLike> = T extends primitive.Valid ? primitive.Size<T> : T extends Like<infer M> ? M['size'] : number;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export const init = Symbol('struct_init');
|
2
|
+
export const metadata = Symbol('struct');
|
3
|
+
export function isStatic(arg) {
|
4
|
+
return typeof arg == 'function' && metadata in arg;
|
5
|
+
}
|
6
|
+
export function isInstance(arg) {
|
7
|
+
return metadata in (arg?.constructor || {});
|
8
|
+
}
|
9
|
+
export function isStruct(arg) {
|
10
|
+
return isInstance(arg) || isStatic(arg);
|
11
|
+
}
|
package/dist/objects.d.ts
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
2
2
|
import type * as FS from 'fs';
|
3
|
+
import { UnionToTuple } from './types.js';
|
3
4
|
export declare function filterObject<O extends object, R extends object>(object: O, predicate: (key: keyof O, value: O[keyof O]) => boolean): R;
|
4
5
|
export declare function pick<T extends object, K extends keyof T>(object: T, ...keys: readonly K[]): Pick<T, K>;
|
5
6
|
export declare function pick<T extends object, K extends keyof T>(object: T, ...keys: readonly (readonly K[])[]): Pick<T, K>;
|
6
7
|
export declare function omit<T extends object, K extends keyof T>(object: T, ...keys: readonly K[]): Omit<T, K>;
|
7
8
|
export declare function omit<T extends object, K extends keyof T>(object: T, ...keys: readonly (readonly K[])[]): Omit<T, K>;
|
8
|
-
export declare function assignWithDefaults<To extends
|
9
|
+
export declare function assignWithDefaults<To extends Record<keyof any, any>, From extends Partial<To>>(to: To, from: From, defaults?: Partial<To>): void;
|
10
|
+
/**
|
11
|
+
* Entries of T
|
12
|
+
*/
|
13
|
+
export type Entries<T extends object> = UnionToTuple<{
|
14
|
+
[K in keyof T]: [K, T[K]];
|
15
|
+
}[keyof T]>;
|
9
16
|
export declare function isJSON(str: string): boolean;
|
10
17
|
export declare abstract class FileMap<V> implements Map<string, V> {
|
11
18
|
protected readonly path: string;
|
@@ -19,7 +26,7 @@ export declare abstract class FileMap<V> implements Map<string, V> {
|
|
19
26
|
abstract has(key: string): boolean;
|
20
27
|
abstract set(key: string, value: V): this;
|
21
28
|
get size(): number;
|
22
|
-
get [Symbol.iterator]():
|
29
|
+
get [Symbol.iterator](): () => IterableIterator<[string, V]>;
|
23
30
|
get keys(): typeof this._map.keys;
|
24
31
|
get values(): typeof this._map.values;
|
25
32
|
get entries(): typeof this._map.entries;
|
@@ -59,7 +66,7 @@ export interface FolderMapOptions {
|
|
59
66
|
* Suffix to append to keys to resolve file names
|
60
67
|
*/
|
61
68
|
suffix: string;
|
62
|
-
fs
|
69
|
+
fs: typeof FS;
|
63
70
|
}
|
64
71
|
/**
|
65
72
|
* A Map overlaying a folder
|
@@ -78,9 +85,3 @@ export declare class FolderMap extends FileMap<string> {
|
|
78
85
|
set(key: string, value: string): this;
|
79
86
|
}
|
80
87
|
export declare function resolveConstructors(object: object): string[];
|
81
|
-
/**
|
82
|
-
* Gets a random int, r, with the probability P(r) = (base)**r
|
83
|
-
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
84
|
-
* @param probability the probability
|
85
|
-
*/
|
86
|
-
export declare function getRandomIntWithRecursiveProbability(probability?: number): number;
|
package/dist/objects.js
CHANGED
@@ -10,7 +10,7 @@ export function pick(object, ...keys) {
|
|
10
10
|
return picked;
|
11
11
|
}
|
12
12
|
export function omit(object, ...keys) {
|
13
|
-
return filterObject(object,
|
13
|
+
return filterObject(object, key => !keys.flat().includes(key));
|
14
14
|
}
|
15
15
|
export function assignWithDefaults(to, from, defaults = to) {
|
16
16
|
const keys = new Set([...Object.keys(to), ...Object.keys(from)]);
|
@@ -138,7 +138,7 @@ export class FolderMap extends FileMap {
|
|
138
138
|
get _names() {
|
139
139
|
return this.fs
|
140
140
|
.readdirSync(this.path)
|
141
|
-
.filter(p => p.endsWith(this.options.suffix))
|
141
|
+
.filter(p => p.endsWith(this.options.suffix || ''))
|
142
142
|
.map(p => p.slice(0, -this.options.suffix.length));
|
143
143
|
}
|
144
144
|
_join(path) {
|
@@ -165,9 +165,10 @@ export class FolderMap extends FileMap {
|
|
165
165
|
return true;
|
166
166
|
}
|
167
167
|
get(key) {
|
168
|
-
if (this.has(key)) {
|
169
|
-
|
168
|
+
if (!this.has(key)) {
|
169
|
+
throw new ReferenceError('Key not found');
|
170
170
|
}
|
171
|
+
return this.fs.readFileSync(this._join(key), 'utf8');
|
171
172
|
}
|
172
173
|
has(key) {
|
173
174
|
return this._names.includes(key);
|
@@ -186,11 +187,3 @@ export function resolveConstructors(object) {
|
|
186
187
|
}
|
187
188
|
return constructors;
|
188
189
|
}
|
189
|
-
/**
|
190
|
-
* Gets a random int, r, with the probability P(r) = (base)**r
|
191
|
-
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
192
|
-
* @param probability the probability
|
193
|
-
*/
|
194
|
-
export function getRandomIntWithRecursiveProbability(probability = 0.5) {
|
195
|
-
return -Math.floor(Math.log(Math.random()) / Math.log(1 / probability));
|
196
|
-
}
|
package/dist/random.d.ts
CHANGED
@@ -3,3 +3,9 @@ export declare function randomHex(length?: number): string;
|
|
3
3
|
export declare function randomBoolean(): boolean;
|
4
4
|
export declare function randomBinaryString(length?: number): string;
|
5
5
|
export declare function randomInt(min?: number, max?: number): number;
|
6
|
+
/**
|
7
|
+
* Gets a random int, r, with the probability P(r) = (base)**r
|
8
|
+
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
9
|
+
* @param probability the probability
|
10
|
+
*/
|
11
|
+
export declare function getRandomIntWithRecursiveProbability(probability?: number): number;
|
package/dist/random.js
CHANGED
@@ -21,3 +21,11 @@ export function randomBinaryString(length = 1) {
|
|
21
21
|
export function randomInt(min = 0, max = 1) {
|
22
22
|
return Math.round(Math.random() * (max - min) + min);
|
23
23
|
}
|
24
|
+
/**
|
25
|
+
* Gets a random int, r, with the probability P(r) = (base)**r
|
26
|
+
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
27
|
+
* @param probability the probability
|
28
|
+
*/
|
29
|
+
export function getRandomIntWithRecursiveProbability(probability = 0.5) {
|
30
|
+
return -Math.floor(Math.log(Math.random()) / Math.log(1 / probability));
|
31
|
+
}
|
package/dist/string.d.ts
CHANGED
@@ -1,2 +1,6 @@
|
|
1
1
|
export declare function capitalize<T extends string>(value: T): Capitalize<T>;
|
2
2
|
export declare function uncapitalize<T extends string>(value: T): Uncapitalize<T>;
|
3
|
+
export type ConcatString<T extends string[]> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}${ConcatString<R>}` : '';
|
4
|
+
export type Join<T extends string[], S extends string = ','> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}${R extends [] ? '' : `${S}${Join<R, S>}`}` : '';
|
5
|
+
export type Whitespace = ' ' | '\t';
|
6
|
+
export type Trim<T extends string> = T extends `${Whitespace}${infer R extends string}` ? Trim<R> : T;
|
package/dist/struct.d.ts
CHANGED
@@ -1,17 +1,11 @@
|
|
1
|
+
import * as Struct from './internal/struct.js';
|
1
2
|
import { ClassLike } from './types.js';
|
2
|
-
|
3
|
-
export
|
4
|
-
/**
|
5
|
-
* Options for struct initialization
|
6
|
-
*/
|
7
|
-
export interface StructOptions {
|
8
|
-
align: number;
|
9
|
-
bigEndian: boolean;
|
10
|
-
}
|
3
|
+
import * as primitive from './internal/primitives.js';
|
4
|
+
export { Struct };
|
11
5
|
/**
|
12
6
|
* Gets the size in bytes of a type
|
13
7
|
*/
|
14
|
-
export declare function sizeof
|
8
|
+
export declare function sizeof<T extends primitive.Valid | Struct.StaticLike | Struct.InstanceLike>(type: T): Struct.Size<T>;
|
15
9
|
/**
|
16
10
|
* Aligns a number
|
17
11
|
*/
|
@@ -19,11 +13,11 @@ export declare function align(value: number, alignment: number): number;
|
|
19
13
|
/**
|
20
14
|
* Decorates a class as a struct
|
21
15
|
*/
|
22
|
-
export declare function struct(options?: Partial<
|
16
|
+
export declare function struct(options?: Partial<Struct.Options>): (target: Struct.StaticLike, _?: ClassDecoratorContext) => void;
|
23
17
|
/**
|
24
18
|
* Decorates a class member to be serialized
|
25
19
|
*/
|
26
|
-
export declare function member(type:
|
20
|
+
export declare function member(type: primitive.Valid | ClassLike, length?: number): (target: object, context?: ClassMemberDecoratorContext | string | symbol) => void;
|
27
21
|
/**
|
28
22
|
* Serializes a struct into a Uint8Array
|
29
23
|
*/
|
@@ -32,6 +26,10 @@ export declare function serialize(instance: unknown): Uint8Array;
|
|
32
26
|
* Deserializes a struct from a Uint8Array
|
33
27
|
*/
|
34
28
|
export declare function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBufferView): void;
|
29
|
+
/**
|
30
|
+
* Also can be a name when legacy decorators are used
|
31
|
+
*/
|
32
|
+
type Context = string | symbol | ClassMemberDecoratorContext;
|
35
33
|
/**
|
36
34
|
* Shortcut types
|
37
35
|
*
|
@@ -39,87 +37,87 @@ export declare function deserialize(instance: unknown, _buffer: ArrayBuffer | Ar
|
|
39
37
|
*/
|
40
38
|
export declare const types: {
|
41
39
|
int8: {
|
42
|
-
(length
|
43
|
-
(target: object, context
|
40
|
+
(length: number): (target: object, context?: Context) => void;
|
41
|
+
(target: object, context?: Context): void;
|
44
42
|
};
|
45
43
|
int16: {
|
46
|
-
(length
|
47
|
-
(target: object, context
|
44
|
+
(length: number): (target: object, context?: Context) => void;
|
45
|
+
(target: object, context?: Context): void;
|
48
46
|
};
|
49
47
|
int32: {
|
50
|
-
(length
|
51
|
-
(target: object, context
|
48
|
+
(length: number): (target: object, context?: Context) => void;
|
49
|
+
(target: object, context?: Context): void;
|
52
50
|
};
|
53
51
|
int64: {
|
54
|
-
(length
|
55
|
-
(target: object, context
|
52
|
+
(length: number): (target: object, context?: Context) => void;
|
53
|
+
(target: object, context?: Context): void;
|
56
54
|
};
|
57
55
|
uint8: {
|
58
|
-
(length
|
59
|
-
(target: object, context
|
56
|
+
(length: number): (target: object, context?: Context) => void;
|
57
|
+
(target: object, context?: Context): void;
|
60
58
|
};
|
61
59
|
uint16: {
|
62
|
-
(length
|
63
|
-
(target: object, context
|
60
|
+
(length: number): (target: object, context?: Context) => void;
|
61
|
+
(target: object, context?: Context): void;
|
64
62
|
};
|
65
63
|
uint32: {
|
66
|
-
(length
|
67
|
-
(target: object, context
|
64
|
+
(length: number): (target: object, context?: Context) => void;
|
65
|
+
(target: object, context?: Context): void;
|
68
66
|
};
|
69
67
|
uint64: {
|
70
|
-
(length
|
71
|
-
(target: object, context
|
68
|
+
(length: number): (target: object, context?: Context) => void;
|
69
|
+
(target: object, context?: Context): void;
|
72
70
|
};
|
73
71
|
float32: {
|
74
|
-
(length
|
75
|
-
(target: object, context
|
72
|
+
(length: number): (target: object, context?: Context) => void;
|
73
|
+
(target: object, context?: Context): void;
|
76
74
|
};
|
77
75
|
float64: {
|
78
|
-
(length
|
79
|
-
(target: object, context
|
76
|
+
(length: number): (target: object, context?: Context) => void;
|
77
|
+
(target: object, context?: Context): void;
|
80
78
|
};
|
81
79
|
Int8: {
|
82
|
-
(length
|
83
|
-
(target: object, context
|
80
|
+
(length: number): (target: object, context?: Context) => void;
|
81
|
+
(target: object, context?: Context): void;
|
84
82
|
};
|
85
83
|
Int16: {
|
86
|
-
(length
|
87
|
-
(target: object, context
|
84
|
+
(length: number): (target: object, context?: Context) => void;
|
85
|
+
(target: object, context?: Context): void;
|
88
86
|
};
|
89
87
|
Int32: {
|
90
|
-
(length
|
91
|
-
(target: object, context
|
88
|
+
(length: number): (target: object, context?: Context) => void;
|
89
|
+
(target: object, context?: Context): void;
|
92
90
|
};
|
93
91
|
Int64: {
|
94
|
-
(length
|
95
|
-
(target: object, context
|
92
|
+
(length: number): (target: object, context?: Context) => void;
|
93
|
+
(target: object, context?: Context): void;
|
96
94
|
};
|
97
95
|
Uint8: {
|
98
|
-
(length
|
99
|
-
(target: object, context
|
96
|
+
(length: number): (target: object, context?: Context) => void;
|
97
|
+
(target: object, context?: Context): void;
|
100
98
|
};
|
101
99
|
Uint16: {
|
102
|
-
(length
|
103
|
-
(target: object, context
|
100
|
+
(length: number): (target: object, context?: Context) => void;
|
101
|
+
(target: object, context?: Context): void;
|
104
102
|
};
|
105
103
|
Uint32: {
|
106
|
-
(length
|
107
|
-
(target: object, context
|
104
|
+
(length: number): (target: object, context?: Context) => void;
|
105
|
+
(target: object, context?: Context): void;
|
108
106
|
};
|
109
107
|
Uint64: {
|
110
|
-
(length
|
111
|
-
(target: object, context
|
108
|
+
(length: number): (target: object, context?: Context) => void;
|
109
|
+
(target: object, context?: Context): void;
|
112
110
|
};
|
113
111
|
Float32: {
|
114
|
-
(length
|
115
|
-
(target: object, context
|
112
|
+
(length: number): (target: object, context?: Context) => void;
|
113
|
+
(target: object, context?: Context): void;
|
116
114
|
};
|
117
115
|
Float64: {
|
118
|
-
(length
|
119
|
-
(target: object, context
|
116
|
+
(length: number): (target: object, context?: Context) => void;
|
117
|
+
(target: object, context?: Context): void;
|
120
118
|
};
|
121
119
|
char: {
|
122
|
-
(length
|
123
|
-
(target: object, context
|
120
|
+
(length: number): (target: object, context?: Context) => void;
|
121
|
+
(target: object, context?: Context): void;
|
124
122
|
};
|
125
123
|
};
|
package/dist/struct.js
CHANGED
@@ -1,42 +1,22 @@
|
|
1
|
+
import * as Struct from './internal/struct.js';
|
1
2
|
import { capitalize } from './string.js';
|
2
|
-
|
3
|
-
|
4
|
-
const numberRegex = /^(u?int)(8|16|32|64)|(float)(32|64)$/i;
|
5
|
-
function normalizePrimitive(type) {
|
6
|
-
return type == 'char' ? 'uint8' : type.toLowerCase();
|
7
|
-
}
|
8
|
-
function isPrimitiveType(type) {
|
9
|
-
return numberRegex.test(type.toString());
|
10
|
-
}
|
11
|
-
function isValidPrimitive(type) {
|
12
|
-
return type == 'char' || numberRegex.test(type.toString().toLowerCase());
|
13
|
-
}
|
14
|
-
const init = Symbol('struct_init');
|
15
|
-
const metadata = Symbol('struct');
|
16
|
-
function isStatic(arg) {
|
17
|
-
return typeof arg == 'function' && metadata in arg;
|
18
|
-
}
|
19
|
-
function isInstance(arg) {
|
20
|
-
return metadata in (arg?.constructor || {});
|
21
|
-
}
|
22
|
-
function isStruct(arg) {
|
23
|
-
return isInstance(arg) || isStatic(arg);
|
24
|
-
}
|
3
|
+
import * as primitive from './internal/primitives.js';
|
4
|
+
export { Struct };
|
25
5
|
/**
|
26
6
|
* Gets the size in bytes of a type
|
27
7
|
*/
|
28
8
|
export function sizeof(type) {
|
29
9
|
// primitive
|
30
10
|
if (typeof type == 'string') {
|
31
|
-
if (!
|
11
|
+
if (!primitive.isValid(type)) {
|
32
12
|
throw new TypeError('Invalid primitive type: ' + type);
|
33
13
|
}
|
34
|
-
return +
|
14
|
+
return (+primitive.normalize(type).match(primitive.regex)[2] / 8);
|
35
15
|
}
|
36
|
-
if (!isStruct(type)) {
|
16
|
+
if (!Struct.isStruct(type)) {
|
37
17
|
throw new TypeError('Not a struct');
|
38
18
|
}
|
39
|
-
const meta =
|
19
|
+
const meta = Struct.isStatic(type) ? type[Struct.metadata] : type.constructor[Struct.metadata];
|
40
20
|
return meta.size;
|
41
21
|
}
|
42
22
|
/**
|
@@ -51,62 +31,67 @@ export function align(value, alignment) {
|
|
51
31
|
export function struct(options = {}) {
|
52
32
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
53
33
|
return function (target, _) {
|
54
|
-
target[init] ||= [];
|
34
|
+
target[Struct.init] ||= [];
|
55
35
|
let size = 0;
|
56
36
|
const members = new Map();
|
57
|
-
for (const { name, type, length } of target[init]) {
|
58
|
-
if (!
|
37
|
+
for (const { name, type, length } of target[Struct.init]) {
|
38
|
+
if (!primitive.isValid(type) && !Struct.isStatic(type)) {
|
59
39
|
throw new TypeError('Not a valid type: ' + type);
|
60
40
|
}
|
61
41
|
members.set(name, {
|
62
42
|
offset: size,
|
63
|
-
type:
|
43
|
+
type: primitive.isValid(type) ? primitive.normalize(type) : type,
|
64
44
|
length,
|
65
45
|
});
|
66
46
|
size += sizeof(type) * (length || 1);
|
67
47
|
size = align(size, options.align || 1);
|
68
48
|
}
|
69
|
-
target[metadata] = { options, members, size };
|
70
|
-
delete target[init];
|
49
|
+
target[Struct.metadata] = { options, members, size };
|
71
50
|
};
|
72
51
|
}
|
73
52
|
/**
|
74
53
|
* Decorates a class member to be serialized
|
75
54
|
*/
|
76
55
|
export function member(type, length) {
|
77
|
-
return function (target,
|
56
|
+
return function (target, context) {
|
57
|
+
let name = typeof context == 'object' ? context.name : context;
|
78
58
|
if (typeof name == 'symbol') {
|
79
59
|
console.warn('Symbol used for struct member name will be coerced to string: ' + name.toString());
|
80
60
|
name = name.toString();
|
81
61
|
}
|
82
|
-
if (
|
62
|
+
if (!name) {
|
63
|
+
throw new ReferenceError('Invalid name for struct member');
|
64
|
+
}
|
65
|
+
if (typeof target != 'object') {
|
83
66
|
throw new TypeError('Invalid member for struct field');
|
84
67
|
}
|
85
68
|
if (!('constructor' in target)) {
|
86
69
|
throw new TypeError('Invalid member for struct field');
|
87
70
|
}
|
88
|
-
target.constructor
|
89
|
-
|
71
|
+
const struct = target.constructor;
|
72
|
+
struct[Struct.init] ||= [];
|
73
|
+
struct[Struct.init].push({ name, type, length });
|
90
74
|
};
|
91
75
|
}
|
92
76
|
/**
|
93
77
|
* Serializes a struct into a Uint8Array
|
94
78
|
*/
|
95
79
|
export function serialize(instance) {
|
96
|
-
if (!isInstance(instance)) {
|
80
|
+
if (!Struct.isInstance(instance)) {
|
97
81
|
throw new TypeError('Can not serialize, not a struct instance');
|
98
82
|
}
|
99
|
-
const { options, members } = instance.constructor[metadata];
|
83
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
100
84
|
const buffer = new Uint8Array(sizeof(instance));
|
101
85
|
const view = new DataView(buffer.buffer);
|
102
86
|
for (const [name, { type, length, offset }] of members) {
|
103
87
|
for (let i = 0; i < (length || 1); i++) {
|
104
88
|
const iOff = offset + sizeof(type) * i;
|
89
|
+
// @ts-expect-error 7053
|
105
90
|
let value = length > 0 ? instance[name][i] : instance[name];
|
106
91
|
if (typeof value == 'string') {
|
107
92
|
value = value.charCodeAt(0);
|
108
93
|
}
|
109
|
-
if (!
|
94
|
+
if (!primitive.isType(type)) {
|
110
95
|
buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
|
111
96
|
continue;
|
112
97
|
}
|
@@ -129,21 +114,24 @@ export function serialize(instance) {
|
|
129
114
|
* Deserializes a struct from a Uint8Array
|
130
115
|
*/
|
131
116
|
export function deserialize(instance, _buffer) {
|
132
|
-
if (!isInstance(instance)) {
|
117
|
+
if (!Struct.isInstance(instance)) {
|
133
118
|
throw new TypeError('Can not deserialize, not a struct instance');
|
134
119
|
}
|
135
|
-
const { options, members } = instance.constructor[metadata];
|
120
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
136
121
|
const buffer = new Uint8Array('buffer' in _buffer ? _buffer.buffer : _buffer);
|
137
122
|
const view = new DataView(buffer.buffer);
|
138
123
|
for (const [name, { type, offset, length }] of members) {
|
139
124
|
for (let i = 0; i < (length || 1); i++) {
|
125
|
+
// @ts-expect-error 7053
|
140
126
|
let object = length > 0 ? instance[name] : instance;
|
141
127
|
const key = length > 0 ? i : name, iOff = offset + sizeof(type) * i;
|
128
|
+
// @ts-expect-error 7053
|
142
129
|
if (typeof instance[name] == 'string') {
|
130
|
+
// @ts-expect-error 7053
|
143
131
|
instance[name] = instance[name].slice(0, i) + String.fromCharCode(view.getUint8(iOff)) + instance[name].slice(i + 1);
|
144
132
|
continue;
|
145
133
|
}
|
146
|
-
if (!
|
134
|
+
if (!primitive.isType(type)) {
|
147
135
|
if (object[key] === null || object[key] === undefined) {
|
148
136
|
continue;
|
149
137
|
}
|
@@ -168,17 +156,17 @@ export function deserialize(instance, _buffer) {
|
|
168
156
|
}
|
169
157
|
}
|
170
158
|
function _member(type) {
|
171
|
-
function
|
159
|
+
function _(targetOrLength, context) {
|
172
160
|
if (typeof targetOrLength == 'number') {
|
173
161
|
return member(type, targetOrLength);
|
174
162
|
}
|
175
163
|
return member(type)(targetOrLength, context);
|
176
164
|
}
|
177
|
-
return
|
165
|
+
return _;
|
178
166
|
}
|
179
167
|
/**
|
180
168
|
* Shortcut types
|
181
169
|
*
|
182
170
|
* Instead of writing `@member(type)` you can write `@types.type`, or `@types.type(length)` for arrays
|
183
171
|
*/
|
184
|
-
export const types = Object.fromEntries(
|
172
|
+
export const types = Object.fromEntries(primitive.valids.map(t => [t, _member(t)]));
|
package/dist/types.d.ts
CHANGED
@@ -71,54 +71,45 @@ export type Empty = [];
|
|
71
71
|
/**
|
72
72
|
* Removes the first element of T and shifts
|
73
73
|
*/
|
74
|
-
export type Shift<T
|
74
|
+
export type Shift<T extends unknown[]> = T extends [unknown, ...infer Rest] ? Rest : never;
|
75
75
|
/**
|
76
76
|
* Gets the first element of T
|
77
77
|
*/
|
78
|
-
export type First<T
|
78
|
+
export type First<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
|
79
79
|
/**
|
80
|
-
* Inserts
|
80
|
+
* Inserts V into T at the start of T
|
81
81
|
*/
|
82
|
-
export type Unshift<T
|
82
|
+
export type Unshift<T extends unknown[], V> = [V, ...T];
|
83
83
|
/**
|
84
84
|
* Removes the last element of T
|
85
85
|
*/
|
86
|
-
export type Pop<T
|
86
|
+
export type Pop<T extends unknown[]> = T extends [...infer _, unknown] ? _ : never;
|
87
87
|
/**
|
88
88
|
* Gets the last element of T
|
89
89
|
*/
|
90
|
-
export type Last<T
|
90
|
+
export type Last<T extends unknown[]> = T extends [...unknown[], infer Last] ? Last : never;
|
91
91
|
/**
|
92
|
-
* Appends
|
92
|
+
* Appends V to T
|
93
93
|
*/
|
94
|
-
export type Push<T
|
94
|
+
export type Push<T extends unknown[], V> = [...T, V];
|
95
95
|
/**
|
96
96
|
* Concats A and B
|
97
97
|
*/
|
98
|
-
export type Concat<A, B> =
|
99
|
-
0: A;
|
100
|
-
1: Concat<Unshift<A, 0>, Shift<B>>;
|
101
|
-
}[Empty extends B ? 0 : 1];
|
98
|
+
export type Concat<A extends unknown[], B extends unknown[]> = Empty extends B ? A : Concat<Unshift<A, 0>, Shift<B>>;
|
102
99
|
/**
|
103
100
|
* Extracts from A what is not B
|
104
101
|
*
|
105
102
|
* @remarks
|
106
103
|
* It does not remove duplicates (so Remove\<[0, 0, 0], [0, 0]\> yields [0]). This is intended and necessary behavior.
|
107
104
|
*/
|
108
|
-
export type Remove<A, B> =
|
109
|
-
0: A;
|
110
|
-
1: Remove<Shift<A>, Shift<B>>;
|
111
|
-
}[Empty extends B ? 0 : 1];
|
105
|
+
export type Remove<A extends unknown[], B extends unknown[]> = Empty extends B ? A : Remove<Shift<A>, Shift<B>>;
|
112
106
|
/**
|
113
107
|
* The length of T
|
114
108
|
*/
|
115
|
-
export type Length<T
|
109
|
+
export type Length<T extends {
|
116
110
|
length: number;
|
117
|
-
}
|
118
|
-
type _FromLength<N extends number, R = Empty> =
|
119
|
-
0: R;
|
120
|
-
1: _FromLength<N, Unshift<R, 0>>;
|
121
|
-
}[Length<R> extends N ? 0 : 1];
|
111
|
+
}> = T['length'];
|
112
|
+
type _FromLength<N extends number, R extends unknown[] = Empty> = Length<R> extends N ? R : _FromLength<N, Unshift<R, 0>>;
|
122
113
|
/**
|
123
114
|
* Creates a tuple of length N
|
124
115
|
*/
|
@@ -175,4 +166,19 @@ export type OptionalTuple<T extends unknown[]> = T extends [infer Head, ...infer
|
|
175
166
|
*/
|
176
167
|
export type MapKeys<T> = T extends Map<infer K, any> ? K : never;
|
177
168
|
export type ClassLike<Instance = unknown> = abstract new (...args: unknown[]) => Instance;
|
169
|
+
/**
|
170
|
+
* Converts a union to an intersection
|
171
|
+
* @see https://stackoverflow.com/a/55128956/17637456
|
172
|
+
*/
|
173
|
+
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
174
|
+
/**
|
175
|
+
* Gets the last element of a union
|
176
|
+
* @see https://stackoverflow.com/a/55128956/17637456
|
177
|
+
*/
|
178
|
+
export type LastOfUnion<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never;
|
179
|
+
/**
|
180
|
+
* Converts a union to a tuple
|
181
|
+
* @see https://stackoverflow.com/a/55128956/17637456
|
182
|
+
*/
|
183
|
+
export type UnionToTuple<T, L = LastOfUnion<T>, N = [T] extends [never] ? true : false> = true extends N ? [] : Push<UnionToTuple<Exclude<T, L>>, L>;
|
178
184
|
export {};
|
package/package.json
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
import { capitalize } from '../string.js';
|
2
|
+
|
3
|
+
type BitsToBytes = {
|
4
|
+
'8': 1;
|
5
|
+
'16': 2;
|
6
|
+
'32': 4;
|
7
|
+
'64': 8;
|
8
|
+
};
|
9
|
+
|
10
|
+
export type Size<T extends string> = T extends `${'int' | 'uint' | 'float'}${infer bits}` ? (bits extends keyof BitsToBytes ? BitsToBytes[bits] : never) : never;
|
11
|
+
export type Type = `${'int' | 'uint'}${8 | 16 | 32 | 64}` | `float${32 | 64}`;
|
12
|
+
export type Valid = Type | Capitalize<Type> | 'char';
|
13
|
+
|
14
|
+
export const types = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float32', 'float64'] satisfies Type[];
|
15
|
+
|
16
|
+
export const valids = [...types, ...types.map(t => capitalize(t)), 'char'] satisfies Valid[];
|
17
|
+
|
18
|
+
export const regex = /^(u?int)(8|16|32|64)|(float)(32|64)$/i;
|
19
|
+
|
20
|
+
export type Normalize<T extends Valid> = T extends 'char' ? 'uint8' : Uncapitalize<T>;
|
21
|
+
|
22
|
+
export function normalize<T extends Valid>(type: T): Normalize<T> {
|
23
|
+
return (type == 'char' ? 'uint8' : type.toLowerCase()) as Normalize<T>;
|
24
|
+
}
|
25
|
+
|
26
|
+
export function isType(type: { toString(): string }): type is Type {
|
27
|
+
return regex.test(type.toString());
|
28
|
+
}
|
29
|
+
|
30
|
+
export function isValid(type: { toString(): string }): type is Valid {
|
31
|
+
return type == 'char' || regex.test(type.toString().toLowerCase());
|
32
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { ClassLike } from '../types.js';
|
2
|
+
import * as primitive from './primitives.js';
|
3
|
+
|
4
|
+
export interface MemberInit {
|
5
|
+
name: string;
|
6
|
+
type: string | ClassLike;
|
7
|
+
length?: number;
|
8
|
+
}
|
9
|
+
|
10
|
+
export const init = Symbol('struct_init');
|
11
|
+
|
12
|
+
export type init = typeof init;
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Options for struct initialization
|
16
|
+
*/
|
17
|
+
export interface Options {
|
18
|
+
align: number;
|
19
|
+
bigEndian: boolean;
|
20
|
+
}
|
21
|
+
|
22
|
+
export interface Member {
|
23
|
+
type: primitive.Type | Static;
|
24
|
+
offset: number;
|
25
|
+
length?: number;
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface Metadata {
|
29
|
+
options: Partial<Options>;
|
30
|
+
members: Map<string, Member>;
|
31
|
+
size: number;
|
32
|
+
}
|
33
|
+
|
34
|
+
export const metadata = Symbol('struct');
|
35
|
+
|
36
|
+
export type metadata = typeof metadata;
|
37
|
+
|
38
|
+
export interface Static<T extends Metadata = Metadata> {
|
39
|
+
[metadata]: T;
|
40
|
+
new (): Instance;
|
41
|
+
prototype: Instance;
|
42
|
+
}
|
43
|
+
|
44
|
+
export interface StaticLike<T extends Metadata = Metadata> extends ClassLike {
|
45
|
+
[metadata]?: T;
|
46
|
+
[init]?: MemberInit[];
|
47
|
+
}
|
48
|
+
|
49
|
+
export function isStatic<T extends Metadata = Metadata>(arg: unknown): arg is Static<T> {
|
50
|
+
return typeof arg == 'function' && metadata in arg;
|
51
|
+
}
|
52
|
+
|
53
|
+
export interface Instance<T extends Metadata = Metadata> {
|
54
|
+
constructor: Static<T>;
|
55
|
+
}
|
56
|
+
|
57
|
+
export interface InstanceLike<T extends Metadata = Metadata> {
|
58
|
+
constructor: StaticLike<T>;
|
59
|
+
}
|
60
|
+
|
61
|
+
export function isInstance<T extends Metadata = Metadata>(arg: unknown): arg is Instance<T> {
|
62
|
+
return metadata in (arg?.constructor || {});
|
63
|
+
}
|
64
|
+
|
65
|
+
export function isStruct<T extends Metadata = Metadata>(arg: unknown): arg is Instance<T> | Static<T> {
|
66
|
+
return isInstance(arg) || isStatic(arg);
|
67
|
+
}
|
68
|
+
|
69
|
+
export type Like<T extends Metadata = Metadata> = InstanceLike<T> | StaticLike<T>;
|
70
|
+
|
71
|
+
export type Size<T extends primitive.Valid | StaticLike | InstanceLike> = T extends primitive.Valid ? primitive.Size<T> : T extends Like<infer M> ? M['size'] : number;
|
package/src/objects.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import type * as FS from 'fs';
|
2
|
+
import { UnionToTuple } from './types.js';
|
2
3
|
|
3
4
|
export function filterObject<O extends object, R extends object>(object: O, predicate: (key: keyof O, value: O[keyof O]) => boolean): R {
|
4
5
|
const entries = <[keyof O, O[keyof O]][]>Object.entries(object);
|
@@ -18,11 +19,11 @@ export function pick<T extends object, K extends keyof T>(object: T, ...keys: re
|
|
18
19
|
export function omit<T extends object, K extends keyof T>(object: T, ...keys: readonly K[]): Omit<T, K>;
|
19
20
|
export function omit<T extends object, K extends keyof T>(object: T, ...keys: readonly (readonly K[])[]): Omit<T, K>;
|
20
21
|
export function omit<T extends object, K extends keyof T>(object: T, ...keys: readonly K[] | readonly (readonly K[])[]): Omit<T, K> {
|
21
|
-
return filterObject<T, Omit<T, K>>(object,
|
22
|
+
return filterObject<T, Omit<T, K>>(object, key => !keys.flat().includes(key as K));
|
22
23
|
}
|
23
24
|
|
24
|
-
export function assignWithDefaults<To extends
|
25
|
-
const keys = new Set([...Object.keys(to), ...Object.keys(from)]);
|
25
|
+
export function assignWithDefaults<To extends Record<keyof any, any>, From extends Partial<To>>(to: To, from: From, defaults: Partial<To> = to): void {
|
26
|
+
const keys = new Set<keyof To | keyof From>([...Object.keys(to), ...Object.keys(from)]);
|
26
27
|
for (const key of keys) {
|
27
28
|
try {
|
28
29
|
to[key] = from[key] ?? defaults[key] ?? to[key];
|
@@ -32,6 +33,11 @@ export function assignWithDefaults<To extends object, From extends object>(to: T
|
|
32
33
|
}
|
33
34
|
}
|
34
35
|
|
36
|
+
/**
|
37
|
+
* Entries of T
|
38
|
+
*/
|
39
|
+
export type Entries<T extends object> = UnionToTuple<{ [K in keyof T]: [K, T[K]] }[keyof T]>;
|
40
|
+
|
35
41
|
export function isJSON(str: string) {
|
36
42
|
try {
|
37
43
|
JSON.parse(str);
|
@@ -184,7 +190,7 @@ export interface FolderMapOptions {
|
|
184
190
|
*/
|
185
191
|
suffix: string;
|
186
192
|
|
187
|
-
fs
|
193
|
+
fs: typeof FS;
|
188
194
|
}
|
189
195
|
|
190
196
|
/**
|
@@ -199,14 +205,14 @@ export class FolderMap extends FileMap<string> {
|
|
199
205
|
path: string,
|
200
206
|
public readonly options: Partial<FolderMapOptions>
|
201
207
|
) {
|
202
|
-
super(path, options.fs);
|
208
|
+
super(path, options.fs!);
|
203
209
|
}
|
204
210
|
|
205
211
|
protected get _names(): string[] {
|
206
212
|
return this.fs
|
207
213
|
.readdirSync(this.path)
|
208
|
-
.filter(p => p.endsWith(this.options.suffix))
|
209
|
-
.map(p => p.slice(0, -this.options.suffix
|
214
|
+
.filter(p => p.endsWith(this.options.suffix || ''))
|
215
|
+
.map(p => p.slice(0, -this.options.suffix!.length));
|
210
216
|
}
|
211
217
|
|
212
218
|
protected _join(path: string): string {
|
@@ -214,7 +220,7 @@ export class FolderMap extends FileMap<string> {
|
|
214
220
|
}
|
215
221
|
|
216
222
|
protected get _map(): Map<string, string> {
|
217
|
-
const entries = [];
|
223
|
+
const entries: [string, string][] = [];
|
218
224
|
for (const name of this._names) {
|
219
225
|
const content = this.fs.readFileSync(this._join(name), 'utf8');
|
220
226
|
entries.push([name, content]);
|
@@ -238,9 +244,10 @@ export class FolderMap extends FileMap<string> {
|
|
238
244
|
}
|
239
245
|
|
240
246
|
public get(key: string): string {
|
241
|
-
if (this.has(key)) {
|
242
|
-
|
247
|
+
if (!this.has(key)) {
|
248
|
+
throw new ReferenceError('Key not found');
|
243
249
|
}
|
250
|
+
return this.fs.readFileSync(this._join(key), 'utf8');
|
244
251
|
}
|
245
252
|
|
246
253
|
public has(key: string): boolean {
|
@@ -262,12 +269,3 @@ export function resolveConstructors(object: object): string[] {
|
|
262
269
|
}
|
263
270
|
return constructors;
|
264
271
|
}
|
265
|
-
|
266
|
-
/**
|
267
|
-
* Gets a random int, r, with the probability P(r) = (base)**r
|
268
|
-
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
269
|
-
* @param probability the probability
|
270
|
-
*/
|
271
|
-
export function getRandomIntWithRecursiveProbability(probability = 0.5): number {
|
272
|
-
return -Math.floor(Math.log(Math.random()) / Math.log(1 / probability));
|
273
|
-
}
|
package/src/random.ts
CHANGED
@@ -25,3 +25,12 @@ export function randomBinaryString(length = 1): string {
|
|
25
25
|
export function randomInt(min = 0, max = 1): number {
|
26
26
|
return Math.round(Math.random() * (max - min) + min);
|
27
27
|
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Gets a random int, r, with the probability P(r) = (base)**r
|
31
|
+
* For example, with a probability of 1/2: P(1) = 1/2, P(2) = 1/4, etc.
|
32
|
+
* @param probability the probability
|
33
|
+
*/
|
34
|
+
export function getRandomIntWithRecursiveProbability(probability = 0.5): number {
|
35
|
+
return -Math.floor(Math.log(Math.random()) / Math.log(1 / probability));
|
36
|
+
}
|
package/src/string.ts
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
export function capitalize<T extends string>(value: T): Capitalize<T> {
|
2
|
-
return <Capitalize<T>>(value.at(0)
|
2
|
+
return <Capitalize<T>>(value.at(0)!.toUpperCase() + value.slice(1));
|
3
3
|
}
|
4
4
|
|
5
5
|
export function uncapitalize<T extends string>(value: T): Uncapitalize<T> {
|
6
|
-
return <Uncapitalize<T>>(value.at(0)
|
6
|
+
return <Uncapitalize<T>>(value.at(0)!.toLowerCase() + value.slice(1));
|
7
7
|
}
|
8
|
+
|
9
|
+
export type ConcatString<T extends string[]> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}${ConcatString<R>}` : '';
|
10
|
+
|
11
|
+
export type Join<T extends string[], S extends string = ','> = T extends [infer F extends string, ...infer R extends string[]]
|
12
|
+
? `${F}${R extends [] ? '' : `${S}${Join<R, S>}`}`
|
13
|
+
: '';
|
14
|
+
|
15
|
+
export type Whitespace = ' ' | '\t';
|
16
|
+
|
17
|
+
export type Trim<T extends string> = T extends `${Whitespace}${infer R extends string}` ? Trim<R> : T;
|
package/src/struct.ts
CHANGED
@@ -1,98 +1,29 @@
|
|
1
|
+
import * as Struct from './internal/struct.js';
|
1
2
|
import { capitalize } from './string.js';
|
2
3
|
import { ClassLike } from './types.js';
|
3
|
-
|
4
|
-
export
|
5
|
-
export type ValidPrimitiveType = PrimitiveType | Capitalize<PrimitiveType> | 'char';
|
6
|
-
|
7
|
-
const primitiveTypes = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float32', 'float64'] satisfies PrimitiveType[];
|
8
|
-
|
9
|
-
const validPrimitiveTypes = [...primitiveTypes, ...primitiveTypes.map(t => capitalize(t)), 'char'] satisfies ValidPrimitiveType[];
|
10
|
-
|
11
|
-
const numberRegex = /^(u?int)(8|16|32|64)|(float)(32|64)$/i;
|
12
|
-
|
13
|
-
function normalizePrimitive(type: ValidPrimitiveType): PrimitiveType {
|
14
|
-
return type == 'char' ? 'uint8' : <PrimitiveType>type.toLowerCase();
|
15
|
-
}
|
16
|
-
|
17
|
-
function isPrimitiveType(type: unknown): type is PrimitiveType {
|
18
|
-
return numberRegex.test(type.toString());
|
19
|
-
}
|
20
|
-
|
21
|
-
function isValidPrimitive(type: unknown): type is ValidPrimitiveType {
|
22
|
-
return type == 'char' || numberRegex.test(type.toString().toLowerCase());
|
23
|
-
}
|
24
|
-
|
25
|
-
interface MemberInit {
|
26
|
-
name: string;
|
27
|
-
type: string | ClassLike;
|
28
|
-
length?: number;
|
29
|
-
}
|
30
|
-
|
31
|
-
const init = Symbol('struct_init');
|
32
|
-
|
33
|
-
/**
|
34
|
-
* Options for struct initialization
|
35
|
-
*/
|
36
|
-
export interface StructOptions {
|
37
|
-
align: number;
|
38
|
-
bigEndian: boolean;
|
39
|
-
}
|
40
|
-
|
41
|
-
interface Member {
|
42
|
-
type: PrimitiveType | Static;
|
43
|
-
offset: number;
|
44
|
-
length?: number;
|
45
|
-
}
|
46
|
-
|
47
|
-
interface Metadata {
|
48
|
-
options: Partial<StructOptions>;
|
49
|
-
members: Map<string, Member>;
|
50
|
-
size: number;
|
51
|
-
}
|
52
|
-
|
53
|
-
const metadata = Symbol('struct');
|
54
|
-
|
55
|
-
interface Static {
|
56
|
-
[metadata]?: Metadata;
|
57
|
-
new (): Instance;
|
58
|
-
prototype: Instance;
|
59
|
-
}
|
60
|
-
|
61
|
-
function isStatic(arg: unknown): arg is Static {
|
62
|
-
return typeof arg == 'function' && metadata in arg;
|
63
|
-
}
|
64
|
-
|
65
|
-
interface Instance {
|
66
|
-
constructor: Static;
|
67
|
-
}
|
68
|
-
|
69
|
-
function isInstance(arg: unknown): arg is Instance {
|
70
|
-
return metadata in (arg?.constructor || {});
|
71
|
-
}
|
72
|
-
|
73
|
-
function isStruct(arg: unknown): arg is Instance | Static {
|
74
|
-
return isInstance(arg) || isStatic(arg);
|
75
|
-
}
|
4
|
+
import * as primitive from './internal/primitives.js';
|
5
|
+
export { Struct };
|
76
6
|
|
77
7
|
/**
|
78
8
|
* Gets the size in bytes of a type
|
79
9
|
*/
|
80
|
-
export function sizeof
|
10
|
+
export function sizeof<T extends primitive.Valid | Struct.StaticLike | Struct.InstanceLike>(type: T): Struct.Size<T> {
|
81
11
|
// primitive
|
82
12
|
if (typeof type == 'string') {
|
83
|
-
if (!
|
13
|
+
if (!primitive.isValid(type)) {
|
84
14
|
throw new TypeError('Invalid primitive type: ' + type);
|
85
15
|
}
|
86
16
|
|
87
|
-
return +
|
17
|
+
return (+primitive.normalize(type).match(primitive.regex)![2] / 8) as Struct.Size<T>;
|
88
18
|
}
|
89
19
|
|
90
|
-
if (!isStruct(type)) {
|
20
|
+
if (!Struct.isStruct(type)) {
|
91
21
|
throw new TypeError('Not a struct');
|
92
22
|
}
|
93
23
|
|
94
|
-
const meta: Metadata =
|
95
|
-
|
24
|
+
const meta: Struct.Metadata = Struct.isStatic(type) ? type[Struct.metadata] : type.constructor[Struct.metadata];
|
25
|
+
|
26
|
+
return meta.size as Struct.Size<T>;
|
96
27
|
}
|
97
28
|
|
98
29
|
/**
|
@@ -105,41 +36,45 @@ export function align(value: number, alignment: number): number {
|
|
105
36
|
/**
|
106
37
|
* Decorates a class as a struct
|
107
38
|
*/
|
108
|
-
export function struct(options: Partial<
|
39
|
+
export function struct(options: Partial<Struct.Options> = {}) {
|
109
40
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
110
|
-
return function (target:
|
111
|
-
target[init] ||= [];
|
41
|
+
return function (target: Struct.StaticLike, _?: ClassDecoratorContext) {
|
42
|
+
target[Struct.init] ||= [];
|
112
43
|
let size = 0;
|
113
44
|
const members = new Map();
|
114
|
-
for (const { name, type, length } of target[init]
|
115
|
-
if (!
|
45
|
+
for (const { name, type, length } of target[Struct.init]) {
|
46
|
+
if (!primitive.isValid(type) && !Struct.isStatic(type)) {
|
116
47
|
throw new TypeError('Not a valid type: ' + type);
|
117
48
|
}
|
118
49
|
members.set(name, {
|
119
50
|
offset: size,
|
120
|
-
type:
|
51
|
+
type: primitive.isValid(type) ? primitive.normalize(type) : type,
|
121
52
|
length,
|
122
53
|
});
|
123
54
|
size += sizeof(type) * (length || 1);
|
124
55
|
size = align(size, options.align || 1);
|
125
56
|
}
|
126
57
|
|
127
|
-
target[metadata] = { options, members, size } satisfies Metadata;
|
128
|
-
delete target[init];
|
58
|
+
target[Struct.metadata] = { options, members, size } satisfies Struct.Metadata;
|
129
59
|
};
|
130
60
|
}
|
131
61
|
|
132
62
|
/**
|
133
63
|
* Decorates a class member to be serialized
|
134
64
|
*/
|
135
|
-
export function member(type:
|
136
|
-
return function (target: object,
|
65
|
+
export function member(type: primitive.Valid | ClassLike, length?: number) {
|
66
|
+
return function (target: object, context?: ClassMemberDecoratorContext | string | symbol) {
|
67
|
+
let name = typeof context == 'object' ? context.name : context;
|
137
68
|
if (typeof name == 'symbol') {
|
138
69
|
console.warn('Symbol used for struct member name will be coerced to string: ' + name.toString());
|
139
70
|
name = name.toString();
|
140
71
|
}
|
141
72
|
|
142
|
-
if (
|
73
|
+
if (!name) {
|
74
|
+
throw new ReferenceError('Invalid name for struct member');
|
75
|
+
}
|
76
|
+
|
77
|
+
if (typeof target != 'object') {
|
143
78
|
throw new TypeError('Invalid member for struct field');
|
144
79
|
}
|
145
80
|
|
@@ -147,8 +82,10 @@ export function member(type: ValidPrimitiveType | ClassLike, length?: number) {
|
|
147
82
|
throw new TypeError('Invalid member for struct field');
|
148
83
|
}
|
149
84
|
|
150
|
-
target.constructor
|
151
|
-
|
85
|
+
const struct = (target as Struct.InstanceLike).constructor;
|
86
|
+
|
87
|
+
struct[Struct.init] ||= [];
|
88
|
+
struct[Struct.init].push({ name, type, length } satisfies Struct.MemberInit);
|
152
89
|
};
|
153
90
|
}
|
154
91
|
|
@@ -156,10 +93,10 @@ export function member(type: ValidPrimitiveType | ClassLike, length?: number) {
|
|
156
93
|
* Serializes a struct into a Uint8Array
|
157
94
|
*/
|
158
95
|
export function serialize(instance: unknown): Uint8Array {
|
159
|
-
if (!isInstance(instance)) {
|
96
|
+
if (!Struct.isInstance(instance)) {
|
160
97
|
throw new TypeError('Can not serialize, not a struct instance');
|
161
98
|
}
|
162
|
-
const { options, members } = instance.constructor[metadata];
|
99
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
163
100
|
|
164
101
|
const buffer = new Uint8Array(sizeof(instance));
|
165
102
|
const view = new DataView(buffer.buffer);
|
@@ -168,12 +105,13 @@ export function serialize(instance: unknown): Uint8Array {
|
|
168
105
|
for (let i = 0; i < (length || 1); i++) {
|
169
106
|
const iOff = offset + sizeof(type) * i;
|
170
107
|
|
171
|
-
|
108
|
+
// @ts-expect-error 7053
|
109
|
+
let value = length! > 0 ? instance[name][i] : instance[name];
|
172
110
|
if (typeof value == 'string') {
|
173
111
|
value = value.charCodeAt(0);
|
174
112
|
}
|
175
113
|
|
176
|
-
if (!
|
114
|
+
if (!primitive.isType(type)) {
|
177
115
|
buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
|
178
116
|
continue;
|
179
117
|
}
|
@@ -201,10 +139,10 @@ export function serialize(instance: unknown): Uint8Array {
|
|
201
139
|
* Deserializes a struct from a Uint8Array
|
202
140
|
*/
|
203
141
|
export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBufferView) {
|
204
|
-
if (!isInstance(instance)) {
|
142
|
+
if (!Struct.isInstance(instance)) {
|
205
143
|
throw new TypeError('Can not deserialize, not a struct instance');
|
206
144
|
}
|
207
|
-
const { options, members } = instance.constructor[metadata];
|
145
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
208
146
|
|
209
147
|
const buffer = new Uint8Array('buffer' in _buffer ? _buffer.buffer : _buffer);
|
210
148
|
|
@@ -212,16 +150,19 @@ export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBuffe
|
|
212
150
|
|
213
151
|
for (const [name, { type, offset, length }] of members) {
|
214
152
|
for (let i = 0; i < (length || 1); i++) {
|
215
|
-
|
216
|
-
|
153
|
+
// @ts-expect-error 7053
|
154
|
+
let object = length! > 0 ? instance[name] : instance;
|
155
|
+
const key = length! > 0 ? i : name,
|
217
156
|
iOff = offset + sizeof(type) * i;
|
218
157
|
|
158
|
+
// @ts-expect-error 7053
|
219
159
|
if (typeof instance[name] == 'string') {
|
160
|
+
// @ts-expect-error 7053
|
220
161
|
instance[name] = instance[name].slice(0, i) + String.fromCharCode(view.getUint8(iOff)) + instance[name].slice(i + 1);
|
221
162
|
continue;
|
222
163
|
}
|
223
164
|
|
224
|
-
if (!
|
165
|
+
if (!primitive.isType(type)) {
|
225
166
|
if (object[key] === null || object[key] === undefined) {
|
226
167
|
continue;
|
227
168
|
}
|
@@ -229,7 +170,7 @@ export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBuffe
|
|
229
170
|
continue;
|
230
171
|
}
|
231
172
|
|
232
|
-
if (length > 0) {
|
173
|
+
if (length! > 0) {
|
233
174
|
object ||= [];
|
234
175
|
}
|
235
176
|
|
@@ -250,17 +191,22 @@ export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBuffe
|
|
250
191
|
}
|
251
192
|
}
|
252
193
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
194
|
+
/**
|
195
|
+
* Also can be a name when legacy decorators are used
|
196
|
+
*/
|
197
|
+
type Context = string | symbol | ClassMemberDecoratorContext;
|
198
|
+
|
199
|
+
function _member<T extends primitive.Valid>(type: T) {
|
200
|
+
function _(length: number): (target: object, context?: Context) => void;
|
201
|
+
function _(target: object, context?: Context): void;
|
202
|
+
function _(targetOrLength: object | number, context?: Context) {
|
257
203
|
if (typeof targetOrLength == 'number') {
|
258
204
|
return member(type, targetOrLength);
|
259
205
|
}
|
260
206
|
|
261
207
|
return member(type)(targetOrLength, context);
|
262
208
|
}
|
263
|
-
return
|
209
|
+
return _;
|
264
210
|
}
|
265
211
|
|
266
212
|
/**
|
@@ -268,4 +214,4 @@ function _member<T extends ValidPrimitiveType>(type: T) {
|
|
268
214
|
*
|
269
215
|
* Instead of writing `@member(type)` you can write `@types.type`, or `@types.type(length)` for arrays
|
270
216
|
*/
|
271
|
-
export const types = Object.fromEntries(
|
217
|
+
export const types = Object.fromEntries(primitive.valids.map(t => [t, _member(t)])) as { [K in primitive.Valid]: ReturnType<typeof _member<K>> };
|
package/src/types.ts
CHANGED
@@ -85,37 +85,37 @@ export type Empty = [];
|
|
85
85
|
/**
|
86
86
|
* Removes the first element of T and shifts
|
87
87
|
*/
|
88
|
-
export type Shift<T
|
88
|
+
export type Shift<T extends unknown[]> = T extends [unknown, ...infer Rest] ? Rest : never;
|
89
89
|
|
90
90
|
/**
|
91
91
|
* Gets the first element of T
|
92
92
|
*/
|
93
|
-
export type First<T
|
93
|
+
export type First<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
|
94
94
|
|
95
95
|
/**
|
96
|
-
* Inserts
|
96
|
+
* Inserts V into T at the start of T
|
97
97
|
*/
|
98
|
-
export type Unshift<T
|
98
|
+
export type Unshift<T extends unknown[], V> = [V, ...T];
|
99
99
|
|
100
100
|
/**
|
101
101
|
* Removes the last element of T
|
102
102
|
*/
|
103
|
-
export type Pop<T
|
103
|
+
export type Pop<T extends unknown[]> = T extends [...infer _, unknown] ? _ : never;
|
104
104
|
|
105
105
|
/**
|
106
106
|
* Gets the last element of T
|
107
107
|
*/
|
108
|
-
export type Last<T
|
108
|
+
export type Last<T extends unknown[]> = T extends [...unknown[], infer Last] ? Last : never;
|
109
109
|
|
110
110
|
/**
|
111
|
-
* Appends
|
111
|
+
* Appends V to T
|
112
112
|
*/
|
113
|
-
export type Push<T
|
113
|
+
export type Push<T extends unknown[], V> = [...T, V];
|
114
114
|
|
115
115
|
/**
|
116
116
|
* Concats A and B
|
117
117
|
*/
|
118
|
-
export type Concat<A, B> =
|
118
|
+
export type Concat<A extends unknown[], B extends unknown[]> = Empty extends B ? A : Concat<Unshift<A, 0>, Shift<B>>;
|
119
119
|
|
120
120
|
/**
|
121
121
|
* Extracts from A what is not B
|
@@ -123,14 +123,14 @@ export type Concat<A, B> = { 0: A; 1: Concat<Unshift<A, 0>, Shift<B>> }[Empty ex
|
|
123
123
|
* @remarks
|
124
124
|
* It does not remove duplicates (so Remove\<[0, 0, 0], [0, 0]\> yields [0]). This is intended and necessary behavior.
|
125
125
|
*/
|
126
|
-
export type Remove<A, B> =
|
126
|
+
export type Remove<A extends unknown[], B extends unknown[]> = Empty extends B ? A : Remove<Shift<A>, Shift<B>>;
|
127
127
|
|
128
128
|
/**
|
129
129
|
* The length of T
|
130
130
|
*/
|
131
|
-
export type Length<T
|
131
|
+
export type Length<T extends { length: number }> = T['length'];
|
132
132
|
|
133
|
-
type _FromLength<N extends number, R = Empty> =
|
133
|
+
type _FromLength<N extends number, R extends unknown[] = Empty> = Length<R> extends N ? R : _FromLength<N, Unshift<R, 0>>;
|
134
134
|
|
135
135
|
/**
|
136
136
|
* Creates a tuple of length N
|
@@ -202,3 +202,21 @@ export type OptionalTuple<T extends unknown[]> = T extends [infer Head, ...infer
|
|
202
202
|
export type MapKeys<T> = T extends Map<infer K, any> ? K : never;
|
203
203
|
|
204
204
|
export type ClassLike<Instance = unknown> = abstract new (...args: unknown[]) => Instance;
|
205
|
+
|
206
|
+
/**
|
207
|
+
* Converts a union to an intersection
|
208
|
+
* @see https://stackoverflow.com/a/55128956/17637456
|
209
|
+
*/
|
210
|
+
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
211
|
+
|
212
|
+
/**
|
213
|
+
* Gets the last element of a union
|
214
|
+
* @see https://stackoverflow.com/a/55128956/17637456
|
215
|
+
*/
|
216
|
+
export type LastOfUnion<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never;
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Converts a union to a tuple
|
220
|
+
* @see https://stackoverflow.com/a/55128956/17637456
|
221
|
+
*/
|
222
|
+
export type UnionToTuple<T, L = LastOfUnion<T>, N = [T] extends [never] ? true : false> = true extends N ? [] : Push<UnionToTuple<Exclude<T, L>>, L>;
|