utilium 0.3.4 → 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, context?: ClassMemberDecoratorContext | string | symbol) => 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?: string | symbol | ClassMemberDecoratorContext) => void;
43
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
47
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
51
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
55
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
59
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
63
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
67
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
71
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
75
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
79
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
83
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
87
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
91
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
95
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
99
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
103
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
107
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
111
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
115
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
119
- (target: object, context?: string | symbol | 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?: string | symbol | ClassMemberDecoratorContext) => void;
123
- (target: object, context?: string | symbol | 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,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 (!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
  /**
@@ -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 ((typeof target != 'object' || typeof target != 'function') && !('constructor' in target)) {
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[init] ||= [];
87
- target.constructor[init].push({ name, type, length });
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 (!isPrimitiveType(type)) {
94
+ if (!primitive.isType(type)) {
108
95
  buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
109
96
  continue;
110
97
  }
@@ -127,21 +114,24 @@ 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 (!isPrimitiveType(type)) {
134
+ if (!primitive.isType(type)) {
145
135
  if (object[key] === null || object[key] === undefined) {
146
136
  continue;
147
137
  }
@@ -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(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.4",
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,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<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) {
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 ((typeof target != 'object' || typeof target != 'function') && !('constructor' in target)) {
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[init] ||= [];
148
- 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);
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
- let value = length > 0 ? instance[name][i] : instance[name];
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 (!isPrimitiveType(type)) {
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,16 +150,19 @@ 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
- let object = length > 0 ? instance[name] : instance;
213
- 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,
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 (!isPrimitiveType(type)) {
165
+ if (!primitive.isType(type)) {
222
166
  if (object[key] === null || object[key] === undefined) {
223
167
  continue;
224
168
  }
@@ -226,7 +170,7 @@ export function deserialize(instance: unknown, _buffer: ArrayBuffer | ArrayBuffe
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
- function _member<T extends ValidPrimitiveType>(type: T) {
251
- function _(length?: number): (target: object, context?: string | symbol | ClassMemberDecoratorContext) => void;
252
- function _(target: object, context?: string | symbol | ClassMemberDecoratorContext): void;
253
- function _(targetOrLength: object | number, context?: string | symbol | 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) {
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(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>;