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.
@@ -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 object, From extends object>(to: To, from: From, defaults?: Partial<To>): void;
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](): any;
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?: typeof 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, (key) => !keys.flat().includes(key));
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
- return this.fs.readFileSync(this._join(key), 'utf8');
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
- export type PrimitiveType = `${'int' | 'uint'}${8 | 16 | 32 | 64}` | `float${32 | 64}`;
3
- export type ValidPrimitiveType = PrimitiveType | Capitalize<PrimitiveType> | 'char';
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(type: ValidPrimitiveType | ClassLike | object): number;
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<StructOptions>): (target: ClassLike, _?: ClassDecoratorContext) => void;
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: ValidPrimitiveType | ClassLike, length?: number): (target: object, { name }: ClassMemberDecoratorContext) => void;
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?: number): (target: object, context: ClassMemberDecoratorContext) => void;
43
- (target: object, context: ClassMemberDecoratorContext): void;
40
+ (length: number): (target: object, context?: Context) => void;
41
+ (target: object, context?: Context): void;
44
42
  };
45
43
  int16: {
46
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
47
- (target: object, context: ClassMemberDecoratorContext): void;
44
+ (length: number): (target: object, context?: Context) => void;
45
+ (target: object, context?: Context): void;
48
46
  };
49
47
  int32: {
50
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
51
- (target: object, context: ClassMemberDecoratorContext): void;
48
+ (length: number): (target: object, context?: Context) => void;
49
+ (target: object, context?: Context): void;
52
50
  };
53
51
  int64: {
54
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
55
- (target: object, context: ClassMemberDecoratorContext): void;
52
+ (length: number): (target: object, context?: Context) => void;
53
+ (target: object, context?: Context): void;
56
54
  };
57
55
  uint8: {
58
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
59
- (target: object, context: ClassMemberDecoratorContext): void;
56
+ (length: number): (target: object, context?: Context) => void;
57
+ (target: object, context?: Context): void;
60
58
  };
61
59
  uint16: {
62
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
63
- (target: object, context: ClassMemberDecoratorContext): void;
60
+ (length: number): (target: object, context?: Context) => void;
61
+ (target: object, context?: Context): void;
64
62
  };
65
63
  uint32: {
66
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
67
- (target: object, context: ClassMemberDecoratorContext): void;
64
+ (length: number): (target: object, context?: Context) => void;
65
+ (target: object, context?: Context): void;
68
66
  };
69
67
  uint64: {
70
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
71
- (target: object, context: ClassMemberDecoratorContext): void;
68
+ (length: number): (target: object, context?: Context) => void;
69
+ (target: object, context?: Context): void;
72
70
  };
73
71
  float32: {
74
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
75
- (target: object, context: ClassMemberDecoratorContext): void;
72
+ (length: number): (target: object, context?: Context) => void;
73
+ (target: object, context?: Context): void;
76
74
  };
77
75
  float64: {
78
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
79
- (target: object, context: ClassMemberDecoratorContext): void;
76
+ (length: number): (target: object, context?: Context) => void;
77
+ (target: object, context?: Context): void;
80
78
  };
81
79
  Int8: {
82
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
83
- (target: object, context: ClassMemberDecoratorContext): void;
80
+ (length: number): (target: object, context?: Context) => void;
81
+ (target: object, context?: Context): void;
84
82
  };
85
83
  Int16: {
86
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
87
- (target: object, context: ClassMemberDecoratorContext): void;
84
+ (length: number): (target: object, context?: Context) => void;
85
+ (target: object, context?: Context): void;
88
86
  };
89
87
  Int32: {
90
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
91
- (target: object, context: ClassMemberDecoratorContext): void;
88
+ (length: number): (target: object, context?: Context) => void;
89
+ (target: object, context?: Context): void;
92
90
  };
93
91
  Int64: {
94
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
95
- (target: object, context: ClassMemberDecoratorContext): void;
92
+ (length: number): (target: object, context?: Context) => void;
93
+ (target: object, context?: Context): void;
96
94
  };
97
95
  Uint8: {
98
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
99
- (target: object, context: ClassMemberDecoratorContext): void;
96
+ (length: number): (target: object, context?: Context) => void;
97
+ (target: object, context?: Context): void;
100
98
  };
101
99
  Uint16: {
102
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
103
- (target: object, context: ClassMemberDecoratorContext): void;
100
+ (length: number): (target: object, context?: Context) => void;
101
+ (target: object, context?: Context): void;
104
102
  };
105
103
  Uint32: {
106
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
107
- (target: object, context: ClassMemberDecoratorContext): void;
104
+ (length: number): (target: object, context?: Context) => void;
105
+ (target: object, context?: Context): void;
108
106
  };
109
107
  Uint64: {
110
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
111
- (target: object, context: ClassMemberDecoratorContext): void;
108
+ (length: number): (target: object, context?: Context) => void;
109
+ (target: object, context?: Context): void;
112
110
  };
113
111
  Float32: {
114
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
115
- (target: object, context: ClassMemberDecoratorContext): void;
112
+ (length: number): (target: object, context?: Context) => void;
113
+ (target: object, context?: Context): void;
116
114
  };
117
115
  Float64: {
118
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
119
- (target: object, context: ClassMemberDecoratorContext): void;
116
+ (length: number): (target: object, context?: Context) => void;
117
+ (target: object, context?: Context): void;
120
118
  };
121
119
  char: {
122
- (length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
123
- (target: object, context: ClassMemberDecoratorContext): void;
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
- const primitiveTypes = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float32', 'float64'];
3
- const validPrimitiveTypes = [...primitiveTypes, ...primitiveTypes.map(t => capitalize(t)), 'char'];
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 (!isValidPrimitive(type)) {
11
+ if (!primitive.isValid(type)) {
32
12
  throw new TypeError('Invalid primitive type: ' + type);
33
13
  }
34
- return +normalizePrimitive(type).match(numberRegex)[2] / 8;
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 = metadata in type ? type[metadata] : type.constructor[metadata];
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 (!isValidPrimitive(type) && !isStatic(type)) {
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: isValidPrimitive(type) ? normalizePrimitive(type) : 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, { name }) {
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 (typeof target != 'object' || typeof target != 'function') {
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[init] ||= [];
89
- target.constructor[init].push({ name, type, length });
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 (!isPrimitiveType(type)) {
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 (!isPrimitiveType(type)) {
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 decorator(targetOrLength, context) {
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 decorator;
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(validPrimitiveTypes.map(t => [t, _member(t)]));
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> = T extends unknown[] ? (((...x: T) => void) extends (h: any, ...t: infer I) => void ? I : []) : unknown;
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> = T extends unknown[] ? (((...x: T) => void) extends (h: infer I, ...t: any) => void ? I : []) : never;
78
+ export type First<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
79
79
  /**
80
- * Inserts A into T at the start of T
80
+ * Inserts V into T at the start of T
81
81
  */
82
- export type Unshift<T, A> = T extends unknown[] ? (((h: A, ...t: T) => void) extends (...i: infer I) => void ? I : unknown) : never;
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> = T extends unknown[] ? (((...x: T) => void) extends (...i: [...infer I, any]) => void ? I : unknown) : never;
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> = T extends unknown[] ? (((...x: T) => void) extends (...i: [...infer H, infer I]) => void ? I : unknown) : never;
90
+ export type Last<T extends unknown[]> = T extends [...unknown[], infer Last] ? Last : never;
91
91
  /**
92
- * Appends A to T
92
+ * Appends V to T
93
93
  */
94
- export type Push<T, A> = T extends unknown[] ? (((...a: [...T, A]) => void) extends (...i: infer I) => void ? I : unknown) : never;
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> = T extends {
109
+ export type Length<T extends {
116
110
  length: number;
117
- } ? T['length'] : never;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utilium",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Typescript utilies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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, (key: K) => !keys.flat().includes(key));
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 object, From extends object>(to: To, from: From, defaults: Partial<To> = to): void {
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?: typeof 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.length));
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
- return this.fs.readFileSync(this._join(key), 'utf8');
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).toUpperCase() + value.slice(1));
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).toLowerCase() + value.slice(1));
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 type PrimitiveType = `${'int' | 'uint'}${8 | 16 | 32 | 64}` | `float${32 | 64}`;
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(type: ValidPrimitiveType | ClassLike | object): number {
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 (!isValidPrimitive(type)) {
13
+ if (!primitive.isValid(type)) {
84
14
  throw new TypeError('Invalid primitive type: ' + type);
85
15
  }
86
16
 
87
- return +normalizePrimitive(type).match(numberRegex)[2] / 8;
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 = metadata in type ? type[metadata] : type.constructor[metadata];
95
- return meta.size;
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<StructOptions> = {}) {
39
+ export function struct(options: Partial<Struct.Options> = {}) {
109
40
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
110
- return function (target: ClassLike, _?: ClassDecoratorContext) {
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] as MemberInit[]) {
115
- if (!isValidPrimitive(type) && !isStatic(type)) {
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: isValidPrimitive(type) ? normalizePrimitive(type) : 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: ValidPrimitiveType | ClassLike, length?: number) {
136
- return function (target: object, { name }: ClassMemberDecoratorContext) {
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 (typeof target != 'object' || typeof target != 'function') {
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[init] ||= [];
151
- target.constructor[init].push({ name, type, length } satisfies MemberInit);
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
- let value = length > 0 ? instance[name][i] : instance[name];
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 (!isPrimitiveType(type)) {
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
- let object = length > 0 ? instance[name] : instance;
216
- const key = length > 0 ? i : name,
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 (!isPrimitiveType(type)) {
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
- function _member<T extends ValidPrimitiveType>(type: T) {
254
- function decorator(length?: number): (target: object, context: ClassMemberDecoratorContext) => void;
255
- function decorator(target: object, context: ClassMemberDecoratorContext): void;
256
- function decorator(targetOrLength: object | number, context?: ClassMemberDecoratorContext) {
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 decorator;
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(validPrimitiveTypes.map(t => [t, _member(t)])) as { [K in ValidPrimitiveType]: ReturnType<typeof _member<K>> };
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> = T extends unknown[] ? (((...x: T) => void) extends (h: any, ...t: infer I) => void ? I : []) : unknown;
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> = T extends unknown[] ? (((...x: T) => void) extends (h: infer I, ...t: any) => void ? I : []) : never;
93
+ export type First<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
94
94
 
95
95
  /**
96
- * Inserts A into T at the start of T
96
+ * Inserts V into T at the start of T
97
97
  */
98
- export type Unshift<T, A> = T extends unknown[] ? (((h: A, ...t: T) => void) extends (...i: infer I) => void ? I : unknown) : never;
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> = T extends unknown[] ? (((...x: T) => void) extends (...i: [...infer I, any]) => void ? I : unknown) : never;
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> = T extends unknown[] ? (((...x: T) => void) extends (...i: [...infer H, infer I]) => void ? I : unknown) : never;
108
+ export type Last<T extends unknown[]> = T extends [...unknown[], infer Last] ? Last : never;
109
109
 
110
110
  /**
111
- * Appends A to T
111
+ * Appends V to T
112
112
  */
113
- export type Push<T, A> = T extends unknown[] ? (((...a: [...T, A]) => void) extends (...i: infer I) => void ? I : unknown) : never;
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> = { 0: A; 1: Concat<Unshift<A, 0>, Shift<B>> }[Empty extends B ? 0 : 1];
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> = { 0: A; 1: Remove<Shift<A>, Shift<B>> }[Empty extends B ? 0 : 1];
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> = T extends { length: number } ? T['length'] : never;
131
+ export type Length<T extends { length: number }> = T['length'];
132
132
 
133
- type _FromLength<N extends number, R = Empty> = { 0: R; 1: _FromLength<N, Unshift<R, 0>> }[Length<R> extends N ? 0 : 1];
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>;