@gtkx/runtime 1.6.0 → 2.0.0-beta.1

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.
Files changed (88) hide show
  1. package/README.md +5 -5
  2. package/dist/application-class.d.ts +1 -1
  3. package/dist/application-class.d.ts.map +1 -1
  4. package/dist/application-class.js +1 -2
  5. package/dist/application-class.js.map +1 -1
  6. package/dist/arg.d.ts +4 -1
  7. package/dist/arg.d.ts.map +1 -1
  8. package/dist/arg.js.map +1 -1
  9. package/dist/bind.d.ts.map +1 -1
  10. package/dist/bind.js +1 -9
  11. package/dist/bind.js.map +1 -1
  12. package/dist/callback.d.ts +6 -1
  13. package/dist/callback.d.ts.map +1 -1
  14. package/dist/callback.js +33 -21
  15. package/dist/callback.js.map +1 -1
  16. package/dist/closure.d.ts +3 -1
  17. package/dist/closure.d.ts.map +1 -1
  18. package/dist/closure.js +17 -6
  19. package/dist/closure.js.map +1 -1
  20. package/dist/descriptors.d.ts +22 -51
  21. package/dist/descriptors.d.ts.map +1 -1
  22. package/dist/descriptors.js +53 -27
  23. package/dist/descriptors.js.map +1 -1
  24. package/dist/fn.d.ts.map +1 -1
  25. package/dist/fn.js +23 -9
  26. package/dist/fn.js.map +1 -1
  27. package/dist/folded-lengths.d.ts +2 -1
  28. package/dist/folded-lengths.d.ts.map +1 -1
  29. package/dist/folded-lengths.js +17 -7
  30. package/dist/folded-lengths.js.map +1 -1
  31. package/dist/index.d.ts +2 -24
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -24
  34. package/dist/index.js.map +1 -1
  35. package/dist/internal.d.ts +2 -2
  36. package/dist/internal.d.ts.map +1 -1
  37. package/dist/internal.js +2 -2
  38. package/dist/internal.js.map +1 -1
  39. package/dist/native-value.d.ts +9 -1
  40. package/dist/native-value.d.ts.map +1 -1
  41. package/dist/native-value.js +20 -3
  42. package/dist/native-value.js.map +1 -1
  43. package/dist/properties.d.ts +1 -1
  44. package/dist/properties.d.ts.map +1 -1
  45. package/dist/properties.js +2 -11
  46. package/dist/properties.js.map +1 -1
  47. package/dist/register-class.d.ts +45 -249
  48. package/dist/register-class.d.ts.map +1 -1
  49. package/dist/register-class.js.map +1 -1
  50. package/dist/signal.d.ts.map +1 -1
  51. package/dist/signal.js +9 -22
  52. package/dist/signal.js.map +1 -1
  53. package/dist/t.d.ts +5 -21
  54. package/dist/t.d.ts.map +1 -1
  55. package/dist/t.js +1 -5
  56. package/dist/t.js.map +1 -1
  57. package/dist/type.d.ts.map +1 -1
  58. package/dist/type.js +7 -1
  59. package/dist/type.js.map +1 -1
  60. package/dist/value.d.ts.map +1 -1
  61. package/dist/value.js +7 -1
  62. package/dist/value.js.map +1 -1
  63. package/dist/variant.d.ts +2 -6
  64. package/dist/variant.d.ts.map +1 -1
  65. package/dist/variant.js.map +1 -1
  66. package/dist/vfunc-call.d.ts.map +1 -1
  67. package/dist/vfunc-call.js +2 -11
  68. package/dist/vfunc-call.js.map +1 -1
  69. package/package.json +9 -5
  70. package/src/application-class.ts +2 -2
  71. package/src/arg.ts +4 -1
  72. package/src/bind.ts +1 -12
  73. package/src/callback.ts +53 -29
  74. package/src/closure.ts +31 -6
  75. package/src/descriptors.ts +95 -59
  76. package/src/fn.ts +31 -10
  77. package/src/folded-lengths.ts +23 -10
  78. package/src/index.ts +2 -24
  79. package/src/internal.ts +2 -2
  80. package/src/native-value.ts +24 -3
  81. package/src/properties.ts +3 -16
  82. package/src/register-class.ts +45 -249
  83. package/src/signal.ts +9 -34
  84. package/src/t.ts +5 -21
  85. package/src/type.ts +9 -1
  86. package/src/value.ts +9 -1
  87. package/src/variant.ts +2 -6
  88. package/src/vfunc-call.ts +3 -16
@@ -36,12 +36,7 @@ type StringDescriptor = Extract<Descriptor, { kind: "string" }>;
36
36
 
37
37
  /** Descriptor variant for a `GObject`, extended with the statically declared type of its value. */
38
38
  type ObjectDescriptor = Extract<Descriptor, { kind: "object" }> & {
39
- /**
40
- * Returns the wrapper class of the value's declared type. Referencing the class here keeps it
41
- * in a tree-shaken bundle; the registry is still consulted first, so the thunk only decides
42
- * the wrapper when no class is registered for the value's runtime type or an ancestor below
43
- * the declared one.
44
- */
39
+ /** Retains and supplies the declared wrapper when no more-derived wrapper is registered. */
45
40
  fallbackClass?: () => AnyClass;
46
41
  };
47
42
 
@@ -54,10 +49,7 @@ type BufferDescriptor = Extract<Descriptor, { kind: "buffer" }>;
54
49
 
55
50
  /** Descriptor variant for a `GBoxed` value, extended with the statically declared type of its value. */
56
51
  type BoxedDescriptor = Extract<Descriptor, { kind: "boxed" }> & {
57
- /**
58
- * Returns the wrapper class of the value's declared type, used when no class is registered
59
- * for the boxed GType. Referencing the class here keeps it in a tree-shaken bundle.
60
- */
52
+ /** Retains and supplies the declared wrapper when none is registered. */
61
53
  fallbackClass?: () => AnyClass;
62
54
  };
63
55
 
@@ -71,12 +63,7 @@ type StructDescriptor = Extract<Descriptor, { kind: "struct" }> & {
71
63
  type FundamentalDescriptor = Extract<Descriptor, { kind: "fundamental" }> & {
72
64
  /** Class a decoded value is wrapped in; without it the wrapper comes from the type named by `typeName`. */
73
65
  wrapperClass?: AnyClass;
74
- /**
75
- * Returns the wrapper class of the value's declared type, used when no class is registered
76
- * for the value's runtime type or an ancestor. Referencing the class here keeps it in a
77
- * tree-shaken bundle. Unlike `wrapperClass`, the registry is consulted first, so a value of
78
- * a more derived registered type keeps its own wrapper.
79
- */
66
+ /** Retains and supplies the declared wrapper after checking the runtime-type registry. */
80
67
  fallbackClass?: () => AnyClass;
81
68
  };
82
69
 
@@ -115,10 +102,7 @@ type BoxedOptions = {
115
102
  fallbackClass?: () => AnyClass;
116
103
  };
117
104
 
118
- /**
119
- * What the bindings do with a callback's return value, how the callee takes its closure, and how
120
- * long that closure has to stay alive.
121
- */
105
+ /** Callback result, closure ownership, and lifetime options. */
122
106
  type CallbackOptions = {
123
107
  /** The callee also takes a destroy notify, which frees the closure once it is done with it. */
124
108
  hasDestroy?: boolean;
@@ -128,10 +112,7 @@ type CallbackOptions = {
128
112
  hasUserData?: boolean;
129
113
  /** Position of `user_data` among the callback's own arguments, dropped before the closure is called. */
130
114
  userDataIndex?: number;
131
- /**
132
- * The callback's C signature ends with a `GError**`, which receives a `GError` built from
133
- * whatever the JavaScript function throws while the callback returns its failure value.
134
- */
115
+ /** Converts a thrown value to the callback's trailing `GError**`. */
135
116
  canThrow?: boolean;
136
117
  /** Lifetime of the closure; defaults to `notified` when `hasDestroy` is set and `call` otherwise. */
137
118
  scope?: CallbackDescriptor["scope"];
@@ -149,12 +130,10 @@ type ArrayOptions = {
149
130
  fixedSize?: number | undefined;
150
131
  /** Whether the array carries raw bytes, and so decodes to a `Uint8Array` rather than to numbers. */
151
132
  isBytes?: boolean | undefined;
152
- /**
153
- * Whether the caller supplies the storage for a fixed-length out array: the runtime allocates
154
- * a buffer of the element stride times the fixed element count, passes its pointer, and
155
- * decodes the elements the callee wrote into it.
156
- */
133
+ /** Allocates and decodes caller-owned storage for a fixed-length out array. */
157
134
  isCallerAllocated?: boolean | undefined;
135
+ /** Adds a zero element after the declared length. */
136
+ isZeroTerminated?: boolean | undefined;
158
137
  };
159
138
 
160
139
  /** Where a cursor array's base buffer and total length come from. */
@@ -197,6 +176,12 @@ type StructOptions = {
197
176
  size?: number;
198
177
  /** Class a decoded value is wrapped in, instead of the one registered for its GType. */
199
178
  wrapperClass?: AnyClass;
179
+ /** Library the struct's declared copy and free functions are resolved from; without it neither is used. */
180
+ sharedLibrary?: string;
181
+ /** Function duplicating an instance, used instead of a byte copy when the struct declares one. */
182
+ copyFnName?: string;
183
+ /** Function releasing an instance, used instead of `g_free` when the struct declares one. */
184
+ freeFnName?: string;
200
185
  };
201
186
 
202
187
  /** Descriptor for a `gint8`, marshalled as a number. */
@@ -238,20 +223,43 @@ const fundamentalLifecycles: Map<string, FundamentalLifecycle> = new Map();
238
223
  const isGtypeDescriptor = (descriptor: Descriptor): descriptor is TypeDescriptor =>
239
224
  descriptor.kind === "biguint64" && "type" in descriptor;
240
225
 
241
- /**
242
- * Builds a descriptor for a C string, whose optional length sizes the caller-allocated buffer
243
- * used when the string is passed by reference.
244
- */
245
- const stringT = (ownership: Ownership = "borrowed", length?: number): StringDescriptor =>
246
- length === undefined ? { kind: "string", ownership } : { kind: "string", ownership, length };
247
-
248
- /**
249
- * Builds a descriptor for a `GObject`, wrapped in the class registered for its runtime GType.
250
- * `fallbackClass` names the wrapper class of the declared type, retaining it in tree-shaken
251
- * bundles and wrapping the value in it when nothing at least as derived is registered.
252
- */
253
- const objectT = (ownership: Ownership = "borrowed", fallbackClass?: () => AnyClass): ObjectDescriptor =>
254
- fallbackClass === undefined ? { kind: "object", ownership } : { kind: "object", ownership, fallbackClass };
226
+ /** Builds a C-string descriptor, including optional buffer length and record-owned storage. */
227
+ const stringT = (
228
+ ownership: Ownership = "borrowed",
229
+ length?: number,
230
+ hasOwnedStorage?: boolean,
231
+ ): StringDescriptor => {
232
+ const result: StringDescriptor = { kind: "string", ownership };
233
+
234
+ if (length !== undefined) {
235
+ result.length = length;
236
+ }
237
+
238
+ if (hasOwnedStorage === true) {
239
+ result.hasOwnedStorage = true;
240
+ }
241
+
242
+ return result;
243
+ };
244
+
245
+ /** Builds a runtime-typed `GObject` descriptor with an optional retained fallback class. */
246
+ const objectT = (
247
+ ownership: Ownership = "borrowed",
248
+ fallbackClass?: () => AnyClass,
249
+ typeName?: string,
250
+ ): ObjectDescriptor => {
251
+ const result: ObjectDescriptor = { kind: "object", ownership };
252
+
253
+ if (fallbackClass !== undefined) {
254
+ result.fallbackClass = fallbackClass;
255
+ }
256
+
257
+ if (typeName !== undefined) {
258
+ result.typeName = typeName;
259
+ }
260
+
261
+ return result;
262
+ };
255
263
 
256
264
  /** Wraps a descriptor in a pointer to it, for an output or inout argument. */
257
265
  const refT = (innerDescriptor: Descriptor, isInout = false): RefDescriptor =>
@@ -269,19 +277,28 @@ const hashTableT = (
269
277
  ownership,
270
278
  });
271
279
 
272
- /** Builds a descriptor for an enumeration, resolving its GType from the named `get_type` function. */
273
- const enumT = (sharedLibrary: string, typeFnName: string, isSigned: boolean): EnumDescriptor => ({
274
- kind: "enum",
275
- sharedLibrary,
276
- getTypeFnName: typeFnName,
277
- isSigned,
278
- });
280
+ /** Builds an enum descriptor from a GType function or an explicit set of members. */
281
+ const enumT = (
282
+ sharedLibrary: string,
283
+ typeFnName: string,
284
+ isSigned: boolean,
285
+ members?: number[],
286
+ ): EnumDescriptor => {
287
+ const result: EnumDescriptor = {
288
+ kind: "enum",
289
+ sharedLibrary,
290
+ getTypeFnName: typeFnName,
291
+ isSigned,
292
+ };
279
293
 
280
- /**
281
- * Builds a descriptor for a flags type, resolving its GType from the named `get_type` function.
282
- * For flags without a registered GType, pass empty library and function names and supply `mask`,
283
- * the union of all valid bits, which invalid combinations are rejected against.
284
- */
294
+ if (members !== undefined) {
295
+ result.members = members;
296
+ }
297
+
298
+ return result;
299
+ };
300
+
301
+ /** Builds a flags descriptor from a GType function or an explicit bit mask. */
285
302
  const flagsT = (sharedLibrary: string, typeFnName: string, isSigned: boolean, mask?: number): FlagsDescriptor => {
286
303
  const result: FlagsDescriptor = {
287
304
  kind: "flags",
@@ -344,6 +361,22 @@ const boxedT = (typeName: string, options: BoxedOptions = {}): BoxedDescriptor =
344
361
  return result;
345
362
  };
346
363
 
364
+ const applyStructLifecycle = (result: StructDescriptor, options: StructOptions): void => {
365
+ if (options.sharedLibrary === undefined) {
366
+ return;
367
+ }
368
+
369
+ result.sharedLibrary = options.sharedLibrary;
370
+
371
+ if (options.copyFnName !== undefined) {
372
+ result.copyFnName = options.copyFnName;
373
+ }
374
+
375
+ if (options.freeFnName !== undefined) {
376
+ result.freeFnName = options.freeFnName;
377
+ }
378
+ };
379
+
347
380
  /** Builds a descriptor for a plain C struct. */
348
381
  const structT = (ownership: Ownership = "borrowed", options: StructOptions = {}): StructDescriptor => {
349
382
  const result: StructDescriptor = { kind: "struct", ownership };
@@ -364,6 +397,8 @@ const structT = (ownership: Ownership = "borrowed", options: StructOptions = {})
364
397
  result.isInline = true;
365
398
  }
366
399
 
400
+ applyStructLifecycle(result, options);
401
+
367
402
  return result;
368
403
  };
369
404
 
@@ -455,6 +490,10 @@ const arrayT = (
455
490
  result.isCallerAllocated = true;
456
491
  }
457
492
 
493
+ if (options.isZeroTerminated === true) {
494
+ result.isZeroTerminated = true;
495
+ }
496
+
458
497
  return result;
459
498
  };
460
499
 
@@ -498,10 +537,7 @@ const sizedArrayT = (
498
537
  options: ArrayOptions = {},
499
538
  ): ArrayDescriptor => arrayT(itemDescriptor, "sized", ownership, { ...options, sizeParamIndex });
500
539
 
501
- /**
502
- * Builds a descriptor for an out pointer into the buffer another argument supplied, decoded as the
503
- * elements from where it points to the end of that buffer.
504
- */
540
+ /** Builds an out-array cursor into another argument's buffer. */
505
541
  const cursorArrayT = (
506
542
  itemDescriptor: Descriptor,
507
543
  bounds: CursorBounds,
package/src/fn.ts CHANGED
@@ -158,12 +158,33 @@ const isPassThroughPlan = (plan: ArgSpec, index: number): boolean =>
158
158
  !plan.carriesGtype &&
159
159
  plan.arg.type.kind !== "callback";
160
160
 
161
- const requiredInputIndices = (plans: ArgSpec[]): number[] =>
162
- plans.filter((plan) => plan.requiresInput && plan.arg.isRequired === true).map((plan) => plan.inputIndex);
161
+ const KINDS_PASSED_AS_POINTER: ReadonlySet<Descriptor["kind"]> = new Set<Descriptor["kind"]>([
162
+ "array",
163
+ "boxed",
164
+ "buffer",
165
+ "callback",
166
+ "fundamental",
167
+ "hashtable",
168
+ "object",
169
+ "string",
170
+ "struct",
171
+ ]);
172
+
173
+ type RequiredInput = {
174
+ index: number;
175
+ rejectsNull: boolean;
176
+ };
177
+
178
+ const requiredInputs = (plans: ArgSpec[]): RequiredInput[] =>
179
+ plans
180
+ .filter((plan) => plan.requiresInput && plan.arg.isRequired === true)
181
+ .map((plan) => ({ index: plan.inputIndex, rejectsNull: KINDS_PASSED_AS_POINTER.has(plan.arg.type.kind) }));
182
+
183
+ const assertRequiredInputs = (required: RequiredInput[], inputs: unknown[]): void => {
184
+ for (const { index, rejectsNull } of required) {
185
+ const input = inputs[index];
163
186
 
164
- const assertRequiredInputs = (requiredIndices: number[], inputs: unknown[]): void => {
165
- for (const index of requiredIndices) {
166
- if (inputs[index] === undefined) {
187
+ if (input === undefined || (rejectsNull && input === null)) {
167
188
  throw new TypeError(`Missing required argument at position ${String(index + 1)}`);
168
189
  }
169
190
  }
@@ -193,10 +214,10 @@ const directCallable = (
193
214
  plans: ArgSpec[],
194
215
  ): ((...inputs: unknown[]) => unknown) => {
195
216
  const argCount = plans.length;
196
- const requiredIndices = requiredInputIndices(plans);
217
+ const required = requiredInputs(plans);
197
218
 
198
219
  const marshal = (inputs: unknown[]): unknown => {
199
- assertRequiredInputs(requiredIndices, inputs);
220
+ assertRequiredInputs(required, inputs);
200
221
 
201
222
  return readReturn(call(descriptor, resizeInputs(inputs, argCount)));
202
223
  };
@@ -226,14 +247,14 @@ function fromNativeCallable(
226
247
  return directCallable(descriptor, readReturn, hasPrimary, plans);
227
248
  }
228
249
 
229
- const requiredIndices = requiredInputIndices(plans);
250
+ const required = requiredInputs(plans);
230
251
 
231
252
  const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown =>
232
253
  packTupleResult(readOutParams(outPlans, inputs, nativeValues), readReturn(nativeResult), hasPrimary);
233
254
 
234
255
  if (canThrow) {
235
256
  return (...inputs) => {
236
- assertRequiredInputs(requiredIndices, inputs);
257
+ assertRequiredInputs(required, inputs);
237
258
  const nativeValues = buildNativeValues(plans, inputs, getRefSeeds?.());
238
259
  const errorRef: Ref = { value: null };
239
260
  nativeValues.push(errorRef);
@@ -245,7 +266,7 @@ function fromNativeCallable(
245
266
  }
246
267
 
247
268
  return (...inputs) => {
248
- assertRequiredInputs(requiredIndices, inputs);
269
+ assertRequiredInputs(required, inputs);
249
270
  const nativeValues = buildNativeValues(plans, inputs, getRefSeeds?.());
250
271
 
251
272
  return shape(inputs, nativeValues, call(descriptor, nativeValues));
@@ -28,15 +28,7 @@ const addLengthSource = (sources: LengthSources, index: number | undefined, sour
28
28
  return;
29
29
  }
30
30
 
31
- const existing = sources.get(index);
32
-
33
- if (existing === undefined) {
34
- sources.set(index, [source]);
35
-
36
- return;
37
- }
38
-
39
- existing.push(source);
31
+ sources.getOrInsertComputed(index, () => []).push(source);
40
32
  };
41
33
 
42
34
  const addOutArgLengthSources = (sources: LengthSources, spec: FoldedLengthSpec): void => {
@@ -52,6 +44,27 @@ const addOutArgLengthSources = (sources: LengthSources, spec: FoldedLengthSpec):
52
44
  }
53
45
  };
54
46
 
47
+ const foldedLengthArgIndices = (spec: FoldedLengthSpec): ReadonlySet<number> => {
48
+ const indices: Set<number> = new Set();
49
+
50
+ const add = (declaredIndex: number | undefined): void => {
51
+ const argIndex = effectiveArgIndex(declaredIndex, spec.userDataIndex);
52
+
53
+ if (argIndex !== undefined) {
54
+ indices.add(argIndex);
55
+ }
56
+ };
57
+
58
+ add(sizedArrayLengthIndex(spec.returnDescriptor));
59
+
60
+ for (const descriptor of spec.argDescriptors) {
61
+ add(sizedArrayLengthIndex(descriptor));
62
+ add(outArgLengthIndex(descriptor));
63
+ }
64
+
65
+ return indices;
66
+ };
67
+
55
68
  const foldedLengthSources = (spec: FoldedLengthSpec): LengthSources => {
56
69
  const sources: LengthSources = new Map();
57
70
  const returnLengthIndex = effectiveArgIndex(sizedArrayLengthIndex(spec.returnDescriptor), spec.userDataIndex);
@@ -61,4 +74,4 @@ const foldedLengthSources = (spec: FoldedLengthSpec): LengthSources => {
61
74
  return sources;
62
75
  };
63
76
 
64
- export { foldedLengthSources, type LengthSource, type LengthSources };
77
+ export { foldedLengthArgIndices, foldedLengthSources, type LengthSource, type LengthSources };
package/src/index.ts CHANGED
@@ -1,22 +1,14 @@
1
1
  import "./exit-hook.js";
2
2
 
3
- /** @public */
4
3
  export { type ApplicationClass, type CommandLineApplication, createApplication } from "./application-class.js";
5
- /** @public */
4
+ export { CallbackMarshalError } from "./callback.js";
6
5
  export { type ClosureCallback, ClosureMarshalError, toClosure, tryToClosure } from "./closure.js";
7
- /** @public */
8
6
  export { createErrorDomain, type ErrorDomain } from "./error.js";
9
- /** @public */
10
7
  export { type Field, type StridedField } from "./field.js";
11
- /** @public */
12
8
  export { onExit, quit, quitApplication, runApplication, type RunApplicationResult } from "./lifecycle.js";
13
- /** @public */
14
9
  export { offSignal, onceSignal, onSignal } from "./listeners.js";
15
- /** @public */
16
10
  export { installMixins, type Mixin } from "./mixin.js";
17
- /** @public */
18
- export { fromNative, toNative } from "./native-value.js";
19
- /** @public */
11
+ export { fromNative, toHashTableEntries, toNative } from "./native-value.js";
20
12
  export {
21
13
  type ConstructBinding,
22
14
  type ConstructBindings,
@@ -25,22 +17,16 @@ export {
25
17
  registerConstructProperties,
26
18
  setObjectProperty,
27
19
  } from "./object.js";
28
- /** @public */
29
20
  export { getParamSpecFlags, getParamSpecOwnerType, getParamSpecValueType } from "./param-spec.js";
30
- /** @public */
31
21
  export { promisify, trimFinish } from "./promisify.js";
32
- /** @public */
33
22
  export {
34
23
  coerceObjectProperty,
35
24
  getDeclaredPropertyName,
36
25
  isReadableProperty,
37
26
  newParamSpecOverride,
38
27
  } from "./properties.js";
39
- /** @public */
40
28
  export { matchAllRegex, matchRegex } from "./regex.js";
41
- /** @public */
42
29
  export { type Interface, registerClass, type SignalGType, type SignalSpec } from "./register-class.js";
43
- /** @public */
44
30
  export {
45
31
  getClassType,
46
32
  getHandle,
@@ -58,7 +44,6 @@ export {
58
44
  type WrapperClass,
59
45
  type WrapperClassResolver,
60
46
  } from "./registry.js";
61
- /** @public */
62
47
  export {
63
48
  connectSignal,
64
49
  disconnectSignal,
@@ -67,9 +52,7 @@ export {
67
52
  type SignalHandler,
68
53
  signalForHandlerName,
69
54
  } from "./signal.js";
70
- /** @public */
71
55
  export { t } from "./t.js";
72
- /** @public */
73
56
  export {
74
57
  resolveType,
75
58
  TYPE_BOOLEAN,
@@ -105,7 +88,6 @@ export {
105
88
  typeParent,
106
89
  valueIsA,
107
90
  } from "./type.js";
108
- /** @public */
109
91
  export {
110
92
  fromValue,
111
93
  getBoxedValue,
@@ -115,7 +97,6 @@ export {
115
97
  tryToValueHandle,
116
98
  ValueMarshalError,
117
99
  } from "./value.js";
118
- /** @public */
119
100
  export {
120
101
  type ByteArray,
121
102
  type FromVariantOptions,
@@ -126,9 +107,6 @@ export {
126
107
  type VariantInput,
127
108
  type VariantValue,
128
109
  } from "./variant.js";
129
- /** @public */
130
110
  export { callParent, callVfunc } from "./vfunc-call.js";
131
- /** @public */
132
111
  export { alloc, type ExternalObject, type Handle, read, write } from "@gtkx/native";
133
- /** @public */
134
112
  export { type AnyClass } from "@gtkx/utils";
package/src/internal.ts CHANGED
@@ -5,9 +5,9 @@ export {
5
5
  registeredElementProperties,
6
6
  registeredElementSignals,
7
7
  } from "./element-metadata.js";
8
- export { checkError, createErrorDomain } from "./error.js";
8
+ export { createErrorDomain } from "./error.js";
9
9
  export { type ApplicationInstance, getApplicationInstance } from "./lifecycle.js";
10
- export { getExactWrapperClass, registerClassType, resolveWrapperClass, wrapHandle } from "./registry.js";
10
+ export { getExactWrapperClass, resolveWrapperClass } from "./registry.js";
11
11
  export { hasSignalListener } from "./signal.js";
12
12
  export { resolveType } from "./type.js";
13
13
  export {
@@ -255,12 +255,33 @@ function collectionToNative(descriptor: ArrayDescriptor, value: unknown): unknow
255
255
  return value == null ? null : mapCollection(descriptor, value, toNative);
256
256
  }
257
257
 
258
- function hashTableToNative(descriptor: HashTableDescriptor, value: unknown): unknown {
258
+ /**
259
+ * Normalises the JavaScript form of a hash table argument into its entry list. A `Map` is the
260
+ * expected form and any other iterable of pairs is accepted; anything else is rejected, a plain
261
+ * object above all, which `Array.from` would quietly flatten into an empty table.
262
+ *
263
+ * @param value The JavaScript value passed for a hash table argument.
264
+ */
265
+ function toHashTableEntries(value: unknown): [unknown, unknown][] | null {
259
266
  if (value == null) {
260
267
  return null;
261
268
  }
262
269
 
263
- return [...(value as Map<unknown, unknown>)].map(([key, val]): [unknown, unknown] => [
270
+ if (typeof (value as Partial<Iterable<unknown>>)[Symbol.iterator] !== "function") {
271
+ throw new TypeError("A hash table argument must be a Map or an iterable of [key, value] pairs");
272
+ }
273
+
274
+ return [...(value as Iterable<[unknown, unknown]>)];
275
+ }
276
+
277
+ function hashTableToNative(descriptor: HashTableDescriptor, value: unknown): unknown {
278
+ const entries = toHashTableEntries(value);
279
+
280
+ if (entries === null) {
281
+ return null;
282
+ }
283
+
284
+ return entries.map(([key, val]): [unknown, unknown] => [
264
285
  toNative(descriptor.keyDescriptor, key),
265
286
  toNative(descriptor.valueDescriptor, val),
266
287
  ]);
@@ -302,4 +323,4 @@ function toNative(descriptor: Descriptor, value: unknown): unknown {
302
323
  }
303
324
  }
304
325
 
305
- export { fromNative, toNative };
326
+ export { fromNative, toHashTableEntries, toNative };
package/src/properties.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native";
2
- import type { AnyClass } from "@gtkx/utils";
3
- import { camelCase, kebabCase, toCamelIdentifier } from "@gtkx/utils";
2
+ import { type AnyClass, camelCase, kebabCase, toCamelIdentifier } from "@gtkx/utils";
4
3
  import { bind } from "./bind.js";
5
4
  import { biguint64T, fundamentalT, refT, sizedArrayT, stringT, structT, uint32T, voidT } from "./descriptors.js";
6
5
  import { LIB, PARAM_T, VALUE_T } from "./library.js";
@@ -347,21 +346,9 @@ function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null
347
346
  }
348
347
 
349
348
  function coercionCheckFor(gtype: bigint, name: string): PropertyCheck | null {
350
- let checks = coercionChecks.get(gtype);
349
+ const checks = coercionChecks.getOrInsertComputed(gtype, () => new Map<string, PropertyCheck | null>());
351
350
 
352
- if (checks === undefined) {
353
- checks = new Map();
354
- coercionChecks.set(gtype, checks);
355
- }
356
-
357
- let check = checks.get(name);
358
-
359
- if (check === undefined) {
360
- check = lookupCoercionCheck(gtype, name);
361
- checks.set(name, check);
362
- }
363
-
364
- return check;
351
+ return checks.getOrInsertComputed(name, () => lookupCoercionCheck(gtype, name));
365
352
  }
366
353
 
367
354
  function truncateToWhole(check: PropertyCheck, value: number): number {