utilium 1.7.11 → 1.7.12

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.
@@ -27,4 +27,16 @@ type LoggableDecoratorContext = Exclude<DecoratorContext, ClassFieldDecoratorCon
27
27
  * Create a function that can be used to decorate classes and non-field members.
28
28
  */
29
29
  export declare function createLogDecorator(options: CreateLoggerOptions): <T extends (...args: any[]) => any>(value: T, context: LoggableDecoratorContext) => T;
30
+ /**
31
+ * @internal @hidden
32
+ */
33
+ export declare let U_DEBUG: boolean;
34
+ /**
35
+ * @internal @hidden
36
+ */
37
+ export declare function _setDebug(value: boolean): void;
38
+ /**
39
+ * @internal @hidden
40
+ */
41
+ export declare function _debugLog(...text: any[]): void;
30
42
  export {};
package/dist/debugging.js CHANGED
@@ -53,3 +53,20 @@ export function createLogDecorator(options) {
53
53
  };
54
54
  };
55
55
  }
56
+ /**
57
+ * @internal @hidden
58
+ */
59
+ export let U_DEBUG = 'process' in globalThis && 'env' in globalThis.process && globalThis.process.env.U_DEBUG == 'true';
60
+ /**
61
+ * @internal @hidden
62
+ */
63
+ export function _setDebug(value) {
64
+ U_DEBUG = value;
65
+ }
66
+ /**
67
+ * @internal @hidden
68
+ */
69
+ export function _debugLog(...text) {
70
+ if (U_DEBUG)
71
+ console.debug('[U]', ...text);
72
+ }
@@ -10,7 +10,7 @@ declare global {
10
10
  readonly deserialize: unique symbol;
11
11
  }
12
12
  }
13
- export type TypeLike = Custom | Like | primitive.Valid;
13
+ export type TypeLike = Custom | Like | primitive.Valid | undefined | null;
14
14
  export type Type = Custom | Static | primitive.Typename;
15
15
  /**
16
16
  * Member initialization data
@@ -30,22 +30,31 @@ export interface Options {
30
30
  isUnion: boolean;
31
31
  }
32
32
  export interface Member {
33
+ name: string;
33
34
  type: Type;
34
- offset: number;
35
+ staticOffset: number;
35
36
  length?: number | string;
37
+ /** A C-style type/name declaration string, used for diagnostics */
38
+ decl: string;
36
39
  }
37
40
  export interface Metadata {
38
41
  options: Partial<Options>;
39
42
  members: Map<string, Member>;
40
- init?: MemberInit[];
41
43
  staticSize: number;
44
+ isDynamic: boolean;
42
45
  }
43
46
  type _DecoratorMetadata<T extends Metadata = Metadata> = DecoratorMetadata & {
44
- struct?: Partial<T>;
47
+ struct?: T;
48
+ structInit?: MemberInit[];
45
49
  };
46
50
  export interface DecoratorContext<T extends Metadata = Metadata> {
47
51
  metadata: _DecoratorMetadata<T>;
48
52
  }
53
+ /**
54
+ * Initializes the struct metadata for a class
55
+ * This also handles copying metadata from parent classes
56
+ */
57
+ export declare function initMetadata(context: DecoratorContext): MemberInit[];
49
58
  export type MemberContext = ClassMemberDecoratorContext & DecoratorContext;
50
59
  export interface Static<T extends Metadata = Metadata> {
51
60
  [Symbol.metadata]: {
@@ -63,12 +72,9 @@ export declare function isValidMetadata<T extends Metadata = Metadata>(arg: unkn
63
72
  /**
64
73
  * Polyfill context.metadata
65
74
  * @see https://github.com/microsoft/TypeScript/issues/53461
75
+ * @internal @hidden
66
76
  */
67
- export declare function _polyfill_contextMetadata(target: object): void;
68
- /**
69
- * Gets a reference to Symbol.metadata, even on platforms that do not expose it globally (like Node)
70
- */
71
- export declare function symbol_metadata(arg: ClassLike): typeof Symbol.metadata;
77
+ export declare function _polyfill_metadata(target: object): void;
72
78
  export declare function isStatic<T extends Metadata = Metadata>(arg: unknown): arg is Static<T>;
73
79
  export interface Instance<T extends Metadata = Metadata> {
74
80
  constructor: Static<T>;
@@ -90,7 +96,7 @@ export interface Custom {
90
96
  }
91
97
  export declare function isCustom(arg: unknown): arg is Custom;
92
98
  export type Like<T extends Metadata = Metadata> = InstanceLike<T> | StaticLike<T>;
93
- export type Size<T extends TypeLike> = T extends {
94
- readonly [Symbol.size]: infer S;
99
+ export type Size<T extends TypeLike> = T extends undefined | null ? 0 : T extends {
100
+ readonly [Symbol.size]: infer S extends number;
95
101
  } ? S : T extends primitive.Valid ? primitive.Size<T> : number;
96
102
  export {};
@@ -1,22 +1,32 @@
1
+ /**
2
+ * Polyfill Symbol.metadata
3
+ * @see https://github.com/microsoft/TypeScript/issues/53461
4
+ */
5
+ Symbol.metadata ??= Symbol.for('Symbol.metadata');
1
6
  Object.assign(Symbol, {
2
7
  size: Symbol('uSize'),
3
8
  serialize: Symbol('uSerialize'),
4
9
  deserialize: Symbol('uDeserialize'),
5
10
  });
11
+ /**
12
+ * Initializes the struct metadata for a class
13
+ * This also handles copying metadata from parent classes
14
+ */
15
+ export function initMetadata(context) {
16
+ context.metadata ??= {};
17
+ context.metadata.structInit = [...(context.metadata.structInit ?? [])];
18
+ return context.metadata.structInit;
19
+ }
6
20
  export function isValidMetadata(arg) {
7
21
  return arg != null && typeof arg == 'object' && 'struct' in arg;
8
22
  }
9
- /**
10
- * Polyfill Symbol.metadata
11
- * @see https://github.com/microsoft/TypeScript/issues/53461
12
- */
13
- Symbol.metadata ??= Symbol.for('Symbol.metadata');
14
23
  /**
15
24
  * Polyfill context.metadata
16
25
  * @see https://github.com/microsoft/TypeScript/issues/53461
26
+ * @internal @hidden
17
27
  */
18
- export function _polyfill_contextMetadata(target) {
19
- if (!Symbol?.metadata || Symbol.metadata in target)
28
+ export function _polyfill_metadata(target) {
29
+ if (Symbol.metadata in target)
20
30
  return;
21
31
  Object.defineProperty(target, Symbol.metadata, {
22
32
  enumerable: true,
@@ -25,21 +35,8 @@ export function _polyfill_contextMetadata(target) {
25
35
  value: Object.create(null),
26
36
  });
27
37
  }
28
- /**
29
- * Gets a reference to Symbol.metadata, even on platforms that do not expose it globally (like Node)
30
- */
31
- export function symbol_metadata(arg) {
32
- const symbol_metadata = Symbol.metadata || Object.getOwnPropertySymbols(arg).find(s => s.description == 'Symbol.metadata');
33
- _polyfill_contextMetadata(arg);
34
- if (!symbol_metadata) {
35
- throw new ReferenceError('Could not get a reference to Symbol.metadata');
36
- }
37
- return symbol_metadata;
38
- }
39
38
  export function isStatic(arg) {
40
- return (typeof arg == 'function'
41
- && symbol_metadata(arg) in arg
42
- && isValidMetadata(arg[symbol_metadata(arg)]));
39
+ return typeof arg == 'function' && Symbol.metadata in arg && isValidMetadata(arg[Symbol.metadata]);
43
40
  }
44
41
  export function isInstance(arg) {
45
42
  return arg != null && typeof arg == 'object' && isStatic(arg.constructor);
package/dist/struct.d.ts CHANGED
@@ -5,7 +5,7 @@ export * as Struct from './internal/struct.js';
5
5
  /**
6
6
  * Gets the size in bytes of a type
7
7
  */
8
- export declare function sizeof<T extends TypeLike>(type: T): Size<T>;
8
+ export declare function sizeof<T extends TypeLike>(type: T | T[]): Size<T>;
9
9
  /**
10
10
  * Returns the offset (in bytes) of a member in a struct.
11
11
  */
package/dist/struct.js CHANGED
@@ -1,32 +1,23 @@
1
1
  import { toUint8Array } from './buffer.js';
2
+ import { _debugLog } from './debugging.js';
2
3
  import * as primitive from './internal/primitives.js';
3
- import { checkInstance, checkStruct, isCustom, isStatic, symbol_metadata } from './internal/struct.js';
4
+ import { _polyfill_metadata, checkInstance, checkStruct, initMetadata, isCustom, isInstance, isStatic, } from './internal/struct.js';
4
5
  import { _throw } from './misc.js';
5
6
  import { capitalize } from './string.js';
6
7
  export * as Struct from './internal/struct.js';
7
- /**
8
- * Gets the length of an array in a struct
9
- * @param length The numeric length or the name of the field which has the array length (like __counted_by)
10
- * @param name The name of the array field— only used for errors
11
- */
12
- function _memberLength(struct, length, name) {
13
- if (length === undefined)
14
- return -1;
15
- if (typeof length != 'string')
16
- return Number.isSafeInteger(length) && length >= 0
17
- ? length
18
- : _throw(new Error('Array lengths must be natural numbers'));
19
- if (!(length in struct))
20
- throw new Error(`Can not use non-existent member to count ${name}: ` + length);
21
- const n = struct[length];
22
- if (typeof n != 'number')
23
- throw new Error(`Can not use "${name}" to count ${length}`);
24
- return n;
25
- }
26
8
  /**
27
9
  * Gets the size in bytes of a type
28
10
  */
29
11
  export function sizeof(type) {
12
+ if (type === undefined || type === null)
13
+ return 0;
14
+ if (Array.isArray(type)) {
15
+ let size = 0;
16
+ for (let i = 0; i < type.length; i++) {
17
+ size += sizeof(type[i]);
18
+ }
19
+ return size;
20
+ }
30
21
  // primitive
31
22
  if (typeof type == 'string') {
32
23
  primitive.checkValid(type);
@@ -35,16 +26,18 @@ export function sizeof(type) {
35
26
  if (isCustom(type))
36
27
  return type[Symbol.size];
37
28
  checkStruct(type);
38
- const { struct } = isStatic(type)
39
- ? type[symbol_metadata(type)]
40
- : type.constructor[symbol_metadata(type.constructor)];
29
+ const constructor = isStatic(type) ? type : type.constructor;
30
+ _polyfill_metadata(constructor);
31
+ const { struct } = constructor[Symbol.metadata];
41
32
  if (isStatic(type))
42
33
  return struct.staticSize;
43
34
  let size = struct.staticSize;
44
- for (const [name, { type: memberType, length: key }] of struct.members) {
45
- if (typeof key != 'string')
35
+ for (const member of struct.members.values()) {
36
+ if (typeof member.length != 'string')
46
37
  continue;
47
- size += sizeof(memberType) * _memberLength(type, key, name);
38
+ for (let i = 0; i < type[member.length]; i++) {
39
+ size += sizeof(type[member.name][i]);
40
+ }
48
41
  }
49
42
  return size;
50
43
  }
@@ -53,12 +46,20 @@ export function sizeof(type) {
53
46
  */
54
47
  export function offsetof(type, memberName) {
55
48
  checkStruct(type);
56
- const struct = isStatic(type) ? type : type.constructor;
57
- const metadata = struct[symbol_metadata(struct)].struct;
58
- const member = metadata.members.get(memberName);
59
- if (!member)
60
- throw new Error('Struct does not have member: ' + memberName);
61
- return member.offset;
49
+ const constructor = isStatic(type) ? type : type.constructor;
50
+ _polyfill_metadata(constructor);
51
+ const { struct } = constructor[Symbol.metadata];
52
+ if (isStatic(type) || !struct.isDynamic) {
53
+ return (struct.members.get(memberName)?.staticOffset
54
+ ?? _throw(new Error('Struct does not have member: ' + memberName)));
55
+ }
56
+ let offset = 0;
57
+ for (const member of struct.members.values()) {
58
+ if (member.name == memberName)
59
+ return offset;
60
+ offset += sizeof(type[member.name]);
61
+ }
62
+ throw new Error('Struct does not have member: ' + memberName);
62
63
  }
63
64
  /**
64
65
  * Aligns a number
@@ -71,24 +72,35 @@ export function align(value, alignment) {
71
72
  */
72
73
  export function struct(options = {}) {
73
74
  return function _decorateStruct(target, context) {
74
- context.metadata ??= {};
75
- context.metadata.struct ??= {};
76
- context.metadata.struct.init ??= [];
77
- let staticSize = 0;
78
75
  const members = new Map();
79
- for (const { name, type, length } of context.metadata.struct.init) {
76
+ let staticSize = 0, isDynamic = false;
77
+ for (const { name, type, length } of initMetadata(context)) {
80
78
  if (!primitive.isValid(type) && !isStatic(type))
81
79
  throw new TypeError('Not a valid type: ' + type);
80
+ if (typeof length == 'string') {
81
+ const countedBy = members.get(length);
82
+ if (!countedBy)
83
+ throw new Error(`"${length}" is undefined or declared after "${name}"`);
84
+ if (!primitive.isType(countedBy.type))
85
+ throw new Error(`"${length}" is not a number and cannot be used to count "${name}"`);
86
+ }
87
+ let decl = `${typeof type == 'string' ? type : type.name} ${name}`;
88
+ if (length !== undefined)
89
+ decl += `[${length}]`;
82
90
  members.set(name, {
83
- offset: options.isUnion ? 0 : staticSize,
91
+ name,
92
+ staticOffset: options.isUnion ? 0 : staticSize,
84
93
  type: primitive.isValid(type) ? primitive.normalize(type) : type,
85
94
  length,
95
+ decl,
86
96
  });
87
97
  const memberSize = typeof length == 'string' ? 0 : sizeof(type) * (length || 1);
98
+ isDynamic ||= typeof length == 'string';
88
99
  staticSize = options.isUnion ? Math.max(staticSize, memberSize) : staticSize + memberSize;
89
100
  staticSize = align(staticSize, options.align || 1);
101
+ _debugLog('define', target.name + '.' + name);
90
102
  }
91
- context.metadata.struct = { options, members, staticSize };
103
+ context.metadata.struct = { options, members, staticSize, isDynamic };
92
104
  return target;
93
105
  };
94
106
  }
@@ -104,13 +116,20 @@ export function member(type, length) {
104
116
  }
105
117
  if (!name)
106
118
  throw new ReferenceError('Invalid name for struct member');
107
- context.metadata ??= {};
108
- context.metadata.struct ??= {};
109
- context.metadata.struct.init ??= [];
110
- context.metadata.struct.init.push({ name, type, length });
119
+ initMetadata(context).push({ name, type, length });
111
120
  return value;
112
121
  };
113
122
  }
123
+ /** Gets the length of a member */
124
+ function _memberLength(instance, member) {
125
+ if (member.length === undefined)
126
+ return -1;
127
+ if (typeof member.length == 'string')
128
+ return instance[member.length];
129
+ return Number.isSafeInteger(member.length) && member.length >= 0
130
+ ? member.length
131
+ : _throw(new Error('Array lengths must be natural numbers'));
132
+ }
114
133
  /**
115
134
  * Serializes a struct into a Uint8Array
116
135
  */
@@ -118,47 +137,53 @@ export function serialize(instance) {
118
137
  if (isCustom(instance) && typeof instance[Symbol.serialize] == 'function')
119
138
  return instance[Symbol.serialize]();
120
139
  checkInstance(instance);
121
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
122
- const buffer = new Uint8Array(sizeof(instance));
140
+ _polyfill_metadata(instance.constructor);
141
+ const { options, members } = instance.constructor[Symbol.metadata].struct;
142
+ const size = sizeof(instance);
143
+ const buffer = new Uint8Array(size);
123
144
  const view = new DataView(buffer.buffer);
145
+ _debugLog('serialize', instance.constructor.name);
146
+ let offset = 0, nextOffset = 0;
124
147
  // for unions we should write members in ascending last modified order, but we don't have that info.
125
- for (const [name, { type, length: rawLength, offset }] of members) {
126
- const length = _memberLength(instance, rawLength, name);
148
+ for (const member of members.values()) {
149
+ const length = _memberLength(instance, member);
150
+ _debugLog('\t', member.decl);
127
151
  for (let i = 0; i < Math.abs(length); i++) {
128
- const iOff = offset + sizeof(type) * i;
129
- let value = length != -1 ? instance[name][i] : instance[name];
152
+ let value = length != -1 ? instance[member.name][i] : instance[member.name];
130
153
  if (typeof value == 'string') {
131
154
  value = value.charCodeAt(0);
132
155
  }
133
- if (!primitive.isType(type)) {
134
- buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
156
+ offset = nextOffset;
157
+ nextOffset += isInstance(value) ? sizeof(value) : sizeof(member.type);
158
+ if (!primitive.isType(member.type)) {
159
+ buffer.set(value ? serialize(value) : new Uint8Array(sizeof(member.type)), offset);
135
160
  continue;
136
161
  }
137
- const fn = `set${capitalize(type)}`;
162
+ const fn = `set${capitalize(member.type)}`;
138
163
  if (fn == 'setInt64') {
139
- view.setBigInt64(iOff, BigInt(value), !options.bigEndian);
164
+ view.setBigInt64(offset, BigInt(value), !options.bigEndian);
140
165
  continue;
141
166
  }
142
167
  if (fn == 'setUint64') {
143
- view.setBigUint64(iOff, BigInt(value), !options.bigEndian);
168
+ view.setBigUint64(offset, BigInt(value), !options.bigEndian);
144
169
  continue;
145
170
  }
146
171
  if (fn == 'setInt128') {
147
- view.setBigUint64(iOff + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
148
- view.setBigInt64(iOff + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
172
+ view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
173
+ view.setBigInt64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
149
174
  continue;
150
175
  }
151
176
  if (fn == 'setUint128') {
152
- view.setBigUint64(iOff + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
153
- view.setBigUint64(iOff + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
177
+ view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
178
+ view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
154
179
  continue;
155
180
  }
156
181
  if (fn == 'setFloat128') {
157
- view.setFloat64(iOff + (!options.bigEndian ? 0 : 8), Number(value), !options.bigEndian);
158
- view.setBigUint64(iOff + (!options.bigEndian ? 8 : 0), BigInt(0), !options.bigEndian);
182
+ view.setFloat64(offset + (!options.bigEndian ? 0 : 8), Number(value), !options.bigEndian);
183
+ view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), BigInt(0), !options.bigEndian);
159
184
  continue;
160
185
  }
161
- view[fn](iOff, Number(value), !options.bigEndian);
186
+ view[fn](offset, Number(value), !options.bigEndian);
162
187
  }
163
188
  }
164
189
  return buffer;
@@ -171,53 +196,62 @@ export function deserialize(instance, _buffer) {
171
196
  if (isCustom(instance) && typeof instance[Symbol.deserialize] == 'function')
172
197
  return instance[Symbol.deserialize](buffer);
173
198
  checkInstance(instance);
174
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
199
+ _polyfill_metadata(instance.constructor);
200
+ const { options, members } = instance.constructor[Symbol.metadata].struct;
175
201
  const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
176
- for (const [name, { type, offset, length: rawLength }] of members) {
177
- const length = _memberLength(instance, rawLength, name);
202
+ _debugLog('deserialize', instance.constructor.name);
203
+ let offset = 0, nextOffset = 0;
204
+ for (const member of members.values()) {
205
+ const length = _memberLength(instance, member);
206
+ _debugLog('\t', member.decl);
178
207
  for (let i = 0; i < Math.abs(length); i++) {
179
- let object = length != -1 ? instance[name] : instance;
180
- const key = length != -1 ? i : name, iOff = offset + sizeof(type) * i;
181
- if (typeof instance[name] == 'string') {
182
- instance[name] =
183
- instance[name].slice(0, i) + String.fromCharCode(view.getUint8(iOff)) + instance[name].slice(i + 1);
208
+ let object = length != -1 ? instance[member.name] : instance;
209
+ const key = length != -1 ? i : member.name;
210
+ offset = nextOffset;
211
+ if (!isInstance(object[key]))
212
+ nextOffset += sizeof(member.type);
213
+ if (typeof instance[member.name] == 'string') {
214
+ instance[member.name] =
215
+ instance[member.name].slice(0, i)
216
+ + String.fromCharCode(view.getUint8(offset))
217
+ + instance[member.name].slice(i + 1);
184
218
  continue;
185
219
  }
186
- if (!primitive.isType(type)) {
187
- if (object[key] === null || object[key] === undefined) {
220
+ if (!primitive.isType(member.type)) {
221
+ if (object[key] === null || object[key] === undefined)
188
222
  continue;
189
- }
190
- deserialize(object[key], new Uint8Array(buffer.subarray(iOff, iOff + sizeof(type))));
223
+ deserialize(object[key], new Uint8Array(buffer.subarray(offset)));
224
+ nextOffset += sizeof(object[key]);
191
225
  continue;
192
226
  }
193
227
  if (length && length != -1)
194
228
  object ||= [];
195
- const fn = `get${capitalize(type)}`;
229
+ const fn = `get${capitalize(member.type)}`;
196
230
  if (fn == 'getInt64') {
197
- object[key] = view.getBigInt64(iOff, !options.bigEndian);
231
+ object[key] = view.getBigInt64(offset, !options.bigEndian);
198
232
  continue;
199
233
  }
200
234
  if (fn == 'getUint64') {
201
- object[key] = view.getBigUint64(iOff, !options.bigEndian);
235
+ object[key] = view.getBigUint64(offset, !options.bigEndian);
202
236
  continue;
203
237
  }
204
238
  if (fn == 'getInt128') {
205
239
  object[key] =
206
- (view.getBigInt64(iOff + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
207
- | view.getBigUint64(iOff + (!options.bigEndian ? 0 : 8), !options.bigEndian);
240
+ (view.getBigInt64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
241
+ | view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
208
242
  continue;
209
243
  }
210
244
  if (fn == 'getUint128') {
211
245
  object[key] =
212
- (view.getBigUint64(iOff + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
213
- | view.getBigUint64(iOff + (!options.bigEndian ? 0 : 8), !options.bigEndian);
246
+ (view.getBigUint64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
247
+ | view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
214
248
  continue;
215
249
  }
216
250
  if (fn == 'getFloat128') {
217
- object[key] = view.getFloat64(iOff + (!options.bigEndian ? 0 : 8), !options.bigEndian);
251
+ object[key] = view.getFloat64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
218
252
  continue;
219
253
  }
220
- object[key] = view[fn](iOff, !options.bigEndian);
254
+ object[key] = view[fn](offset, !options.bigEndian);
221
255
  }
222
256
  }
223
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utilium",
3
- "version": "1.7.11",
3
+ "version": "1.7.12",
4
4
  "description": "Typescript utilities",
5
5
  "funding": {
6
6
  "type": "individual",
package/src/debugging.ts CHANGED
@@ -92,3 +92,22 @@ export function createLogDecorator(options: CreateLoggerOptions) {
92
92
  } as T;
93
93
  };
94
94
  }
95
+
96
+ /**
97
+ * @internal @hidden
98
+ */
99
+ export let U_DEBUG = 'process' in globalThis && 'env' in globalThis.process && globalThis.process.env.U_DEBUG == 'true';
100
+
101
+ /**
102
+ * @internal @hidden
103
+ */
104
+ export function _setDebug(value: boolean) {
105
+ U_DEBUG = value;
106
+ }
107
+
108
+ /**
109
+ * @internal @hidden
110
+ */
111
+ export function _debugLog(...text: any[]) {
112
+ if (U_DEBUG) console.debug('[U]', ...text);
113
+ }
@@ -14,13 +14,19 @@ declare global {
14
14
  }
15
15
  }
16
16
 
17
+ /**
18
+ * Polyfill Symbol.metadata
19
+ * @see https://github.com/microsoft/TypeScript/issues/53461
20
+ */
21
+ (Symbol as { metadata: symbol }).metadata ??= Symbol.for('Symbol.metadata');
22
+
17
23
  Object.assign(Symbol, {
18
24
  size: Symbol('uSize'),
19
25
  serialize: Symbol('uSerialize'),
20
26
  deserialize: Symbol('uDeserialize'),
21
27
  });
22
28
 
23
- export type TypeLike = Custom | Like | primitive.Valid;
29
+ export type TypeLike = Custom | Like | primitive.Valid | undefined | null;
24
30
 
25
31
  export type Type = Custom | Static | primitive.Typename;
26
32
 
@@ -44,26 +50,43 @@ export interface Options {
44
50
  }
45
51
 
46
52
  export interface Member {
53
+ name: string;
47
54
  type: Type;
48
- offset: number;
55
+ staticOffset: number;
49
56
  length?: number | string;
57
+
58
+ /** A C-style type/name declaration string, used for diagnostics */
59
+ decl: string;
50
60
  }
51
61
 
52
62
  export interface Metadata {
53
63
  options: Partial<Options>;
54
64
  members: Map<string, Member>;
55
- init?: MemberInit[];
56
65
  staticSize: number;
66
+ isDynamic: boolean;
57
67
  }
58
68
 
59
69
  type _DecoratorMetadata<T extends Metadata = Metadata> = DecoratorMetadata & {
60
- struct?: Partial<T>;
70
+ struct?: T;
71
+ structInit?: MemberInit[];
61
72
  };
62
73
 
63
74
  export interface DecoratorContext<T extends Metadata = Metadata> {
64
75
  metadata: _DecoratorMetadata<T>;
65
76
  }
66
77
 
78
+ /**
79
+ * Initializes the struct metadata for a class
80
+ * This also handles copying metadata from parent classes
81
+ */
82
+ export function initMetadata(context: DecoratorContext): MemberInit[] {
83
+ context.metadata ??= {};
84
+
85
+ context.metadata.structInit = [...(context.metadata.structInit ?? [])];
86
+
87
+ return context.metadata.structInit;
88
+ }
89
+
67
90
  export type MemberContext = ClassMemberDecoratorContext & DecoratorContext;
68
91
 
69
92
  export interface Static<T extends Metadata = Metadata> {
@@ -84,18 +107,13 @@ export function isValidMetadata<T extends Metadata = Metadata>(
84
107
  return arg != null && typeof arg == 'object' && 'struct' in arg;
85
108
  }
86
109
 
87
- /**
88
- * Polyfill Symbol.metadata
89
- * @see https://github.com/microsoft/TypeScript/issues/53461
90
- */
91
- (Symbol as { metadata: symbol }).metadata ??= Symbol.for('Symbol.metadata');
92
-
93
110
  /**
94
111
  * Polyfill context.metadata
95
112
  * @see https://github.com/microsoft/TypeScript/issues/53461
113
+ * @internal @hidden
96
114
  */
97
- export function _polyfill_contextMetadata(target: object): void {
98
- if (!Symbol?.metadata || Symbol.metadata in target) return;
115
+ export function _polyfill_metadata(target: object): void {
116
+ if (Symbol.metadata in target) return;
99
117
 
100
118
  Object.defineProperty(target, Symbol.metadata, {
101
119
  enumerable: true,
@@ -105,26 +123,8 @@ export function _polyfill_contextMetadata(target: object): void {
105
123
  });
106
124
  }
107
125
 
108
- /**
109
- * Gets a reference to Symbol.metadata, even on platforms that do not expose it globally (like Node)
110
- */
111
- export function symbol_metadata(arg: ClassLike): typeof Symbol.metadata {
112
- const symbol_metadata =
113
- Symbol.metadata || Object.getOwnPropertySymbols(arg).find(s => s.description == 'Symbol.metadata');
114
- _polyfill_contextMetadata(arg);
115
- if (!symbol_metadata) {
116
- throw new ReferenceError('Could not get a reference to Symbol.metadata');
117
- }
118
-
119
- return symbol_metadata as typeof Symbol.metadata;
120
- }
121
-
122
126
  export function isStatic<T extends Metadata = Metadata>(arg: unknown): arg is Static<T> {
123
- return (
124
- typeof arg == 'function'
125
- && symbol_metadata(arg as ClassLike) in arg
126
- && isValidMetadata(arg[symbol_metadata(arg as ClassLike)])
127
- );
127
+ return typeof arg == 'function' && Symbol.metadata in arg && isValidMetadata(arg[Symbol.metadata]);
128
128
  }
129
129
 
130
130
  export interface Instance<T extends Metadata = Metadata> {
@@ -176,8 +176,10 @@ export function isCustom(arg: unknown): arg is Custom {
176
176
 
177
177
  export type Like<T extends Metadata = Metadata> = InstanceLike<T> | StaticLike<T>;
178
178
 
179
- export type Size<T extends TypeLike> = T extends { readonly [Symbol.size]: infer S }
180
- ? S
181
- : T extends primitive.Valid
182
- ? primitive.Size<T>
183
- : number;
179
+ export type Size<T extends TypeLike> = T extends undefined | null
180
+ ? 0
181
+ : T extends { readonly [Symbol.size]: infer S extends number }
182
+ ? S
183
+ : T extends primitive.Valid
184
+ ? primitive.Size<T>
185
+ : number;
package/src/struct.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { toUint8Array } from './buffer.js';
2
+ import { _debugLog } from './debugging.js';
2
3
  import * as primitive from './internal/primitives.js';
3
4
  import type {
4
5
  DecoratorContext,
@@ -10,45 +11,39 @@ import type {
10
11
  Metadata,
11
12
  Options,
12
13
  Size,
13
- Static,
14
14
  StaticLike,
15
15
  TypeLike,
16
16
  } from './internal/struct.js';
17
- import { checkInstance, checkStruct, isCustom, isStatic, symbol_metadata } from './internal/struct.js';
17
+ import {
18
+ _polyfill_metadata,
19
+ checkInstance,
20
+ checkStruct,
21
+ initMetadata,
22
+ isCustom,
23
+ isInstance,
24
+ isStatic,
25
+ } from './internal/struct.js';
18
26
  import { _throw } from './misc.js';
19
27
  import { capitalize } from './string.js';
20
28
  import type { ClassLike } from './types.js';
21
29
  export * as Struct from './internal/struct.js';
22
30
 
23
31
  /**
24
- * Gets the length of an array in a struct
25
- * @param length The numeric length or the name of the field which has the array length (like __counted_by)
26
- * @param name The name of the array field— only used for errors
32
+ * Gets the size in bytes of a type
27
33
  */
28
- function _memberLength<T extends Metadata>(
29
- struct: Instance<T>,
30
- length: string | number | undefined,
31
- name: string
32
- ): number {
33
- if (length === undefined) return -1;
34
- if (typeof length != 'string')
35
- return Number.isSafeInteger(length) && length >= 0
36
- ? length
37
- : _throw(new Error('Array lengths must be natural numbers'));
38
-
39
- if (!(length in struct)) throw new Error(`Can not use non-existent member to count ${name}: ` + length);
34
+ export function sizeof<T extends TypeLike>(type: T | T[]): Size<T> {
35
+ if (type === undefined || type === null) return 0 as Size<T>;
40
36
 
41
- const n = (struct as any)[length];
37
+ if (Array.isArray(type)) {
38
+ let size = 0;
42
39
 
43
- if (typeof n != 'number') throw new Error(`Can not use "${name}" to count ${length}`);
40
+ for (let i = 0; i < type.length; i++) {
41
+ size += sizeof(type[i]);
42
+ }
44
43
 
45
- return n;
46
- }
44
+ return size as Size<T>;
45
+ }
47
46
 
48
- /**
49
- * Gets the size in bytes of a type
50
- */
51
- export function sizeof<T extends TypeLike>(type: T): Size<T> {
52
47
  // primitive
53
48
  if (typeof type == 'string') {
54
49
  primitive.checkValid(type);
@@ -60,17 +55,19 @@ export function sizeof<T extends TypeLike>(type: T): Size<T> {
60
55
 
61
56
  checkStruct(type);
62
57
 
63
- const { struct } = isStatic(type)
64
- ? type[symbol_metadata(type)]
65
- : type.constructor[symbol_metadata(type.constructor)];
58
+ const constructor = isStatic(type) ? type : type.constructor;
59
+ _polyfill_metadata(constructor);
60
+ const { struct } = constructor[Symbol.metadata];
66
61
 
67
62
  if (isStatic(type)) return struct.staticSize as Size<T>;
68
63
 
69
64
  let size = struct.staticSize;
70
65
 
71
- for (const [name, { type: memberType, length: key }] of struct.members) {
72
- if (typeof key != 'string') continue;
73
- size += sizeof(memberType) * _memberLength(type, key, name);
66
+ for (const member of struct.members.values()) {
67
+ if (typeof member.length != 'string') continue;
68
+ for (let i = 0; i < (type as any)[member.length]; i++) {
69
+ size += sizeof((type as any)[member.name][i]);
70
+ }
74
71
  }
75
72
 
76
73
  return size as Size<T>;
@@ -82,12 +79,26 @@ export function sizeof<T extends TypeLike>(type: T): Size<T> {
82
79
  export function offsetof(type: StaticLike | InstanceLike, memberName: string): number {
83
80
  checkStruct(type);
84
81
 
85
- const struct = isStatic(type) ? type : type.constructor;
86
- const metadata = struct[symbol_metadata(struct)].struct;
82
+ const constructor = isStatic(type) ? type : type.constructor;
83
+
84
+ _polyfill_metadata(constructor);
85
+ const { struct } = constructor[Symbol.metadata];
87
86
 
88
- const member = metadata.members.get(memberName);
89
- if (!member) throw new Error('Struct does not have member: ' + memberName);
90
- return member.offset;
87
+ if (isStatic(type) || !struct.isDynamic) {
88
+ return (
89
+ struct.members.get(memberName)?.staticOffset
90
+ ?? _throw(new Error('Struct does not have member: ' + memberName))
91
+ );
92
+ }
93
+
94
+ let offset = 0;
95
+
96
+ for (const member of struct.members.values()) {
97
+ if (member.name == memberName) return offset;
98
+ offset += sizeof((type as any)[member.name]);
99
+ }
100
+
101
+ throw new Error('Struct does not have member: ' + memberName);
91
102
  }
92
103
 
93
104
  /**
@@ -105,26 +116,44 @@ export function struct(options: Partial<Options> = {}) {
105
116
  target: T,
106
117
  context: ClassDecoratorContext & DecoratorContext
107
118
  ): T {
108
- context.metadata ??= {};
109
- context.metadata.struct ??= {};
110
- context.metadata.struct.init ??= [];
111
-
112
- let staticSize = 0;
113
119
  const members = new Map<string, Member>();
114
- for (const { name, type, length } of context.metadata.struct.init) {
120
+
121
+ let staticSize = 0,
122
+ isDynamic = false;
123
+
124
+ for (const { name, type, length } of initMetadata(context)) {
115
125
  if (!primitive.isValid(type) && !isStatic(type)) throw new TypeError('Not a valid type: ' + type);
116
126
 
127
+ if (typeof length == 'string') {
128
+ const countedBy = members.get(length);
129
+
130
+ if (!countedBy) throw new Error(`"${length}" is undefined or declared after "${name}"`);
131
+
132
+ if (!primitive.isType(countedBy.type))
133
+ throw new Error(`"${length}" is not a number and cannot be used to count "${name}"`);
134
+ }
135
+
136
+ let decl = `${typeof type == 'string' ? type : type.name} ${name}`;
137
+
138
+ if (length !== undefined) decl += `[${length}]`;
139
+
117
140
  members.set(name, {
118
- offset: options.isUnion ? 0 : staticSize,
141
+ name,
142
+ staticOffset: options.isUnion ? 0 : staticSize,
119
143
  type: primitive.isValid(type) ? primitive.normalize(type) : type,
120
144
  length,
145
+ decl,
121
146
  });
147
+
122
148
  const memberSize = typeof length == 'string' ? 0 : sizeof(type) * (length || 1);
149
+ isDynamic ||= typeof length == 'string';
123
150
  staticSize = options.isUnion ? Math.max(staticSize, memberSize) : staticSize + memberSize;
124
151
  staticSize = align(staticSize, options.align || 1);
152
+
153
+ _debugLog('define', target.name + '.' + name);
125
154
  }
126
155
 
127
- context.metadata.struct = { options, members, staticSize } satisfies Metadata;
156
+ context.metadata.struct = { options, members, staticSize, isDynamic } satisfies Metadata;
128
157
 
129
158
  return target;
130
159
  };
@@ -143,14 +172,20 @@ export function member(type: primitive.Valid | ClassLike, length?: number | stri
143
172
 
144
173
  if (!name) throw new ReferenceError('Invalid name for struct member');
145
174
 
146
- context.metadata ??= {};
147
- context.metadata.struct ??= {};
148
- context.metadata.struct.init ??= [];
149
- context.metadata.struct.init.push({ name, type, length } satisfies MemberInit);
175
+ initMetadata(context).push({ name, type, length } satisfies MemberInit);
150
176
  return value;
151
177
  };
152
178
  }
153
179
 
180
+ /** Gets the length of a member */
181
+ function _memberLength<T extends Metadata>(instance: Instance<T>, member: Member): number {
182
+ if (member.length === undefined) return -1;
183
+ if (typeof member.length == 'string') return (instance as any)[member.length];
184
+ return Number.isSafeInteger(member.length) && member.length >= 0
185
+ ? member.length
186
+ : _throw(new Error('Array lengths must be natural numbers'));
187
+ }
188
+
154
189
  /**
155
190
  * Serializes a struct into a Uint8Array
156
191
  */
@@ -158,58 +193,69 @@ export function serialize(instance: unknown): Uint8Array {
158
193
  if (isCustom(instance) && typeof instance[Symbol.serialize] == 'function') return instance[Symbol.serialize]!();
159
194
 
160
195
  checkInstance(instance);
161
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
196
+ _polyfill_metadata(instance.constructor);
197
+ const { options, members } = instance.constructor[Symbol.metadata].struct;
162
198
 
163
- const buffer = new Uint8Array(sizeof(instance));
199
+ const size = sizeof(instance);
200
+ const buffer = new Uint8Array(size);
164
201
  const view = new DataView(buffer.buffer);
165
202
 
203
+ _debugLog('serialize', instance.constructor.name);
204
+
205
+ let offset = 0,
206
+ nextOffset = 0;
207
+
166
208
  // for unions we should write members in ascending last modified order, but we don't have that info.
167
- for (const [name, { type, length: rawLength, offset }] of members) {
168
- const length = _memberLength(instance, rawLength, name);
169
- for (let i = 0; i < Math.abs(length); i++) {
170
- const iOff = offset + sizeof(type) * i;
209
+ for (const member of members.values()) {
210
+ const length = _memberLength(instance, member);
211
+
212
+ _debugLog('\t', member.decl);
171
213
 
172
- let value = length != -1 ? instance[name][i] : instance[name];
214
+ for (let i = 0; i < Math.abs(length); i++) {
215
+ let value = length != -1 ? instance[member.name][i] : instance[member.name];
173
216
  if (typeof value == 'string') {
174
217
  value = value.charCodeAt(0);
175
218
  }
176
219
 
177
- if (!primitive.isType(type)) {
178
- buffer.set(value ? serialize(value) : new Uint8Array(sizeof(type)), iOff);
220
+ offset = nextOffset;
221
+ nextOffset += isInstance(value) ? sizeof(value) : sizeof(member.type);
222
+
223
+ if (!primitive.isType(member.type)) {
224
+ buffer.set(value ? serialize(value) : new Uint8Array(sizeof(member.type)), offset);
179
225
  continue;
180
226
  }
181
227
 
182
- const fn = `set${capitalize(type)}` as const;
228
+ const fn = `set${capitalize(member.type)}` as const;
183
229
 
184
230
  if (fn == 'setInt64') {
185
- view.setBigInt64(iOff, BigInt(value), !options.bigEndian);
231
+ view.setBigInt64(offset, BigInt(value), !options.bigEndian);
186
232
  continue;
187
233
  }
188
234
 
189
235
  if (fn == 'setUint64') {
190
- view.setBigUint64(iOff, BigInt(value), !options.bigEndian);
236
+ view.setBigUint64(offset, BigInt(value), !options.bigEndian);
191
237
  continue;
192
238
  }
193
239
 
194
240
  if (fn == 'setInt128') {
195
- view.setBigUint64(iOff + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
196
- view.setBigInt64(iOff + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
241
+ view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
242
+ view.setBigInt64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
197
243
  continue;
198
244
  }
199
245
 
200
246
  if (fn == 'setUint128') {
201
- view.setBigUint64(iOff + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
202
- view.setBigUint64(iOff + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
247
+ view.setBigUint64(offset + (!options.bigEndian ? 0 : 8), value & primitive.mask64, !options.bigEndian);
248
+ view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), value >> BigInt(64), !options.bigEndian);
203
249
  continue;
204
250
  }
205
251
 
206
252
  if (fn == 'setFloat128') {
207
- view.setFloat64(iOff + (!options.bigEndian ? 0 : 8), Number(value), !options.bigEndian);
208
- view.setBigUint64(iOff + (!options.bigEndian ? 8 : 0), BigInt(0), !options.bigEndian);
253
+ view.setFloat64(offset + (!options.bigEndian ? 0 : 8), Number(value), !options.bigEndian);
254
+ view.setBigUint64(offset + (!options.bigEndian ? 8 : 0), BigInt(0), !options.bigEndian);
209
255
  continue;
210
256
  }
211
257
 
212
- view[fn](iOff, Number(value), !options.bigEndian);
258
+ view[fn](offset, Number(value), !options.bigEndian);
213
259
  }
214
260
  }
215
261
 
@@ -226,64 +272,77 @@ export function deserialize(instance: unknown, _buffer: ArrayBufferLike | ArrayB
226
272
  return instance[Symbol.deserialize]!(buffer);
227
273
 
228
274
  checkInstance(instance);
229
- const { options, members } = instance.constructor[symbol_metadata(instance.constructor)].struct;
275
+ _polyfill_metadata(instance.constructor);
276
+ const { options, members } = instance.constructor[Symbol.metadata].struct;
230
277
 
231
278
  const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
232
279
 
233
- for (const [name, { type, offset, length: rawLength }] of members) {
234
- const length = _memberLength(instance, rawLength, name);
280
+ _debugLog('deserialize', instance.constructor.name);
281
+
282
+ let offset = 0,
283
+ nextOffset = 0;
284
+
285
+ for (const member of members.values()) {
286
+ const length = _memberLength(instance, member);
287
+
288
+ _debugLog('\t', member.decl);
289
+
235
290
  for (let i = 0; i < Math.abs(length); i++) {
236
- let object = length != -1 ? instance[name] : instance;
237
- const key = length != -1 ? i : name,
238
- iOff = offset + sizeof(type) * i;
291
+ let object = length != -1 ? instance[member.name] : instance;
292
+ const key = length != -1 ? i : member.name;
239
293
 
240
- if (typeof instance[name] == 'string') {
241
- instance[name] =
242
- instance[name].slice(0, i) + String.fromCharCode(view.getUint8(iOff)) + instance[name].slice(i + 1);
294
+ offset = nextOffset;
295
+ if (!isInstance(object[key])) nextOffset += sizeof(member.type);
296
+
297
+ if (typeof instance[member.name] == 'string') {
298
+ instance[member.name] =
299
+ instance[member.name].slice(0, i)
300
+ + String.fromCharCode(view.getUint8(offset))
301
+ + instance[member.name].slice(i + 1);
243
302
  continue;
244
303
  }
245
304
 
246
- if (!primitive.isType(type)) {
247
- if (object[key] === null || object[key] === undefined) {
248
- continue;
249
- }
250
- deserialize(object[key], new Uint8Array(buffer.subarray(iOff, iOff + sizeof(type))));
305
+ if (!primitive.isType(member.type)) {
306
+ if (object[key] === null || object[key] === undefined) continue;
307
+
308
+ deserialize(object[key], new Uint8Array(buffer.subarray(offset)));
309
+ nextOffset += sizeof(object[key]);
251
310
  continue;
252
311
  }
253
312
 
254
313
  if (length && length != -1) object ||= [];
255
314
 
256
- const fn = `get${capitalize(type)}` as const;
315
+ const fn = `get${capitalize(member.type)}` as const;
257
316
  if (fn == 'getInt64') {
258
- object[key] = view.getBigInt64(iOff, !options.bigEndian);
317
+ object[key] = view.getBigInt64(offset, !options.bigEndian);
259
318
  continue;
260
319
  }
261
320
 
262
321
  if (fn == 'getUint64') {
263
- object[key] = view.getBigUint64(iOff, !options.bigEndian);
322
+ object[key] = view.getBigUint64(offset, !options.bigEndian);
264
323
  continue;
265
324
  }
266
325
 
267
326
  if (fn == 'getInt128') {
268
327
  object[key] =
269
- (view.getBigInt64(iOff + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
270
- | view.getBigUint64(iOff + (!options.bigEndian ? 0 : 8), !options.bigEndian);
328
+ (view.getBigInt64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
329
+ | view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
271
330
  continue;
272
331
  }
273
332
 
274
333
  if (fn == 'getUint128') {
275
334
  object[key] =
276
- (view.getBigUint64(iOff + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
277
- | view.getBigUint64(iOff + (!options.bigEndian ? 0 : 8), !options.bigEndian);
335
+ (view.getBigUint64(offset + (!options.bigEndian ? 8 : 0), !options.bigEndian) << BigInt(64))
336
+ | view.getBigUint64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
278
337
  continue;
279
338
  }
280
339
 
281
340
  if (fn == 'getFloat128') {
282
- object[key] = view.getFloat64(iOff + (!options.bigEndian ? 0 : 8), !options.bigEndian);
341
+ object[key] = view.getFloat64(offset + (!options.bigEndian ? 0 : 8), !options.bigEndian);
283
342
  continue;
284
343
  }
285
344
 
286
- object[key] = view[fn](iOff, !options.bigEndian);
345
+ object[key] = view[fn](offset, !options.bigEndian);
287
346
  }
288
347
  }
289
348
  }