node-ctypes 1.2.0 → 1.3.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.
@@ -69,10 +69,11 @@
69
69
  *
70
70
  * @param {Function} alloc - Memory allocation function
71
71
  * @param {Function} struct - Struct definition function
72
+ * @param {Function} bitfield - Bitfield helper function for Python-style [name, type, bits] syntax
72
73
  * @returns {Class} Structure base class
73
74
  * @private
74
75
  */
75
- export function createStructureClass(alloc, struct) {
76
+ export function createStructureClass(alloc, struct, bitfield) {
76
77
  /**
77
78
  * Structure base class for creating C-style structures.
78
79
  *
@@ -135,280 +136,310 @@ export function createStructureClass(alloc, struct) {
135
136
  * c.radius = 5;
136
137
  * ```
137
138
  */
138
- class Structure {
139
- constructor(...args) {
140
- const Ctor = this.constructor;
141
- const def = Ctor._structDef || Ctor._buildStruct();
142
- // Allocate buffer for instance
143
- let buf = alloc(def.size);
144
- buf.fill(0);
145
-
146
- // Support positional args: new Point(10,20)
147
- if (args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) && !Buffer.isBuffer(args[0])) {
148
- const initial = args[0];
149
- for (const [k, v] of Object.entries(initial)) {
150
- def.set(buf, k, v);
151
- }
152
- } else if (args.length === 1 && Buffer.isBuffer(args[0])) {
153
- // new Point(buffer) - wrap existing buffer
154
- buf = args[0];
155
- // Validate buffer size matches struct size
156
- if (buf.length < def.size) {
157
- throw new RangeError(`Buffer size (${buf.length}) is smaller than struct size (${def.size})`);
158
- }
159
- } else if (args.length > 0) {
160
- // Positional mapping to non-anonymous declared fields order
161
- const ordered = def.fields.filter((f) => !f.isAnonymous);
162
- for (let i = 0; i < Math.min(args.length, ordered.length); i++) {
163
- def.set(buf, ordered[i].name, args[i]);
139
+ class Structure {
140
+ constructor(...args) {
141
+ const Ctor = this.constructor;
142
+ const def = Ctor._structDef || Ctor._buildStruct();
143
+ // Allocate buffer for instance
144
+ let buf = alloc(def.size);
145
+ buf.fill(0);
146
+
147
+ // Support positional args: new Point(10,20)
148
+ if (args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0]) && !Buffer.isBuffer(args[0])) {
149
+ const initial = args[0];
150
+ for (const [k, v] of Object.entries(initial)) {
151
+ def.set(buf, k, v);
152
+ }
153
+ } else if (args.length === 1 && Buffer.isBuffer(args[0])) {
154
+ // new Point(buffer) - wrap existing buffer
155
+ buf = args[0];
156
+ // Validate buffer size matches struct size
157
+ if (buf.length < def.size) {
158
+ throw new RangeError(`Buffer size (${buf.length}) is smaller than struct size (${def.size})`);
159
+ }
160
+ } else if (args.length > 0) {
161
+ // Positional mapping to non-anonymous declared fields order
162
+ const ordered = def.fields.filter((f) => !f.isAnonymous);
163
+ for (let i = 0; i < Math.min(args.length, ordered.length); i++) {
164
+ def.set(buf, ordered[i].name, args[i]);
165
+ }
164
166
  }
165
- }
166
167
 
167
- // Store internal references on this (needed for methods like toObject)
168
- this.__buffer = buf;
169
- this.__structDef = def;
170
-
171
- // Build field map for O(1) lookup
172
- const fieldMap = new Map();
173
- const anonFieldNames = new Set();
174
- for (const field of def.fields) {
175
- fieldMap.set(field.name, field);
176
- if (field.isAnonymous && field.type && Array.isArray(field.type.fields)) {
177
- for (const subField of field.type.fields) {
178
- anonFieldNames.add(subField.name);
168
+ // Store internal references on this (needed for methods like toObject)
169
+ this.__buffer = buf;
170
+ this.__structDef = def;
171
+
172
+ // Build field map for O(1) lookup
173
+ const fieldMap = new Map();
174
+ const anonFieldNames = new Set();
175
+ for (const field of def.fields) {
176
+ fieldMap.set(field.name, field);
177
+ if (field.isAnonymous && field.type && Array.isArray(field.type.fields)) {
178
+ for (const subField of field.type.fields) {
179
+ anonFieldNames.add(subField.name);
180
+ }
179
181
  }
180
182
  }
181
- }
182
183
 
183
- // Return Proxy instead of using Object.defineProperty for each field
184
- return new Proxy(this, {
185
- get(target, prop, receiver) {
186
- // Special properties that exist on the instance
187
- if (prop === "_buffer") return buf;
188
- if (prop === "_structDef") return def;
189
- if (prop === "__buffer") return buf;
190
- if (prop === "__structDef") return def;
191
- if (prop === Symbol.toStringTag) return Ctor.name || "Structure";
192
- if (prop === Symbol.iterator) return undefined;
193
-
194
- // Methods defined on the prototype
195
- if (typeof target[prop] === "function") {
196
- return target[prop].bind(target);
197
- }
184
+ // Return Proxy instead of using Object.defineProperty for each field
185
+ // Pre-compute fast lookup set for all field names (including anonymous)
186
+ const allFieldNames = new Set(fieldMap.keys());
187
+ for (const name of anonFieldNames) allFieldNames.add(name);
188
+
189
+ return new Proxy(this, {
190
+ get(target, prop, receiver) {
191
+ // FAST PATH: struct field access (most common case)
192
+ // Check string type first, then Set lookup - both are O(1)
193
+ if (typeof prop === "string" && allFieldNames.has(prop)) {
194
+ return def.get(buf, prop);
195
+ }
198
196
 
199
- // Check if it's a known field (O(1) lookup)
200
- if (typeof prop === "string" && fieldMap.has(prop)) {
201
- return def.get(buf, prop);
202
- }
197
+ // SLOW PATH: special properties and methods
198
+ // Symbol check (Symbol.toStringTag, Symbol.iterator)
199
+ if (typeof prop === "symbol") {
200
+ if (prop === Symbol.toStringTag) return Ctor.name || "Structure";
201
+ return undefined;
202
+ }
203
203
 
204
- // Check anonymous fields
205
- if (typeof prop === "string" && anonFieldNames.has(prop)) {
206
- return def.get(buf, prop);
207
- }
204
+ // Internal buffer access
205
+ if (prop === "_buffer" || prop === "__buffer") return buf;
206
+ if (prop === "_structDef" || prop === "__structDef") return def;
208
207
 
209
- // Fallback to target properties
210
- return Reflect.get(target, prop, receiver);
211
- },
212
- set(target, prop, value, receiver) {
213
- // Check if it's a known field (O(1) lookup)
214
- if (typeof prop === "string" && fieldMap.has(prop)) {
215
- def.set(buf, prop, value);
216
- return true;
217
- }
208
+ // Methods defined on the prototype
209
+ const method = target[prop];
210
+ if (typeof method === "function") {
211
+ return method.bind(target);
212
+ }
218
213
 
219
- // Check anonymous fields
220
- if (typeof prop === "string" && anonFieldNames.has(prop)) {
221
- def.set(buf, prop, value);
222
- return true;
223
- }
214
+ // Fallback to target properties
215
+ return Reflect.get(target, prop, receiver);
216
+ },
217
+ set(target, prop, value, receiver) {
218
+ // FAST PATH: struct field access (most common case)
219
+ if (typeof prop === "string" && allFieldNames.has(prop)) {
220
+ def.set(buf, prop, value);
221
+ return true;
222
+ }
224
223
 
225
- // Fallback
226
- return Reflect.set(target, prop, value, receiver);
227
- },
228
- has(target, prop) {
229
- if (prop === "_buffer" || prop === "_structDef" || prop === "toObject") return true;
230
- if (typeof prop === "string" && fieldMap.has(prop)) return true;
231
- if (typeof prop === "string" && anonFieldNames.has(prop)) return true;
232
- return Reflect.has(target, prop);
233
- },
234
- ownKeys(target) {
235
- const keys = [];
236
- for (const f of def.fields) {
237
- if (f.isAnonymous && f.type && Array.isArray(f.type.fields)) {
238
- for (const sf of f.type.fields) {
239
- keys.push(sf.name);
224
+ // Fallback
225
+ return Reflect.set(target, prop, value, receiver);
226
+ },
227
+ has(target, prop) {
228
+ if (typeof prop === "string" && allFieldNames.has(prop)) return true;
229
+ if (prop === "_buffer" || prop === "_structDef" || prop === "toObject") return true;
230
+ return Reflect.has(target, prop);
231
+ },
232
+ ownKeys(target) {
233
+ const keys = [];
234
+ for (const f of def.fields) {
235
+ if (f.isAnonymous && f.type && Array.isArray(f.type.fields)) {
236
+ for (const sf of f.type.fields) {
237
+ keys.push(sf.name);
238
+ }
239
+ } else {
240
+ keys.push(f.name);
240
241
  }
241
- } else {
242
- keys.push(f.name);
243
242
  }
244
- }
245
- return keys;
246
- },
247
- getOwnPropertyDescriptor(target, prop) {
248
- if (typeof prop === "string" && (fieldMap.has(prop) || anonFieldNames.has(prop))) {
249
- return {
250
- enumerable: true,
251
- configurable: true,
252
- writable: true,
253
- };
254
- }
255
- if (prop === "_buffer" || prop === "_structDef") {
256
- return {
257
- enumerable: false,
258
- configurable: true,
259
- writable: true,
260
- };
261
- }
262
- return Reflect.getOwnPropertyDescriptor(target, prop);
263
- },
264
- });
265
- }
243
+ return keys;
244
+ },
245
+ getOwnPropertyDescriptor(target, prop) {
246
+ if (typeof prop === "string" && allFieldNames.has(prop)) {
247
+ return {
248
+ enumerable: true,
249
+ configurable: true,
250
+ writable: true,
251
+ };
252
+ }
253
+ if (prop === "_buffer" || prop === "_structDef") {
254
+ return {
255
+ enumerable: false,
256
+ configurable: true,
257
+ writable: true,
258
+ };
259
+ }
260
+ return Reflect.getOwnPropertyDescriptor(target, prop);
261
+ },
262
+ });
263
+ }
266
264
 
267
- // Build the underlying struct definition and cache it on the constructor
268
- static _buildStruct() {
269
- // Accept either array _fields_ (Python style) or object map
270
- const fields = this._fields_ || {};
271
- let mapFields = {};
265
+ // Static getters for size, alignment, and fields (enables use as nested type)
266
+ static get size() {
267
+ const def = this._structDef || this._buildStruct();
268
+ return def.size;
269
+ }
272
270
 
273
- if (Array.isArray(fields)) {
274
- for (const entry of fields) {
275
- if (Array.isArray(entry) && entry.length >= 2) {
276
- mapFields[entry[0]] = entry[1];
277
- }
278
- }
279
- } else if (typeof fields === "object" && fields !== null) {
280
- mapFields = { ...fields };
271
+ static get alignment() {
272
+ const def = this._structDef || this._buildStruct();
273
+ return def.alignment;
274
+ }
275
+
276
+ static get fields() {
277
+ const def = this._structDef || this._buildStruct();
278
+ return def.fields;
279
+ }
280
+
281
+ static get create() {
282
+ const def = this._structDef || this._buildStruct();
283
+ return def.create.bind(def);
281
284
  }
282
285
 
283
- // Handle anonymous fields list: convert to { anonymous: true, type: T }
284
- if (Array.isArray(this._anonymous_)) {
285
- for (const anonName of this._anonymous_) {
286
- if (mapFields[anonName] !== undefined) {
287
- mapFields[anonName] = {
288
- anonymous: true,
289
- type: mapFields[anonName],
290
- };
286
+ // Build the underlying struct definition and cache it on the constructor
287
+ static _buildStruct() {
288
+ // Accept either array _fields_ (Python style) or object map
289
+ const fields = this._fields_ || {};
290
+ let mapFields = {};
291
+
292
+ if (Array.isArray(fields)) {
293
+ for (const entry of fields) {
294
+ if (Array.isArray(entry) && entry.length >= 2) {
295
+ const name = entry[0];
296
+ const type = entry[1];
297
+ // Python-style bitfield: [name, type, bits]
298
+ if (entry.length >= 3 && typeof entry[2] === "number") {
299
+ mapFields[name] = bitfield(type, entry[2]);
300
+ } else {
301
+ mapFields[name] = type;
302
+ }
303
+ }
291
304
  }
305
+ } else if (typeof fields === "object" && fields !== null) {
306
+ mapFields = { ...fields };
292
307
  }
293
- }
294
308
 
295
- const options = {};
296
- if (this._pack_ !== undefined) options.packed = !!this._pack_;
309
+ // Handle anonymous fields list: convert to { anonymous: true, type: T }
310
+ if (Array.isArray(this._anonymous_)) {
311
+ for (const anonName of this._anonymous_) {
312
+ if (mapFields[anonName] !== undefined) {
313
+ let anonType = mapFields[anonName];
314
+ // Normalize type: if it's a Structure/Union class, get its _structDef
315
+ if (typeof anonType === "function" && typeof anonType._buildStruct === "function") {
316
+ anonType = anonType._structDef || anonType._buildStruct();
317
+ }
318
+ mapFields[anonName] = {
319
+ anonymous: true,
320
+ type: anonType,
321
+ };
322
+ }
323
+ }
324
+ }
297
325
 
298
- const def = struct(mapFields, options);
299
- this._structDef = def;
300
- return def;
301
- }
326
+ const options = {};
327
+ if (this._pack_ !== undefined) options.packed = !!this._pack_;
302
328
 
303
- // Create returns an instance of the JS class (not a plain object)
304
- static create(values = {}) {
305
- const def = this._structDef || this._buildStruct();
306
- // If a Buffer provided, wrap it
307
- if (Buffer.isBuffer(values)) {
308
- const inst = new this(values);
309
- return inst;
329
+ const def = struct(mapFields, options);
330
+ this._structDef = def;
331
+ return def;
310
332
  }
311
- // If values is instance of this class, return it
312
- if (values && values._buffer && values._structDef === def) {
313
- return values;
333
+
334
+ // Create returns an instance of the JS class (not a plain object)
335
+ static create(values = {}) {
336
+ const def = this._structDef || this._buildStruct();
337
+ // If a Buffer provided, wrap it
338
+ if (Buffer.isBuffer(values)) {
339
+ const inst = new this(values);
340
+ return inst;
341
+ }
342
+ // If values is instance of this class, return it
343
+ if (values && values._buffer && values._structDef === def) {
344
+ return values;
345
+ }
346
+ return new this(values);
314
347
  }
315
- return new this(values);
316
- }
317
348
 
318
- // Create raw plain object without synchronization (for performance)
319
- static createRaw(values = {}) {
320
- const def = this._structDef || this._buildStruct();
321
- return def.toObject(def.create(values)._buffer);
322
- }
349
+ // Create raw plain object without synchronization (for performance)
350
+ static createRaw(values = {}) {
351
+ const def = this._structDef || this._buildStruct();
352
+ return def.toObject(def.create(values)._buffer);
353
+ }
323
354
 
324
- // Synchronize plain object into instance buffer
325
- syncFromObject(obj) {
326
- const plain = this.__structDef.toObject(this.__buffer);
327
- Object.assign(plain, obj);
328
- this.__structDef.fromObject(this.__buffer, plain);
329
- }
355
+ // Synchronize plain object into instance buffer
356
+ syncFromObject(obj) {
357
+ const plain = this.__structDef.toObject(this.__buffer);
358
+ Object.assign(plain, obj);
359
+ this.__structDef.fromObject(this.__buffer, plain);
360
+ }
330
361
 
331
- // toObject: accept Buffer, instance or plain buffer
332
- static toObject(bufOrInst) {
333
- const def = this._structDef || this._buildStruct();
334
- if (bufOrInst && bufOrInst._buffer) {
335
- return def.toObject(bufOrInst._buffer);
362
+ // toObject: accept Buffer, instance or plain buffer
363
+ static toObject(bufOrInst) {
364
+ const def = this._structDef || this._buildStruct();
365
+ if (bufOrInst && bufOrInst._buffer) {
366
+ return def.toObject(bufOrInst._buffer);
367
+ }
368
+ return def.toObject(bufOrInst);
336
369
  }
337
- return def.toObject(bufOrInst);
338
- }
339
370
 
340
- // fromObject: write plain object into buffer
341
- static fromObject(bufOrInst, obj) {
342
- const def = this._structDef || this._buildStruct();
343
- const buf = bufOrInst && bufOrInst._buffer ? bufOrInst._buffer : bufOrInst;
344
- return def.fromObject(buf, obj);
345
- }
371
+ // fromObject: write plain object into buffer
372
+ static fromObject(bufOrInst, obj) {
373
+ const def = this._structDef || this._buildStruct();
374
+ const buf = bufOrInst && bufOrInst._buffer ? bufOrInst._buffer : bufOrInst;
375
+ return def.fromObject(buf, obj);
376
+ }
346
377
 
347
- // Instance convenience
348
- get(fieldName) {
349
- return this.__structDef.get(this.__buffer, fieldName);
350
- }
378
+ // Instance convenience
379
+ get(fieldName) {
380
+ return this.__structDef.get(this.__buffer, fieldName);
381
+ }
351
382
 
352
- set(fieldName, value) {
353
- return this.__structDef.set(this.__buffer, fieldName, value);
354
- }
383
+ set(fieldName, value) {
384
+ return this.__structDef.set(this.__buffer, fieldName, value);
385
+ }
355
386
 
356
- // Bulk operations for better performance
357
- setFields(fields) {
358
- for (const [name, value] of Object.entries(fields)) {
359
- this.__structDef.set(this.__buffer, name, value);
387
+ // Bulk operations for better performance
388
+ setFields(fields) {
389
+ for (const [name, value] of Object.entries(fields)) {
390
+ this.__structDef.set(this.__buffer, name, value);
391
+ }
360
392
  }
361
- }
362
393
 
363
- getFields(fieldNames) {
364
- const result = {};
365
- for (const name of fieldNames) {
366
- result[name] = this.__structDef.get(this.__buffer, name);
394
+ getFields(fieldNames) {
395
+ const result = {};
396
+ for (const name of fieldNames) {
397
+ result[name] = this.__structDef.get(this.__buffer, name);
398
+ }
399
+ return result;
367
400
  }
368
- return result;
369
- }
370
401
 
371
- // bulk operations (Proxy reads directly from buffer - no cache needed)
372
- withBulkUpdate(callback) {
373
- return callback(this);
374
- }
402
+ // bulk operations (Proxy reads directly from buffer - no cache needed)
403
+ withBulkUpdate(callback) {
404
+ return callback(this);
405
+ }
375
406
 
376
- // Direct typed array access for numeric fields (maximum performance)
377
- getInt32Array(offset = 0, length) {
378
- return new Int32Array(this.__buffer.buffer, this.__buffer.byteOffset + offset, length);
379
- }
407
+ // Direct typed array access for numeric fields (maximum performance)
408
+ getInt32Array(offset = 0, length) {
409
+ return new Int32Array(this.__buffer.buffer, this.__buffer.byteOffset + offset, length);
410
+ }
380
411
 
381
- getFloat64Array(offset = 0, length) {
382
- return new Float64Array(this.__buffer.buffer, this.__buffer.byteOffset + offset, length);
383
- }
412
+ getFloat64Array(offset = 0, length) {
413
+ return new Float64Array(this.__buffer.buffer, this.__buffer.byteOffset + offset, length);
414
+ }
384
415
 
385
- // Get raw buffer slice for external operations
386
- getBufferSlice(offset = 0, size = this.__buffer.length - offset) {
387
- return this.__buffer.subarray(offset, offset + size);
388
- }
416
+ // Get raw buffer slice for external operations
417
+ getBufferSlice(offset = 0, size = this.__buffer.length - offset) {
418
+ return this.__buffer.subarray(offset, offset + size);
419
+ }
389
420
 
390
- // Vectorized operations for arrays of structs
391
- static createArray(count, initialValues = []) {
392
- const instances = [];
393
- for (let i = 0; i < count; i++) {
394
- instances.push(this.create(initialValues[i] || {}));
421
+ // Vectorized operations for arrays of structs
422
+ static createArray(count, initialValues = []) {
423
+ const instances = [];
424
+ for (let i = 0; i < count; i++) {
425
+ instances.push(this.create(initialValues[i] || {}));
426
+ }
427
+ return instances;
395
428
  }
396
- return instances;
397
- }
398
429
 
399
- // Bulk update all instances in array
400
- static updateArray(instances, updates) {
401
- for (let i = 0; i < instances.length && i < updates.length; i++) {
402
- if (updates[i] && typeof updates[i] === "object") {
403
- instances[i].setFields(updates[i]);
430
+ // Bulk update all instances in array
431
+ static updateArray(instances, updates) {
432
+ for (let i = 0; i < instances.length && i < updates.length; i++) {
433
+ if (updates[i] && typeof updates[i] === "object") {
434
+ instances[i].setFields(updates[i]);
435
+ }
404
436
  }
405
437
  }
406
- }
407
438
 
408
- toObject() {
409
- return this.__structDef.toObject(this.__buffer);
439
+ toObject() {
440
+ return this.__structDef.toObject(this.__buffer);
441
+ }
410
442
  }
411
- }
412
443
 
413
444
  return Structure;
414
445
  }
@@ -45,10 +45,11 @@
45
45
  *
46
46
  * @param {Class} Structure - Structure base class to extend from
47
47
  * @param {Function} union - Union definition function
48
+ * @param {Function} bitfield - Bitfield helper function for Python-style [name, type, bits] syntax
48
49
  * @returns {Class} Union base class
49
50
  * @private
50
51
  */
51
- export function createUnionClass(Structure, union) {
52
+ export function createUnionClass(Structure, union, bitfield) {
52
53
  /**
53
54
  * Union base class for creating C-style unions.
54
55
  *
@@ -86,7 +87,14 @@ export function createUnionClass(Structure, union) {
86
87
  if (Array.isArray(fields)) {
87
88
  for (const entry of fields) {
88
89
  if (Array.isArray(entry) && entry.length >= 2) {
89
- mapFields[entry[0]] = entry[1];
90
+ const name = entry[0];
91
+ const type = entry[1];
92
+ // Python-style bitfield: [name, type, bits]
93
+ if (entry.length >= 3 && typeof entry[2] === "number") {
94
+ mapFields[name] = bitfield(type, entry[2]);
95
+ } else {
96
+ mapFields[name] = type;
97
+ }
90
98
  }
91
99
  }
92
100
  } else if (typeof fields === "object" && fields !== null) {
@@ -87,7 +87,15 @@ export function bitfield(baseType, bits, sizeof) {
87
87
  * @private
88
88
  */
89
89
  export function _isStruct(type) {
90
- return typeof type === "object" && type !== null && typeof type.size === "number" && Array.isArray(type.fields) && typeof type.create === "function";
90
+ // Check for struct definition object (from struct() function)
91
+ if (typeof type === "object" && type !== null && typeof type.size === "number" && Array.isArray(type.fields) && typeof type.create === "function") {
92
+ return true;
93
+ }
94
+ // Check for Structure/Union class (has _fields_ or _structDef and _buildStruct)
95
+ if (typeof type === "function" && (type._fields_ || type._structDef) && typeof type._buildStruct === "function") {
96
+ return true;
97
+ }
98
+ return false;
91
99
  }
92
100
 
93
101
  /**
@@ -248,7 +248,12 @@ export function struct(fields, options = {}, sizeof, alloc, readValue, writeValu
248
248
  currentBitFieldBase = null;
249
249
  currentBitOffset = 0;
250
250
 
251
- const nestedStruct = type;
251
+ // Normalize type: if it's a Structure/Union class, get its _structDef
252
+ let nestedStruct = type;
253
+ if (typeof type === "function" && typeof type._buildStruct === "function") {
254
+ nestedStruct = type._structDef || type._buildStruct();
255
+ }
256
+
252
257
  const size = nestedStruct.size;
253
258
  const alignment = packed ? 1 : nestedStruct.alignment;
254
259
 
@@ -94,26 +94,16 @@ export function union(fields, sizeof, alloc, readValue, writeValue, _isStruct, _
94
94
  let size, alignment;
95
95
  let fieldDef = { name, type, offset: 0 };
96
96
 
97
- // Nested struct (object form)
97
+ // Nested struct (object form or class form)
98
98
  if (_isStruct(type)) {
99
- size = type.size;
100
- alignment = type.alignment;
101
- fieldDef.isNested = true;
102
- }
103
- // Struct class (declarata come `class X extends Structure`)
104
- else if (typeof type === "function" && type.prototype instanceof Structure) {
105
- const nested = type._structDef || type._buildStruct();
106
- size = nested.size;
107
- alignment = nested.alignment;
108
- fieldDef.type = nested;
109
- fieldDef.isNested = true;
110
- }
111
- // Union class (declarata come `class X extends Union`)
112
- else if (typeof type === "function" && type.prototype instanceof Union) {
113
- const nested = type._unionDef || type._buildUnion();
114
- size = nested.size;
115
- alignment = nested.alignment;
116
- fieldDef.type = nested;
99
+ // Normalize type: if it's a Structure/Union class, get its _structDef
100
+ let nestedStruct = type;
101
+ if (typeof type === "function" && typeof type._buildStruct === "function") {
102
+ nestedStruct = type._structDef || type._buildStruct();
103
+ fieldDef.type = nestedStruct;
104
+ }
105
+ size = nestedStruct.size;
106
+ alignment = nestedStruct.alignment;
117
107
  fieldDef.isNested = true;
118
108
  }
119
109
  // Array type