utilium 0.3.4 → 0.4.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/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 -44
- 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 +56 -107
- 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,23 +31,22 @@ 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
|
/**
|
@@ -80,31 +59,39 @@ export function member(type, length) {
|
|
80
59
|
console.warn('Symbol used for struct member name will be coerced to string: ' + name.toString());
|
81
60
|
name = name.toString();
|
82
61
|
}
|
83
|
-
if (
|
62
|
+
if (!name) {
|
63
|
+
throw new ReferenceError('Invalid name for struct member');
|
64
|
+
}
|
65
|
+
if (typeof target != 'object') {
|
66
|
+
throw new TypeError('Invalid member for struct field');
|
67
|
+
}
|
68
|
+
if (!('constructor' in target)) {
|
84
69
|
throw new TypeError('Invalid member for struct field');
|
85
70
|
}
|
86
|
-
target.constructor
|
87
|
-
|
71
|
+
const struct = target.constructor;
|
72
|
+
struct[Struct.init] ||= [];
|
73
|
+
struct[Struct.init].push({ name, type, length });
|
88
74
|
};
|
89
75
|
}
|
90
76
|
/**
|
91
77
|
* Serializes a struct into a Uint8Array
|
92
78
|
*/
|
93
79
|
export function serialize(instance) {
|
94
|
-
if (!isInstance(instance)) {
|
80
|
+
if (!Struct.isInstance(instance)) {
|
95
81
|
throw new TypeError('Can not serialize, not a struct instance');
|
96
82
|
}
|
97
|
-
const { options, members } = instance.constructor[metadata];
|
83
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
98
84
|
const buffer = new Uint8Array(sizeof(instance));
|
99
85
|
const view = new DataView(buffer.buffer);
|
100
86
|
for (const [name, { type, length, offset }] of members) {
|
101
87
|
for (let i = 0; i < (length || 1); i++) {
|
102
88
|
const iOff = offset + sizeof(type) * i;
|
89
|
+
// @ts-expect-error 7053
|
103
90
|
let value = length > 0 ? instance[name][i] : instance[name];
|
104
91
|
if (typeof value == 'string') {
|
105
92
|
value = value.charCodeAt(0);
|
106
93
|
}
|
107
|
-
if (!
|
94
|
+
if (!primitive.isType(type)) {
|
108
95
|
buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
|
109
96
|
continue;
|
110
97
|
}
|
@@ -127,25 +114,28 @@ export function serialize(instance) {
|
|
127
114
|
* Deserializes a struct from a Uint8Array
|
128
115
|
*/
|
129
116
|
export function deserialize(instance, _buffer) {
|
130
|
-
if (!isInstance(instance)) {
|
117
|
+
if (!Struct.isInstance(instance)) {
|
131
118
|
throw new TypeError('Can not deserialize, not a struct instance');
|
132
119
|
}
|
133
|
-
const { options, members } = instance.constructor[metadata];
|
120
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
134
121
|
const buffer = new Uint8Array('buffer' in _buffer ? _buffer.buffer : _buffer);
|
135
122
|
const view = new DataView(buffer.buffer);
|
136
123
|
for (const [name, { type, offset, length }] of members) {
|
137
124
|
for (let i = 0; i < (length || 1); i++) {
|
125
|
+
// @ts-expect-error 7053
|
138
126
|
let object = length > 0 ? instance[name] : instance;
|
139
127
|
const key = length > 0 ? i : name, iOff = offset + sizeof(type) * i;
|
128
|
+
// @ts-expect-error 7053
|
140
129
|
if (typeof instance[name] == 'string') {
|
130
|
+
// @ts-expect-error 7053
|
141
131
|
instance[name] = instance[name].slice(0, i) + String.fromCharCode(view.getUint8(iOff)) + instance[name].slice(i + 1);
|
142
132
|
continue;
|
143
133
|
}
|
144
|
-
if (!
|
134
|
+
if (!primitive.isType(type)) {
|
145
135
|
if (object[key] === null || object[key] === undefined) {
|
146
136
|
continue;
|
147
137
|
}
|
148
|
-
deserialize(object[key], new Uint8Array(buffer.slice(iOff, sizeof(type))));
|
138
|
+
deserialize(object[key], new Uint8Array(buffer.slice(iOff, iOff + sizeof(type))));
|
149
139
|
continue;
|
150
140
|
}
|
151
141
|
if (length > 0) {
|
@@ -179,4 +169,4 @@ function _member(type) {
|
|
179
169
|
*
|
180
170
|
* Instead of writing `@member(type)` you can write `@types.type`, or `@types.type(length)` for arrays
|
181
171
|
*/
|
182
|
-
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,34 +36,33 @@ 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:
|
65
|
+
export function member(type: primitive.Valid | ClassLike, length?: number) {
|
136
66
|
return function (target: object, context?: ClassMemberDecoratorContext | string | symbol) {
|
137
67
|
let name = typeof context == 'object' ? context.name : context;
|
138
68
|
if (typeof name == 'symbol') {
|
@@ -140,12 +70,22 @@ export function member(type: ValidPrimitiveType | ClassLike, length?: number) {
|
|
140
70
|
name = name.toString();
|
141
71
|
}
|
142
72
|
|
143
|
-
if (
|
73
|
+
if (!name) {
|
74
|
+
throw new ReferenceError('Invalid name for struct member');
|
75
|
+
}
|
76
|
+
|
77
|
+
if (typeof target != 'object') {
|
78
|
+
throw new TypeError('Invalid member for struct field');
|
79
|
+
}
|
80
|
+
|
81
|
+
if (!('constructor' in target)) {
|
144
82
|
throw new TypeError('Invalid member for struct field');
|
145
83
|
}
|
146
84
|
|
147
|
-
target.constructor
|
148
|
-
|
85
|
+
const struct = (target as Struct.InstanceLike).constructor;
|
86
|
+
|
87
|
+
struct[Struct.init] ||= [];
|
88
|
+
struct[Struct.init].push({ name, type, length } satisfies Struct.MemberInit);
|
149
89
|
};
|
150
90
|
}
|
151
91
|
|
@@ -153,10 +93,10 @@ export function member(type: ValidPrimitiveType | ClassLike, length?: number) {
|
|
153
93
|
* Serializes a struct into a Uint8Array
|
154
94
|
*/
|
155
95
|
export function serialize(instance: unknown): Uint8Array {
|
156
|
-
if (!isInstance(instance)) {
|
96
|
+
if (!Struct.isInstance(instance)) {
|
157
97
|
throw new TypeError('Can not serialize, not a struct instance');
|
158
98
|
}
|
159
|
-
const { options, members } = instance.constructor[metadata];
|
99
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
160
100
|
|
161
101
|
const buffer = new Uint8Array(sizeof(instance));
|
162
102
|
const view = new DataView(buffer.buffer);
|
@@ -165,12 +105,13 @@ export function serialize(instance: unknown): Uint8Array {
|
|
165
105
|
for (let i = 0; i < (length || 1); i++) {
|
166
106
|
const iOff = offset + sizeof(type) * i;
|
167
107
|
|
168
|
-
|
108
|
+
// @ts-expect-error 7053
|
109
|
+
let value = length! > 0 ? instance[name][i] : instance[name];
|
169
110
|
if (typeof value == 'string') {
|
170
111
|
value = value.charCodeAt(0);
|
171
112
|
}
|
172
113
|
|
173
|
-
if (!
|
114
|
+
if (!primitive.isType(type)) {
|
174
115
|
buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
|
175
116
|
continue;
|
176
117
|
}
|
@@ -198,10 +139,10 @@ export function serialize(instance: unknown): Uint8Array {
|
|
198
139
|
* Deserializes a struct from a Uint8Array
|
199
140
|
*/
|
200
141
|
export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBufferView) {
|
201
|
-
if (!isInstance(instance)) {
|
142
|
+
if (!Struct.isInstance(instance)) {
|
202
143
|
throw new TypeError('Can not deserialize, not a struct instance');
|
203
144
|
}
|
204
|
-
const { options, members } = instance.constructor[metadata];
|
145
|
+
const { options, members } = instance.constructor[Struct.metadata];
|
205
146
|
|
206
147
|
const buffer = new Uint8Array('buffer' in _buffer ? _buffer.buffer : _buffer);
|
207
148
|
|
@@ -209,24 +150,27 @@ export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBuffe
|
|
209
150
|
|
210
151
|
for (const [name, { type, offset, length }] of members) {
|
211
152
|
for (let i = 0; i < (length || 1); i++) {
|
212
|
-
|
213
|
-
|
153
|
+
// @ts-expect-error 7053
|
154
|
+
let object = length! > 0 ? instance[name] : instance;
|
155
|
+
const key = length! > 0 ? i : name,
|
214
156
|
iOff = offset + sizeof(type) * i;
|
215
157
|
|
158
|
+
// @ts-expect-error 7053
|
216
159
|
if (typeof instance[name] == 'string') {
|
160
|
+
// @ts-expect-error 7053
|
217
161
|
instance[name] = instance[name].slice(0, i) + String.fromCharCode(view.getUint8(iOff)) + instance[name].slice(i + 1);
|
218
162
|
continue;
|
219
163
|
}
|
220
164
|
|
221
|
-
if (!
|
165
|
+
if (!primitive.isType(type)) {
|
222
166
|
if (object[key] === null || object[key] === undefined) {
|
223
167
|
continue;
|
224
168
|
}
|
225
|
-
deserialize(object[key], new Uint8Array(buffer.slice(iOff, sizeof(type))));
|
169
|
+
deserialize(object[key], new Uint8Array(buffer.slice(iOff, iOff + sizeof(type))));
|
226
170
|
continue;
|
227
171
|
}
|
228
172
|
|
229
|
-
if (length > 0) {
|
173
|
+
if (length! > 0) {
|
230
174
|
object ||= [];
|
231
175
|
}
|
232
176
|
|
@@ -247,10 +191,15 @@ export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBuffe
|
|
247
191
|
}
|
248
192
|
}
|
249
193
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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) {
|
254
203
|
if (typeof targetOrLength == 'number') {
|
255
204
|
return member(type, targetOrLength);
|
256
205
|
}
|
@@ -265,4 +214,4 @@ function _member<T extends ValidPrimitiveType>(type: T) {
|
|
265
214
|
*
|
266
215
|
* Instead of writing `@member(type)` you can write `@types.type`, or `@types.type(length)` for arrays
|
267
216
|
*/
|
268
|
-
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>;
|