utilium 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/buffer.d.ts CHANGED
@@ -13,3 +13,4 @@ export interface ArrayBufferViewConstructor {
13
13
  * @returns The original buffer if resized successfully, or a newly created buffer
14
14
  */
15
15
  export declare function extendBuffer<T extends ArrayBufferLike | ArrayBufferView>(buffer: T, newByteLength: number): T;
16
+ export declare function toUint8Array(buffer: ArrayBufferLike | ArrayBufferView): Uint8Array;
package/dist/buffer.js CHANGED
@@ -30,3 +30,10 @@ export function extendBuffer(buffer, newByteLength) {
30
30
  return newBuffer;
31
31
  }
32
32
  }
33
+ export function toUint8Array(buffer) {
34
+ if (buffer instanceof Uint8Array)
35
+ return buffer;
36
+ if (!ArrayBuffer.isView(buffer))
37
+ return new Uint8Array(buffer);
38
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
39
+ }
@@ -6,16 +6,16 @@ type BitsToBytes = {
6
6
  '128': 16;
7
7
  };
8
8
  export type Size<T extends string> = T extends `${'int' | 'uint' | 'float'}${infer bits}` ? bits extends keyof BitsToBytes ? BitsToBytes[bits] : never : never;
9
- export declare const types: readonly ["int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "int128", "uint128", "float32", "float64", "float128"];
10
- export type Type = (typeof types)[number];
11
- export type Valid = Type | Capitalize<Type> | 'char';
12
- export declare const valids: ("int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "int128" | "uint128" | "float32" | "float64" | "float128" | "Int8" | "Uint8" | "Int16" | "Uint16" | "Int32" | "Uint32" | "Int64" | "Uint64" | "Int128" | "Uint128" | "Float32" | "Float64" | "Float128" | "char")[];
9
+ export declare const typeNames: readonly ["int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", "int128", "uint128", "float32", "float64", "float128"];
10
+ export type Typename = (typeof typeNames)[number];
11
+ export type Valid = Typename | Capitalize<Typename> | 'char';
12
+ export declare const validNames: ("int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "int128" | "uint128" | "float32" | "float64" | "float128" | "Int8" | "Uint8" | "Int16" | "Uint16" | "Int32" | "Uint32" | "Int64" | "Uint64" | "Int128" | "Uint128" | "Float32" | "Float64" | "Float128" | "char")[];
13
13
  export declare const regex: RegExp;
14
14
  export type Normalize<T extends Valid> = T extends 'char' ? 'uint8' : Uncapitalize<T>;
15
15
  export declare function normalize<T extends Valid>(type: T): Normalize<T>;
16
16
  export declare function isType(type: {
17
17
  toString(): string;
18
- }): type is Type;
18
+ }): type is Typename;
19
19
  export declare function isValid(type: {
20
20
  toString(): string;
21
21
  }): type is Valid;
@@ -1,5 +1,5 @@
1
1
  import { capitalize } from '../string.js';
2
- export const types = [
2
+ export const typeNames = [
3
3
  'int8',
4
4
  'uint8',
5
5
  'int16',
@@ -14,7 +14,7 @@ export const types = [
14
14
  'float64',
15
15
  'float128',
16
16
  ];
17
- export const valids = [...types, ...types.map(t => capitalize(t)), 'char'];
17
+ export const validNames = [...typeNames, ...typeNames.map(t => capitalize(t)), 'char'];
18
18
  export const regex = /^(u?int|float)(8|16|32|64|128)$/i;
19
19
  export function normalize(type) {
20
20
  return (type == 'char' ? 'uint8' : type.toLowerCase());
@@ -2,17 +2,25 @@ import type { ClassLike } from '../types.js';
2
2
  import type * as primitive from './primitives.js';
3
3
  declare global {
4
4
  interface SymbolConstructor {
5
- readonly struct_init: unique symbol;
6
- readonly struct_metadata: unique symbol;
5
+ /** User-defined size */
6
+ readonly size: unique symbol;
7
+ /** User-defined serialization */
8
+ readonly serialize: unique symbol;
9
+ /** User-defined deserialization */
10
+ readonly deserialize: unique symbol;
7
11
  }
8
12
  }
13
+ export type TypeLike = UserDefined | Like | primitive.Valid;
14
+ export type Type = UserDefined | Static | primitive.Typename;
15
+ /**
16
+ * Member initialization data
17
+ * This is needed since class decorators are called *after* member decorators
18
+ */
9
19
  export interface MemberInit {
10
20
  name: string;
11
21
  type: string | ClassLike;
12
22
  length?: number;
13
23
  }
14
- /** @deprecated */
15
- export declare const init: typeof Symbol.struct_init;
16
24
  /**
17
25
  * Options for struct initialization
18
26
  */
@@ -22,7 +30,7 @@ export interface Options {
22
30
  isUnion: boolean;
23
31
  }
24
32
  export interface Member {
25
- type: primitive.Type | Static;
33
+ type: Type;
26
34
  offset: number;
27
35
  length?: number;
28
36
  }
@@ -30,20 +38,18 @@ export interface Metadata {
30
38
  options: Partial<Options>;
31
39
  members: Map<string, Member>;
32
40
  size: number;
41
+ init?: MemberInit[];
33
42
  }
34
- /** @deprecated */
35
- export declare const metadata: typeof Symbol.struct_metadata;
36
- export interface _DecoratorMetadata<T extends Metadata = Metadata> extends DecoratorMetadata {
37
- [Symbol.struct_metadata]?: T;
38
- [Symbol.struct_init]?: MemberInit[];
39
- }
43
+ type _DecoratorMetadata<T extends Metadata = Metadata> = DecoratorMetadata & {
44
+ struct?: Partial<T>;
45
+ };
40
46
  export interface DecoratorContext<T extends Metadata = Metadata> {
41
47
  metadata: _DecoratorMetadata<T>;
42
48
  }
43
49
  export type MemberContext = ClassMemberDecoratorContext & DecoratorContext;
44
50
  export interface Static<T extends Metadata = Metadata> {
45
- [Symbol.metadata]: DecoratorMetadata & {
46
- [Symbol.struct_metadata]: T;
51
+ [Symbol.metadata]: {
52
+ struct: T;
47
53
  };
48
54
  new (): Instance<T>;
49
55
  prototype: Instance<T>;
@@ -52,7 +58,7 @@ export interface StaticLike<T extends Metadata = Metadata> extends ClassLike {
52
58
  [Symbol.metadata]?: _DecoratorMetadata<T> | null;
53
59
  }
54
60
  export declare function isValidMetadata<T extends Metadata = Metadata>(arg: unknown): arg is DecoratorMetadata & {
55
- [Symbol.struct_metadata]: T;
61
+ struct: T;
56
62
  };
57
63
  /**
58
64
  * Polyfill context.metadata
@@ -74,5 +80,14 @@ export declare function isInstance<T extends Metadata = Metadata>(arg: unknown):
74
80
  export declare function checkInstance<T extends Metadata = Metadata>(arg: unknown): asserts arg is Instance<T> & Record<keyof any, any>;
75
81
  export declare function isStruct<T extends Metadata = Metadata>(arg: unknown): arg is Instance<T> | Static<T>;
76
82
  export declare function checkStruct<T extends Metadata = Metadata>(arg: unknown): asserts arg is Instance<T> | Static<T>;
83
+ export interface UserDefined {
84
+ readonly [Symbol.size]: number;
85
+ [Symbol.serialize](): Uint8Array;
86
+ [Symbol.deserialize](value: Uint8Array): void;
87
+ }
88
+ export declare function isUserDefined(arg: unknown): arg is UserDefined;
77
89
  export type Like<T extends Metadata = Metadata> = InstanceLike<T> | StaticLike<T>;
78
- export type Size<T extends primitive.Valid | StaticLike | InstanceLike> = T extends primitive.Valid ? primitive.Size<T> : T extends Like<infer M> ? M['size'] : number;
90
+ export type Size<T extends TypeLike> = T extends {
91
+ readonly [Symbol.size]: infer S;
92
+ } ? S : T extends primitive.Valid ? primitive.Size<T> : T extends Like<infer M> ? M['size'] : number;
93
+ export {};
@@ -1,13 +1,10 @@
1
- // @ts-expect-error 2322
2
- Symbol.struct_init ||= Symbol('struct_init');
3
- // @ts-expect-error 2322
4
- Symbol.struct_metadata ||= Symbol('struct_metadata');
5
- /** @deprecated */
6
- export const init = Symbol.struct_init;
7
- /** @deprecated */
8
- export const metadata = Symbol.struct_metadata;
1
+ Object.assign(Symbol, {
2
+ size: Symbol('uSize'),
3
+ serialize: Symbol('uSerialize'),
4
+ deserialize: Symbol('uDeserialize'),
5
+ });
9
6
  export function isValidMetadata(arg) {
10
- return arg != null && typeof arg == 'object' && Symbol.struct_metadata in arg;
7
+ return arg != null && typeof arg == 'object' && 'struct' in arg;
11
8
  }
12
9
  /**
13
10
  * Polyfill Symbol.metadata
@@ -19,12 +16,8 @@ Symbol.metadata ??= Symbol.for('Symbol.metadata');
19
16
  * @see https://github.com/microsoft/TypeScript/issues/53461
20
17
  */
21
18
  export function _polyfill_contextMetadata(target) {
22
- if (!Symbol?.metadata) {
19
+ if (!Symbol?.metadata || Symbol.metadata in target)
23
20
  return;
24
- }
25
- if (Symbol.metadata in target) {
26
- return;
27
- }
28
21
  Object.defineProperty(target, Symbol.metadata, {
29
22
  enumerable: true,
30
23
  configurable: true,
@@ -52,17 +45,24 @@ export function isInstance(arg) {
52
45
  return arg != null && typeof arg == 'object' && isStatic(arg.constructor);
53
46
  }
54
47
  export function checkInstance(arg) {
55
- if (!isInstance(arg)) {
56
- throw new TypeError((typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
57
- + ' is not a struct instance');
58
- }
48
+ if (isInstance(arg))
49
+ return;
50
+ throw new TypeError((typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
51
+ + ' is not a struct instance');
59
52
  }
60
53
  export function isStruct(arg) {
61
54
  return isInstance(arg) || isStatic(arg);
62
55
  }
63
56
  export function checkStruct(arg) {
64
- if (!isStruct(arg)) {
65
- throw new TypeError((typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
66
- + ' is not a struct');
67
- }
57
+ if (isStruct(arg))
58
+ return;
59
+ throw new TypeError((typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
60
+ + ' is not a struct');
61
+ }
62
+ export function isUserDefined(arg) {
63
+ return (typeof arg == 'object'
64
+ && arg != null
65
+ && Symbol.size in arg
66
+ && Symbol.serialize in arg
67
+ && Symbol.deserialize in arg);
68
68
  }
package/dist/objects.d.ts CHANGED
@@ -41,11 +41,10 @@ export type JSONValue = JSONPrimitive | JSONObject | JSONValue[];
41
41
  /**
42
42
  * An object `T` with all of its functions bound to a `This` value
43
43
  */
44
- type Bound<T extends object, This = any> = T & {
44
+ export type Bound<T extends object, This = any> = T & {
45
45
  [k in keyof T]: T[k] extends (...args: any[]) => any ? (this: This, ...args: Parameters<T[k]>) => ReturnType<T[k]> : T[k];
46
46
  };
47
47
  /**
48
48
  * Binds a this value for all of the functions in an object (not recursive)
49
49
  */
50
50
  export declare function bindFunctions<T extends object, This = any>(fns: T, thisValue: This): Bound<T, This>;
51
- export {};
package/dist/struct.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import * as primitive from './internal/primitives.js';
2
- import type { DecoratorContext, InstanceLike, Options, Size, StaticLike } from './internal/struct.js';
3
- import { type MemberContext } from './internal/struct.js';
2
+ import type { DecoratorContext, InstanceLike, Options, Size, StaticLike, TypeLike, MemberContext } from './internal/struct.js';
4
3
  import type { ClassLike } from './types.js';
5
4
  export * as Struct from './internal/struct.js';
6
5
  /**
7
6
  * Gets the size in bytes of a type
8
7
  */
9
- export declare function sizeof<T extends primitive.Valid | StaticLike | InstanceLike>(type: T): Size<T>;
8
+ export declare function sizeof<T extends TypeLike>(type: T): Size<T>;
10
9
  /**
11
10
  * Returns the offset (in bytes) of a member in a struct.
12
11
  */
package/dist/struct.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { toUint8Array } from './buffer.js';
1
2
  import * as primitive from './internal/primitives.js';
2
- import { checkInstance, checkStruct, isStatic, symbol_metadata } from './internal/struct.js';
3
+ import { checkInstance, checkStruct, isStatic, isUserDefined, symbol_metadata } from './internal/struct.js';
3
4
  import { capitalize } from './string.js';
4
5
  export * as Struct from './internal/struct.js';
5
6
  /**
@@ -11,9 +12,12 @@ export function sizeof(type) {
11
12
  primitive.checkValid(type);
12
13
  return (+primitive.normalize(type).match(primitive.regex)[2] / 8);
13
14
  }
15
+ if (isUserDefined(type)) {
16
+ return type[Symbol.size];
17
+ }
14
18
  checkStruct(type);
15
19
  const struct = isStatic(type) ? type : type.constructor;
16
- return struct[symbol_metadata(struct)][Symbol.struct_metadata].size;
20
+ return struct[symbol_metadata(struct)].struct.size;
17
21
  }
18
22
  /**
19
23
  * Returns the offset (in bytes) of a member in a struct.
@@ -21,7 +25,7 @@ export function sizeof(type) {
21
25
  export function offsetof(type, memberName) {
22
26
  checkStruct(type);
23
27
  const struct = isStatic(type) ? type : type.constructor;
24
- const metadata = struct[symbol_metadata(struct)][Symbol.struct_metadata];
28
+ const metadata = struct[symbol_metadata(struct)].struct;
25
29
  const member = metadata.members.get(memberName);
26
30
  if (!member)
27
31
  throw new Error('Struct does not have member: ' + memberName);
@@ -39,11 +43,11 @@ export function align(value, alignment) {
39
43
  export function struct(options = {}) {
40
44
  return function _decorateStruct(target, context) {
41
45
  context.metadata ??= {};
42
- context.metadata[Symbol.struct_init] ||= [];
46
+ context.metadata.struct ??= {};
47
+ context.metadata.struct.init ??= [];
43
48
  let size = 0;
44
49
  const members = new Map();
45
- for (const _ of context.metadata[Symbol.struct_init]) {
46
- const { name, type, length } = _;
50
+ for (const { name, type, length } of context.metadata.struct.init) {
47
51
  if (!primitive.isValid(type) && !isStatic(type)) {
48
52
  throw new TypeError('Not a valid type: ' + type);
49
53
  }
@@ -56,7 +60,7 @@ export function struct(options = {}) {
56
60
  size = options.isUnion ? Math.max(size, memberSize) : size + memberSize;
57
61
  size = align(size, options.align || 1);
58
62
  }
59
- context.metadata[Symbol.struct_metadata] = { options, members, size };
63
+ context.metadata.struct = { options, members, size };
60
64
  return target;
61
65
  };
62
66
  }
@@ -70,12 +74,12 @@ export function member(type, length) {
70
74
  console.warn('Symbol used for struct member name will be coerced to string: ' + name.toString());
71
75
  name = name.toString();
72
76
  }
73
- if (!name) {
77
+ if (!name)
74
78
  throw new ReferenceError('Invalid name for struct member');
75
- }
76
79
  context.metadata ??= {};
77
- context.metadata[Symbol.struct_init] ||= [];
78
- context.metadata[Symbol.struct_init].push({ name, type, length });
80
+ context.metadata.struct ??= {};
81
+ context.metadata.struct.init ??= [];
82
+ context.metadata.struct.init.push({ name, type, length });
79
83
  return value;
80
84
  };
81
85
  }
@@ -83,8 +87,10 @@ export function member(type, length) {
83
87
  * Serializes a struct into a Uint8Array
84
88
  */
85
89
  export function serialize(instance) {
90
+ if (isUserDefined(instance))
91
+ return instance[Symbol.serialize]();
86
92
  checkInstance(instance);
87
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)][Symbol.struct_metadata];
93
+ const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
88
94
  const buffer = new Uint8Array(sizeof(instance));
89
95
  const view = new DataView(buffer.buffer);
90
96
  // for unions we should write members in ascending last modified order, but we don't have that info.
@@ -132,9 +138,11 @@ export function serialize(instance) {
132
138
  * Deserializes a struct from a Uint8Array
133
139
  */
134
140
  export function deserialize(instance, _buffer) {
141
+ const buffer = toUint8Array(_buffer);
142
+ if (isUserDefined(instance))
143
+ return instance[Symbol.deserialize](buffer);
135
144
  checkInstance(instance);
136
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)][Symbol.struct_metadata];
137
- const buffer = _buffer instanceof Uint8Array ? _buffer : new Uint8Array('buffer' in _buffer ? _buffer.buffer : _buffer);
145
+ const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
138
146
  const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
139
147
  for (const [name, { type, offset, length }] of members) {
140
148
  for (let i = 0; i < (length || 1); i++) {
@@ -149,7 +157,7 @@ export function deserialize(instance, _buffer) {
149
157
  if (object[key] === null || object[key] === undefined) {
150
158
  continue;
151
159
  }
152
- deserialize(object[key], new Uint8Array(buffer.slice(iOff, iOff + sizeof(type))));
160
+ deserialize(object[key], new Uint8Array(buffer.subarray(iOff, iOff + sizeof(type))));
153
161
  continue;
154
162
  }
155
163
  if (length > 0) {
@@ -198,4 +206,4 @@ function _member(type) {
198
206
  *
199
207
  * Instead of writing `@member(type)` you can write `@types.type`, or `@types.type(length)` for arrays
200
208
  */
201
- export const types = Object.fromEntries(primitive.valids.map(t => [t, _member(t)]));
209
+ export const types = Object.fromEntries(primitive.validNames.map(t => [t, _member(t)]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utilium",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Typescript utilities",
5
5
  "funding": {
6
6
  "type": "individual",
package/src/buffer.ts CHANGED
@@ -49,3 +49,9 @@ export function extendBuffer<T extends ArrayBufferLike | ArrayBufferView>(buffer
49
49
  return newBuffer;
50
50
  }
51
51
  }
52
+
53
+ export function toUint8Array(buffer: ArrayBufferLike | ArrayBufferView): Uint8Array {
54
+ if (buffer instanceof Uint8Array) return buffer;
55
+ if (!ArrayBuffer.isView(buffer)) return new Uint8Array(buffer);
56
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
57
+ }
@@ -14,7 +14,7 @@ export type Size<T extends string> = T extends `${'int' | 'uint' | 'float'}${inf
14
14
  : never
15
15
  : never;
16
16
 
17
- export const types = [
17
+ export const typeNames = [
18
18
  'int8',
19
19
  'uint8',
20
20
  'int16',
@@ -30,11 +30,11 @@ export const types = [
30
30
  'float128',
31
31
  ] as const;
32
32
 
33
- export type Type = (typeof types)[number];
33
+ export type Typename = (typeof typeNames)[number];
34
34
 
35
- export type Valid = Type | Capitalize<Type> | 'char';
35
+ export type Valid = Typename | Capitalize<Typename> | 'char';
36
36
 
37
- export const valids = [...types, ...types.map(t => capitalize(t)), 'char'] satisfies Valid[];
37
+ export const validNames = [...typeNames, ...typeNames.map(t => capitalize(t)), 'char'] satisfies Valid[];
38
38
 
39
39
  export const regex = /^(u?int|float)(8|16|32|64|128)$/i;
40
40
 
@@ -44,7 +44,7 @@ export function normalize<T extends Valid>(type: T): Normalize<T> {
44
44
  return (type == 'char' ? 'uint8' : type.toLowerCase()) as Normalize<T>;
45
45
  }
46
46
 
47
- export function isType(type: { toString(): string }): type is Type {
47
+ export function isType(type: { toString(): string }): type is Typename {
48
48
  return regex.test(type.toString());
49
49
  }
50
50
 
@@ -3,26 +3,37 @@ import type * as primitive from './primitives.js';
3
3
 
4
4
  declare global {
5
5
  interface SymbolConstructor {
6
- readonly struct_init: unique symbol;
7
- readonly struct_metadata: unique symbol;
6
+ /** User-defined size */
7
+ readonly size: unique symbol;
8
+
9
+ /** User-defined serialization */
10
+ readonly serialize: unique symbol;
11
+
12
+ /** User-defined deserialization */
13
+ readonly deserialize: unique symbol;
8
14
  }
9
15
  }
10
16
 
11
- // @ts-expect-error 2322
12
- Symbol.struct_init ||= Symbol('struct_init');
17
+ Object.assign(Symbol, {
18
+ size: Symbol('uSize'),
19
+ serialize: Symbol('uSerialize'),
20
+ deserialize: Symbol('uDeserialize'),
21
+ });
22
+
23
+ export type TypeLike = UserDefined | Like | primitive.Valid;
13
24
 
14
- // @ts-expect-error 2322
15
- Symbol.struct_metadata ||= Symbol('struct_metadata');
25
+ export type Type = UserDefined | Static | primitive.Typename;
16
26
 
27
+ /**
28
+ * Member initialization data
29
+ * This is needed since class decorators are called *after* member decorators
30
+ */
17
31
  export interface MemberInit {
18
32
  name: string;
19
33
  type: string | ClassLike;
20
34
  length?: number;
21
35
  }
22
36
 
23
- /** @deprecated */
24
- export const init: typeof Symbol.struct_init = Symbol.struct_init;
25
-
26
37
  /**
27
38
  * Options for struct initialization
28
39
  */
@@ -33,7 +44,7 @@ export interface Options {
33
44
  }
34
45
 
35
46
  export interface Member {
36
- type: primitive.Type | Static;
47
+ type: Type;
37
48
  offset: number;
38
49
  length?: number;
39
50
  }
@@ -42,15 +53,12 @@ export interface Metadata {
42
53
  options: Partial<Options>;
43
54
  members: Map<string, Member>;
44
55
  size: number;
56
+ init?: MemberInit[];
45
57
  }
46
58
 
47
- /** @deprecated */
48
- export const metadata: typeof Symbol.struct_metadata = Symbol.struct_metadata;
49
-
50
- export interface _DecoratorMetadata<T extends Metadata = Metadata> extends DecoratorMetadata {
51
- [Symbol.struct_metadata]?: T;
52
- [Symbol.struct_init]?: MemberInit[];
53
- }
59
+ type _DecoratorMetadata<T extends Metadata = Metadata> = DecoratorMetadata & {
60
+ struct?: Partial<T>;
61
+ };
54
62
 
55
63
  export interface DecoratorContext<T extends Metadata = Metadata> {
56
64
  metadata: _DecoratorMetadata<T>;
@@ -59,9 +67,7 @@ export interface DecoratorContext<T extends Metadata = Metadata> {
59
67
  export type MemberContext = ClassMemberDecoratorContext & DecoratorContext;
60
68
 
61
69
  export interface Static<T extends Metadata = Metadata> {
62
- [Symbol.metadata]: DecoratorMetadata & {
63
- [Symbol.struct_metadata]: T;
64
- };
70
+ [Symbol.metadata]: { struct: T };
65
71
  new (): Instance<T>;
66
72
  prototype: Instance<T>;
67
73
  }
@@ -73,9 +79,9 @@ export interface StaticLike<T extends Metadata = Metadata> extends ClassLike {
73
79
  export function isValidMetadata<T extends Metadata = Metadata>(
74
80
  arg: unknown
75
81
  ): arg is DecoratorMetadata & {
76
- [Symbol.struct_metadata]: T;
82
+ struct: T;
77
83
  } {
78
- return arg != null && typeof arg == 'object' && Symbol.struct_metadata in arg;
84
+ return arg != null && typeof arg == 'object' && 'struct' in arg;
79
85
  }
80
86
 
81
87
  /**
@@ -89,12 +95,8 @@ export function isValidMetadata<T extends Metadata = Metadata>(
89
95
  * @see https://github.com/microsoft/TypeScript/issues/53461
90
96
  */
91
97
  export function _polyfill_contextMetadata(target: object): void {
92
- if (!Symbol?.metadata) {
93
- return;
94
- }
95
- if (Symbol.metadata in target) {
96
- return;
97
- }
98
+ if (!Symbol?.metadata || Symbol.metadata in target) return;
99
+
98
100
  Object.defineProperty(target, Symbol.metadata, {
99
101
  enumerable: true,
100
102
  configurable: true,
@@ -140,12 +142,11 @@ export function isInstance<T extends Metadata = Metadata>(arg: unknown): arg is
140
142
  export function checkInstance<T extends Metadata = Metadata>(
141
143
  arg: unknown
142
144
  ): asserts arg is Instance<T> & Record<keyof any, any> {
143
- if (!isInstance(arg)) {
144
- throw new TypeError(
145
- (typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
146
- + ' is not a struct instance'
147
- );
148
- }
145
+ if (isInstance(arg)) return;
146
+ throw new TypeError(
147
+ (typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
148
+ + ' is not a struct instance'
149
+ );
149
150
  }
150
151
 
151
152
  export function isStruct<T extends Metadata = Metadata>(arg: unknown): arg is Instance<T> | Static<T> {
@@ -153,18 +154,35 @@ export function isStruct<T extends Metadata = Metadata>(arg: unknown): arg is In
153
154
  }
154
155
 
155
156
  export function checkStruct<T extends Metadata = Metadata>(arg: unknown): asserts arg is Instance<T> | Static<T> {
156
- if (!isStruct(arg)) {
157
- throw new TypeError(
158
- (typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
159
- + ' is not a struct'
160
- );
161
- }
157
+ if (isStruct(arg)) return;
158
+ throw new TypeError(
159
+ (typeof arg == 'function' ? arg.name : typeof arg == 'object' && arg ? arg.constructor.name : arg)
160
+ + ' is not a struct'
161
+ );
162
+ }
163
+
164
+ export interface UserDefined {
165
+ readonly [Symbol.size]: number;
166
+ [Symbol.serialize](): Uint8Array;
167
+ [Symbol.deserialize](value: Uint8Array): void;
168
+ }
169
+
170
+ export function isUserDefined(arg: unknown): arg is UserDefined {
171
+ return (
172
+ typeof arg == 'object'
173
+ && arg != null
174
+ && Symbol.size in arg
175
+ && Symbol.serialize in arg
176
+ && Symbol.deserialize in arg
177
+ );
162
178
  }
163
179
 
164
180
  export type Like<T extends Metadata = Metadata> = InstanceLike<T> | StaticLike<T>;
165
181
 
166
- export type Size<T extends primitive.Valid | StaticLike | InstanceLike> = T extends primitive.Valid
167
- ? primitive.Size<T>
168
- : T extends Like<infer M>
169
- ? M['size']
170
- : number;
182
+ export type Size<T extends TypeLike> = T extends { readonly [Symbol.size]: infer S }
183
+ ? S
184
+ : T extends primitive.Valid
185
+ ? primitive.Size<T>
186
+ : T extends Like<infer M>
187
+ ? M['size']
188
+ : number;
package/src/objects.ts CHANGED
@@ -122,7 +122,7 @@ export type JSONValue = JSONPrimitive | JSONObject | JSONValue[];
122
122
  /**
123
123
  * An object `T` with all of its functions bound to a `This` value
124
124
  */
125
- type Bound<T extends object, This = any> = T & {
125
+ export type Bound<T extends object, This = any> = T & {
126
126
  [k in keyof T]: T[k] extends (...args: any[]) => any
127
127
  ? (this: This, ...args: Parameters<T[k]>) => ReturnType<T[k]>
128
128
  : T[k];
package/src/struct.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { toUint8Array } from './buffer.js';
1
2
  import * as primitive from './internal/primitives.js';
2
3
  import type {
3
4
  DecoratorContext,
@@ -8,8 +9,10 @@ import type {
8
9
  Options,
9
10
  Size,
10
11
  StaticLike,
12
+ TypeLike,
13
+ MemberContext,
11
14
  } from './internal/struct.js';
12
- import { checkInstance, checkStruct, isStatic, symbol_metadata, type MemberContext } from './internal/struct.js';
15
+ import { checkInstance, checkStruct, isStatic, isUserDefined, symbol_metadata } from './internal/struct.js';
13
16
  import { capitalize } from './string.js';
14
17
  import type { ClassLike } from './types.js';
15
18
  export * as Struct from './internal/struct.js';
@@ -17,7 +20,7 @@ export * as Struct from './internal/struct.js';
17
20
  /**
18
21
  * Gets the size in bytes of a type
19
22
  */
20
- export function sizeof<T extends primitive.Valid | StaticLike | InstanceLike>(type: T): Size<T> {
23
+ export function sizeof<T extends TypeLike>(type: T): Size<T> {
21
24
  // primitive
22
25
  if (typeof type == 'string') {
23
26
  primitive.checkValid(type);
@@ -25,11 +28,15 @@ export function sizeof<T extends primitive.Valid | StaticLike | InstanceLike>(ty
25
28
  return (+primitive.normalize(type).match(primitive.regex)![2] / 8) as Size<T>;
26
29
  }
27
30
 
31
+ if (isUserDefined(type)) {
32
+ return type[Symbol.size] as Size<T>;
33
+ }
34
+
28
35
  checkStruct(type);
29
36
 
30
37
  const struct = isStatic(type) ? type : type.constructor;
31
38
 
32
- return struct[symbol_metadata(struct)][Symbol.struct_metadata].size as Size<T>;
39
+ return struct[symbol_metadata(struct)].struct.size as Size<T>;
33
40
  }
34
41
 
35
42
  /**
@@ -39,7 +46,7 @@ export function offsetof(type: StaticLike | InstanceLike, memberName: string): n
39
46
  checkStruct(type);
40
47
 
41
48
  const struct = isStatic(type) ? type : type.constructor;
42
- const metadata = struct[symbol_metadata(struct)][Symbol.struct_metadata];
49
+ const metadata = struct[symbol_metadata(struct)].struct;
43
50
 
44
51
  const member = metadata.members.get(memberName);
45
52
  if (!member) throw new Error('Struct does not have member: ' + memberName);
@@ -62,11 +69,12 @@ export function struct(options: Partial<Options> = {}) {
62
69
  context: ClassDecoratorContext & DecoratorContext
63
70
  ): T {
64
71
  context.metadata ??= {};
65
- context.metadata[Symbol.struct_init] ||= [];
72
+ context.metadata.struct ??= {};
73
+ context.metadata.struct.init ??= [];
74
+
66
75
  let size = 0;
67
76
  const members = new Map<string, Member>();
68
- for (const _ of context.metadata[Symbol.struct_init]!) {
69
- const { name, type, length } = _;
77
+ for (const { name, type, length } of context.metadata.struct.init) {
70
78
  if (!primitive.isValid(type) && !isStatic(type)) {
71
79
  throw new TypeError('Not a valid type: ' + type);
72
80
  }
@@ -80,7 +88,7 @@ export function struct(options: Partial<Options> = {}) {
80
88
  size = align(size, options.align || 1);
81
89
  }
82
90
 
83
- context.metadata[Symbol.struct_metadata] = { options, members, size } satisfies Metadata;
91
+ context.metadata.struct = { options, members, size } satisfies Metadata;
84
92
  return target;
85
93
  };
86
94
  }
@@ -96,13 +104,12 @@ export function member(type: primitive.Valid | ClassLike, length?: number) {
96
104
  name = name.toString();
97
105
  }
98
106
 
99
- if (!name) {
100
- throw new ReferenceError('Invalid name for struct member');
101
- }
107
+ if (!name) throw new ReferenceError('Invalid name for struct member');
102
108
 
103
109
  context.metadata ??= {};
104
- context.metadata[Symbol.struct_init] ||= [];
105
- context.metadata[Symbol.struct_init]!.push({ name, type, length } satisfies MemberInit);
110
+ context.metadata.struct ??= {};
111
+ context.metadata.struct.init ??= [];
112
+ context.metadata.struct.init.push({ name, type, length } satisfies MemberInit);
106
113
  return value;
107
114
  };
108
115
  }
@@ -111,8 +118,10 @@ export function member(type: primitive.Valid | ClassLike, length?: number) {
111
118
  * Serializes a struct into a Uint8Array
112
119
  */
113
120
  export function serialize(instance: unknown): Uint8Array {
121
+ if (isUserDefined(instance)) return instance[Symbol.serialize]();
122
+
114
123
  checkInstance(instance);
115
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)][Symbol.struct_metadata];
124
+ const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
116
125
 
117
126
  const buffer = new Uint8Array(sizeof(instance));
118
127
  const view = new DataView(buffer.buffer);
@@ -173,11 +182,12 @@ export function serialize(instance: unknown): Uint8Array {
173
182
  * Deserializes a struct from a Uint8Array
174
183
  */
175
184
  export function deserialize(instance: unknown, _buffer: ArrayBufferLike | ArrayBufferView) {
176
- checkInstance(instance);
177
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)][Symbol.struct_metadata];
185
+ const buffer = toUint8Array(_buffer);
178
186
 
179
- const buffer =
180
- _buffer instanceof Uint8Array ? _buffer : new Uint8Array('buffer' in _buffer ? _buffer.buffer : _buffer);
187
+ if (isUserDefined(instance)) return instance[Symbol.deserialize](buffer);
188
+
189
+ checkInstance(instance);
190
+ const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
181
191
 
182
192
  const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
183
193
 
@@ -197,7 +207,7 @@ export function deserialize(instance: unknown, _buffer: ArrayBufferLike | ArrayB
197
207
  if (object[key] === null || object[key] === undefined) {
198
208
  continue;
199
209
  }
200
- deserialize(object[key], new Uint8Array(buffer.slice(iOff, iOff + sizeof(type))));
210
+ deserialize(object[key], new Uint8Array(buffer.subarray(iOff, iOff + sizeof(type))));
201
211
  continue;
202
212
  }
203
213
 
@@ -261,6 +271,6 @@ function _member<T extends primitive.Valid>(type: T) {
261
271
  *
262
272
  * Instead of writing `@member(type)` you can write `@types.type`, or `@types.type(length)` for arrays
263
273
  */
264
- export const types = Object.fromEntries(primitive.valids.map(t => [t, _member(t)])) as {
274
+ export const types = Object.fromEntries(primitive.validNames.map(t => [t, _member(t)])) as {
265
275
  [K in primitive.Valid]: ReturnType<typeof _member<K>>;
266
276
  };