@gtkx/runtime 1.1.0 → 1.2.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.
package/src/closure.ts CHANGED
@@ -40,7 +40,7 @@ const MARSHAL_T = callbackT(
40
40
  CLOSURE_T,
41
41
  MARSHAL_VALUE_T,
42
42
  uint32T,
43
- sizedArrayT(MARSHAL_VALUE_T, N_PARAM_VALUES_INDEX, "borrowed", VALUE_SIZE),
43
+ sizedArrayT(MARSHAL_VALUE_T, N_PARAM_VALUES_INDEX, "borrowed", { elementSize: VALUE_SIZE }),
44
44
  uint64T,
45
45
  uint64T,
46
46
  ],
@@ -116,6 +116,8 @@ type ArrayOptions = {
116
116
  sizeParamIndex?: number | undefined;
117
117
  /** Element count of a fixed-length array. */
118
118
  fixedSize?: number | undefined;
119
+ /** Whether the array carries raw bytes, and so decodes to a `Uint8Array` rather than to numbers. */
120
+ isBytes?: boolean | undefined;
119
121
  };
120
122
 
121
123
  /** Where a cursor array's base buffer and total length come from. */
@@ -360,40 +362,52 @@ const arrayT = (
360
362
  result.fixedSize = options.fixedSize;
361
363
  }
362
364
 
365
+ if (options?.isBytes === true) {
366
+ result.isBytes = true;
367
+ }
368
+
363
369
  return result;
364
370
  };
365
371
 
366
372
  /** Builds a descriptor for a `GList` of items. */
367
- const listT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
368
- arrayT(itemDescriptor, "glist", ownership);
373
+ const listT = (
374
+ itemDescriptor: Descriptor,
375
+ ownership: Ownership = "borrowed",
376
+ options: ArrayOptions = {},
377
+ ): ArrayDescriptor => arrayT(itemDescriptor, "glist", ownership, options);
369
378
 
370
379
  /** Builds a descriptor for a `GSList` of items. */
371
- const slistT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
372
- arrayT(itemDescriptor, "gslist", ownership);
380
+ const slistT = (
381
+ itemDescriptor: Descriptor,
382
+ ownership: Ownership = "borrowed",
383
+ options: ArrayOptions = {},
384
+ ): ArrayDescriptor => arrayT(itemDescriptor, "gslist", ownership, options);
373
385
 
374
386
  /** Builds a descriptor for a `GPtrArray` of items. */
375
- const ptrArrayT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
376
- arrayT(itemDescriptor, "gptrarray", ownership);
387
+ const ptrArrayT = (
388
+ itemDescriptor: Descriptor,
389
+ ownership: Ownership = "borrowed",
390
+ options: ArrayOptions = {},
391
+ ): ArrayDescriptor => arrayT(itemDescriptor, "gptrarray", ownership, options);
377
392
 
378
- /** Builds a descriptor for a `GArray` of items, optionally with an explicit element size. */
393
+ /** Builds a descriptor for a `GArray` of items. */
379
394
  const gArrayT = (
380
395
  itemDescriptor: Descriptor,
381
396
  ownership: Ownership = "borrowed",
382
- elementSize?: number,
383
- ): ArrayDescriptor =>
384
- arrayT(itemDescriptor, "garray", ownership, elementSize === undefined ? undefined : { elementSize });
397
+ options: ArrayOptions = {},
398
+ ): ArrayDescriptor => arrayT(itemDescriptor, "garray", ownership, options);
385
399
 
386
400
  /** Builds a descriptor for a `GByteArray`. */
387
401
  const byteArrayT = (ownership: Ownership = "borrowed"): ArrayDescriptor =>
388
- arrayT(uint8T, "gbytearray", ownership);
402
+ arrayT(uint8T, "gbytearray", ownership, { isBytes: true });
389
403
 
390
404
  /** Builds a descriptor for a C array whose length is carried by another argument. */
391
405
  const sizedArrayT = (
392
406
  itemDescriptor: Descriptor,
393
407
  sizeParamIndex: number,
394
408
  ownership: Ownership = "borrowed",
395
- elementSize?: number,
396
- ): ArrayDescriptor => arrayT(itemDescriptor, "sized", ownership, { sizeParamIndex, elementSize });
409
+ options: ArrayOptions = {},
410
+ ): ArrayDescriptor => arrayT(itemDescriptor, "sized", ownership, { ...options, sizeParamIndex });
397
411
 
398
412
  /**
399
413
  * Builds a descriptor for an out pointer into the buffer another argument supplied, decoded as the
@@ -403,16 +417,16 @@ const cursorArrayT = (
403
417
  itemDescriptor: Descriptor,
404
418
  bounds: CursorBounds,
405
419
  ownership: Ownership = "borrowed",
406
- elementSize?: number,
407
- ): ArrayDescriptor => arrayT(itemDescriptor, "cursor", ownership, { ...bounds, elementSize });
420
+ options: ArrayOptions = {},
421
+ ): ArrayDescriptor => arrayT(itemDescriptor, "cursor", ownership, { ...options, ...bounds });
408
422
 
409
423
  /** Builds a descriptor for a C array of a fixed length. */
410
424
  const fixedArrayT = (
411
425
  itemDescriptor: Descriptor,
412
426
  fixedSize: number,
413
427
  ownership: Ownership = "borrowed",
414
- elementSize?: number,
415
- ): ArrayDescriptor => arrayT(itemDescriptor, "fixed", ownership, { fixedSize, elementSize });
428
+ options: ArrayOptions = {},
429
+ ): ArrayDescriptor => arrayT(itemDescriptor, "fixed", ownership, { ...options, fixedSize });
416
430
 
417
431
  const applyClosureOptions = (result: CallbackDescriptor, options: CallbackOptions): void => {
418
432
  if (options.hasDestroy !== undefined) {
package/src/index.ts CHANGED
@@ -81,6 +81,8 @@ export {
81
81
  /** @public */
82
82
  export { getBoxedValue, setBoxedValue } from "./value.js";
83
83
  /** @public */
84
+ export { fromVariant, toVariant, type VariantValue } from "./variant.js";
85
+ /** @public */
84
86
  export { callParent, callVfunc } from "./vfunc-call.js";
85
87
  /** @public */
86
88
  export { alloc, type ExternalObject, type Handle, read, write } from "@gtkx/native";
@@ -61,7 +61,7 @@ type Camelized<TName extends string> = TName extends `${infer THead}-${infer TTa
61
61
 
62
62
  /**
63
63
  * The names a registered class carries in its property map: every key of
64
- * `RegisterClassOptions.properties` in camelCase, whichever of the three spellings it was written in.
64
+ * `RegisterClassOptions.properties` in camelCase, whichever spelling it was written in.
65
65
  * A `properties` object given a type of its own rather than left to inference has `string` for its key
66
66
  * type and names nothing, because a name only known as `string` addresses no member in particular.
67
67
  */
@@ -129,7 +129,7 @@ type RegisterClassOptions<TInstance extends object, TProperties extends Record<s
129
129
  * A write the ParamSpec would refuse throws rather than reaching GObject, which reports such a
130
130
  * write as a GLib critical and drops it: a `TypeError` for a read-only or construct-only
131
131
  * property and for a value of a type the property cannot hold, and a `RangeError` for a value
132
- * the ParamSpec rejects. The same two checks run over a value handed to the constructor, where
132
+ * the ParamSpec rejects. The same checks run over a value handed to the constructor, where
133
133
  * a construct-only property is the one that is writable. An accepted write emits one `notify`,
134
134
  * which a `freeze_notify` batch collects; a write of the value the property already holds is
135
135
  * dropped and emits none.
@@ -165,7 +165,7 @@ type RegisterClassOptions<TInstance extends object, TProperties extends Record<s
165
165
  * write of `null` to that member both land 0, where a `gint` installed here refuses both.
166
166
  *
167
167
  * A class that defines the camelCase member itself owns the property: its own accessor decides
168
- * what a write means, the other two spellings forward to it, and the type's property slots read
168
+ * what a write means, the other spellings forward to it, and the type's property slots read
169
169
  * and write it rather than the generated storage.
170
170
  */
171
171
  properties?: TProperties;
package/src/registry.ts CHANGED
@@ -11,12 +11,13 @@ import type { Mixin, MixinReceiver } from "./mixin.js";
11
11
  import { TYPE_INVALID, type TypedClass, typeInterfaces, typeIsA, typeName, typeParent } from "./type.js";
12
12
 
13
13
  /**
14
- * Static side of class `C` with its construct signature preserved but the member
15
- * named `K` (default `"new"`) removed. The signature is kept abstract so an abstract
16
- * `C` survives it: a subclass extends the result, and only the subclass's own
17
- * declaration decides whether `new` reaches it.
14
+ * Static side of class `C` with its construct signature preserved but the members named `K`
15
+ * removed. A generated class lists every static it declares itself, so a static it narrows never
16
+ * has to stay assignable to the one it shadows. The signature is kept abstract so an abstract `C`
17
+ * survives it: a subclass extends the result, and only the subclass's own declarations decide
18
+ * which statics reach it.
18
19
  */
19
- type StaticBase<C, K extends PropertyKey = "new"> = Omit<C, K> &
20
+ type StaticBase<C, K extends PropertyKey> = Omit<C, K> &
20
21
  (C extends abstract new (...args: infer A) => infer R ? abstract new (...args: A) => R : never);
21
22
 
22
23
  /** One overridable vtable slot: where it sits in the vtable struct and how it is marshalled. */
package/src/t.ts CHANGED
@@ -69,7 +69,7 @@ type T = {
69
69
  slist: typeof helpers.slistT;
70
70
  /** Builds a descriptor for a `GPtrArray` of items. */
71
71
  ptrArray: typeof helpers.ptrArrayT;
72
- /** Builds a descriptor for a `GArray` of items, optionally with an explicit element size. */
72
+ /** Builds a descriptor for a `GArray` of items. */
73
73
  gArray: typeof helpers.gArrayT;
74
74
  /** Builds a descriptor for a `GByteArray`. */
75
75
  byteArray: typeof helpers.byteArrayT;
package/src/variant.ts ADDED
@@ -0,0 +1,427 @@
1
+ import * as GLib from "@gtkx/gi/glib";
2
+
3
+ /** JavaScript type every GVariant basic type code unpacks to. */
4
+ type BasicValueMap = {
5
+ /** Boolean, held as a single byte reading 0 or 1. */
6
+ b: boolean;
7
+ /** Unsigned byte. */
8
+ y: number;
9
+ /** Signed 16-bit integer. */
10
+ n: number;
11
+ /** Unsigned 16-bit integer. */
12
+ q: number;
13
+ /** Signed 32-bit integer. */
14
+ i: number;
15
+ /** Unsigned 32-bit integer. */
16
+ u: number;
17
+ /** Index into the file descriptor list a message carries. */
18
+ h: number;
19
+ /** Double precision floating point number. */
20
+ d: number;
21
+ /** Signed 64-bit integer. */
22
+ x: bigint;
23
+ /** Unsigned 64-bit integer. */
24
+ t: bigint;
25
+ /** UTF-8 string, under no further constraint on what it holds. */
26
+ s: string;
27
+ /** D-Bus object path. */
28
+ o: string;
29
+ /** GVariant type signature. */
30
+ g: string;
31
+ /** Boxed variant holding a value of any type. */
32
+ v: GLib.Variant;
33
+ };
34
+
35
+ /** Single-character code of one of the GVariant basic types in {@link BasicValueMap}. */
36
+ type BasicCode = keyof BasicValueMap;
37
+ /** Parses the element type of an array, yielding the array type it produces and the rest of the string. */
38
+ type ParseArray<S extends string> = Parse<S> extends [infer V, infer R extends string] ? [V[], R] : never;
39
+ /** Parses the element type of a maybe, yielding the nullable type it produces and the rest of the string. */
40
+ type ParseMaybe<S extends string> = Parse<S> extends [infer V, infer R extends string] ? [V | null, R] : never;
41
+
42
+ /** Collects tuple member types into `Acc` up to the closing parenthesis, yielding them and the rest of the string. */
43
+ type ParseTuple<S extends string, Acc extends unknown[]> = S extends `)${infer Rest}`
44
+ ? [Acc, Rest]
45
+ : [Parse<S>] extends [never]
46
+ ? never
47
+ : Parse<S> extends [infer V, infer R extends string]
48
+ ? ParseTuple<R, [...Acc, V]>
49
+ : never;
50
+
51
+ /** Types a dictionary entry key can hold. */
52
+ type DictKey = string | number | bigint | boolean;
53
+
54
+ /** Parses the key and value types of a dictionary entry, yielding them and the rest of the string after its brace. */
55
+ type ParsePair<S extends string> =
56
+ Parse<S> extends [infer K, infer R1 extends string]
57
+ ? Parse<R1> extends [infer V, infer R2 extends string]
58
+ ? R2 extends `}${infer R3}`
59
+ ? [K] extends [DictKey]
60
+ ? [K, V, R3]
61
+ : never
62
+ : never
63
+ : never
64
+ : never;
65
+
66
+ /**
67
+ * Parses a dictionary entry into the collection an array of those entries unpacks to, a record for string keys and a
68
+ * `Map` otherwise, plus the rest of the string.
69
+ */
70
+ type ParseDict<S extends string> =
71
+ ParsePair<S> extends [infer K, infer V, infer R extends string]
72
+ ? [[K] extends [string] ? Record<string, V> : Map<K, V>, R]
73
+ : never;
74
+
75
+ /** Parses a standalone dictionary entry into a key and value pair, yielding it and the rest of the string. */
76
+ type ParseEntry<S extends string> =
77
+ ParsePair<S> extends [infer K, infer V, infer R extends string] ? [[K, V], R] : never;
78
+
79
+ /** Parses what follows an `a` as a dictionary when it opens an entry, and as a plain array otherwise. */
80
+ type ParseArrayOrDict<S extends string> = S extends `{${infer Rest}` ? ParseDict<Rest> : ParseArray<S>;
81
+
82
+ /**
83
+ * Parses the type at the head of `S`, yielding the JavaScript type it unpacks to and the rest of the string, or
84
+ * `never` when the string is malformed.
85
+ */
86
+ type Parse<S extends string> = S extends `a${infer Rest}`
87
+ ? ParseArrayOrDict<Rest>
88
+ : S extends `m${infer Rest}`
89
+ ? ParseMaybe<Rest>
90
+ : S extends `(${infer Rest}`
91
+ ? ParseTuple<Rest, []>
92
+ : S extends `{${infer Rest}`
93
+ ? ParseEntry<Rest>
94
+ : S extends `${infer C}${infer Rest}`
95
+ ? C extends BasicCode
96
+ ? [BasicValueMap[C], Rest]
97
+ : never
98
+ : never;
99
+
100
+ /** JavaScript type a variant of type string `S` unpacks to, or `unknown` when `S` is not one complete type. */
101
+ type VariantValue<S extends string> = [Parse<S>] extends [never]
102
+ ? unknown
103
+ : Parse<S> extends [infer V, ""]
104
+ ? V
105
+ : unknown;
106
+
107
+ type VariantTypeNode =
108
+ | { kind: "basic"; code: BasicCode } |
109
+ { kind: "array"; elementTypeString: string; element: VariantTypeNode } |
110
+ { kind: "dict"; entryTypeString: string; key: VariantTypeNode; value: VariantTypeNode } |
111
+ { kind: "entry"; key: VariantTypeNode; value: VariantTypeNode } |
112
+ { kind: "tuple"; items: VariantTypeNode[] } |
113
+ { kind: "maybe"; elementTypeString: string; element: VariantTypeNode };
114
+
115
+ const BASIC_CODES = "bynqiuxthdsogv";
116
+ const STRING_KEY_CODES: Set<string> = new Set(["s", "o", "g"]);
117
+
118
+ const CONTAINER_PARSERS: Record<string, (source: string, start: number) => [VariantTypeNode, number]> = {
119
+ a: parseArrayNode,
120
+ m: parseMaybeNode,
121
+ "(": parseTupleNode,
122
+ "{": parseEntryNode,
123
+ };
124
+
125
+ const parsedTypes: Map<string, VariantTypeNode> = new Map();
126
+
127
+ const unpackBasic: Record<BasicCode, (variant: GLib.Variant) => unknown> = {
128
+ b: (variant) => variant.getBoolean(),
129
+ y: (variant) => variant.getByte(),
130
+ n: (variant) => variant.getInt16(),
131
+ q: (variant) => variant.getUint16(),
132
+ i: (variant) => variant.getInt32(),
133
+ u: (variant) => variant.getUint32(),
134
+ h: (variant) => variant.getHandle(),
135
+ d: (variant) => variant.getDouble(),
136
+ x: (variant) => variant.getInt64(),
137
+ t: (variant) => variant.getUint64(),
138
+ s: (variant) => variant.getString()[0],
139
+ o: (variant) => variant.getString()[0],
140
+ g: (variant) => variant.getString()[0],
141
+ v: (variant) => variant.getVariant(),
142
+ };
143
+
144
+ const packBasic: Record<BasicCode, (value: unknown) => GLib.Variant> = {
145
+ b: (value) => GLib.Variant.newBoolean(value as boolean),
146
+ y: (value) => GLib.Variant.newByte(value as number),
147
+ n: (value) => GLib.Variant.newInt16(value as number),
148
+ q: (value) => GLib.Variant.newUint16(value as number),
149
+ i: (value) => GLib.Variant.newInt32(value as number),
150
+ u: (value) => GLib.Variant.newUint32(value as number),
151
+ h: (value) => GLib.Variant.newHandle(value as number),
152
+ d: (value) => GLib.Variant.newDouble(value as number),
153
+ x: (value) => GLib.Variant.newInt64(value as bigint),
154
+ t: (value) => GLib.Variant.newUint64(value as bigint),
155
+ s: (value) => GLib.Variant.newString(value as string),
156
+ o: packObjectPath,
157
+ g: packSignature,
158
+ v: (value) => GLib.Variant.newVariant(value as GLib.Variant),
159
+ };
160
+
161
+ const isBasicCode = (code: string): code is BasicCode => BASIC_CODES.includes(code);
162
+ const isStringKeyed = (key: VariantTypeNode): boolean => key.kind === "basic" && STRING_KEY_CODES.has(key.code);
163
+ const invalidType = (source: string): Error => new Error(`Invalid GVariant type string "${source}"`);
164
+
165
+ const parsePair = (source: string, start: number): [VariantTypeNode, VariantTypeNode, number] => {
166
+ const [key, keyEnd] = parseNode(source, start);
167
+
168
+ if (key.kind !== "basic" || key.code === "v") {
169
+ throw invalidType(source);
170
+ }
171
+
172
+ const [value, valueEnd] = parseNode(source, keyEnd);
173
+
174
+ if (source[valueEnd] !== "}") {
175
+ throw invalidType(source);
176
+ }
177
+
178
+ return [key, value, valueEnd + 1];
179
+ };
180
+
181
+ function parseArrayNode(source: string, start: number): [VariantTypeNode, number] {
182
+ if (source[start] === "{") {
183
+ const [key, value, end] = parsePair(source, start + 1);
184
+
185
+ return [{ kind: "dict", entryTypeString: source.slice(start, end), key, value }, end];
186
+ }
187
+
188
+ const [element, end] = parseNode(source, start);
189
+
190
+ return [{ kind: "array", elementTypeString: source.slice(start, end), element }, end];
191
+ }
192
+
193
+ function parseMaybeNode(source: string, start: number): [VariantTypeNode, number] {
194
+ const [element, end] = parseNode(source, start);
195
+
196
+ return [{ kind: "maybe", elementTypeString: source.slice(start, end), element }, end];
197
+ }
198
+
199
+ function parseTupleNode(source: string, start: number): [VariantTypeNode, number] {
200
+ const items: VariantTypeNode[] = [];
201
+ let position = start;
202
+
203
+ while (source[position] !== ")") {
204
+ if (position >= source.length) {
205
+ throw invalidType(source);
206
+ }
207
+
208
+ const [item, end] = parseNode(source, position);
209
+ items.push(item);
210
+ position = end;
211
+ }
212
+
213
+ return [{ kind: "tuple", items }, position + 1];
214
+ }
215
+
216
+ function parseEntryNode(source: string, start: number): [VariantTypeNode, number] {
217
+ const [key, value, end] = parsePair(source, start);
218
+
219
+ return [{ kind: "entry", key, value }, end];
220
+ }
221
+
222
+ const parseNode = (source: string, start: number): [VariantTypeNode, number] => {
223
+ const code = source[start];
224
+
225
+ if (code === undefined) {
226
+ throw invalidType(source);
227
+ }
228
+
229
+ const container = CONTAINER_PARSERS[code];
230
+
231
+ if (container !== undefined) {
232
+ return container(source, start + 1);
233
+ }
234
+
235
+ if (isBasicCode(code)) {
236
+ return [{ kind: "basic", code }, start + 1];
237
+ }
238
+
239
+ throw invalidType(source);
240
+ };
241
+
242
+ const parseVariantType = (typeString: string): VariantTypeNode => {
243
+ const cached = parsedTypes.get(typeString);
244
+
245
+ if (cached !== undefined) {
246
+ return cached;
247
+ }
248
+
249
+ const [node, end] = parseNode(typeString, 0);
250
+
251
+ if (end !== typeString.length) {
252
+ throw invalidType(typeString);
253
+ }
254
+
255
+ parsedTypes.set(typeString, node);
256
+
257
+ return node;
258
+ };
259
+
260
+ const unpackChildren = (variant: GLib.Variant, unpackChild: (child: GLib.Variant) => unknown): unknown[] => {
261
+ const children: unknown[] = [];
262
+ const count = variant.nChildren();
263
+
264
+ for (let index = 0; index < count; index += 1) {
265
+ children.push(unpackChild(variant.getChildValue(index)));
266
+ }
267
+
268
+ return children;
269
+ };
270
+
271
+ const unpackPair = (key: VariantTypeNode, value: VariantTypeNode, entry: GLib.Variant): [unknown, unknown] => [
272
+ unpackNode(key, entry.getChildValue(0)),
273
+ unpackNode(value, entry.getChildValue(1)),
274
+ ];
275
+
276
+ const unpackDict = (
277
+ node: { key: VariantTypeNode; value: VariantTypeNode },
278
+ variant: GLib.Variant,
279
+ ): Record<string, unknown> | Map<unknown, unknown> => {
280
+ const entries = unpackChildren(variant, (entry) => unpackPair(node.key, node.value, entry)) as [unknown, unknown][];
281
+
282
+ if (!isStringKeyed(node.key)) {
283
+ return new Map(entries);
284
+ }
285
+
286
+ const stringEntries: [string, unknown][] = entries.map(([key, value]) => [key as string, value]);
287
+
288
+ return Object.fromEntries(stringEntries);
289
+ };
290
+
291
+ const unpackMaybe = (element: VariantTypeNode, variant: GLib.Variant): unknown => {
292
+ const child = variant.getMaybe();
293
+
294
+ return child === null ? null : unpackNode(element, child);
295
+ };
296
+
297
+ const unpackNode = (node: VariantTypeNode, variant: GLib.Variant): unknown => {
298
+ switch (node.kind) {
299
+ case "basic": {
300
+ return unpackBasic[node.code](variant);
301
+ }
302
+ case "array": {
303
+ return unpackChildren(variant, (child) => unpackNode(node.element, child));
304
+ }
305
+ case "dict": {
306
+ return unpackDict(node, variant);
307
+ }
308
+ case "entry": {
309
+ return unpackPair(node.key, node.value, variant);
310
+ }
311
+ case "tuple": {
312
+ return node.items.map((item, index) => unpackNode(item, variant.getChildValue(index)));
313
+ }
314
+ case "maybe": {
315
+ return unpackMaybe(node.element, variant);
316
+ }
317
+ }
318
+ };
319
+
320
+ const packValidatedString = (
321
+ value: unknown,
322
+ isValid: (value: string) => boolean,
323
+ description: string,
324
+ construct: (value: string) => GLib.Variant,
325
+ ): GLib.Variant => {
326
+ if (typeof value !== "string" || !isValid(value)) {
327
+ throw new Error(`"${String(value)}" is not a valid GVariant ${description}`);
328
+ }
329
+
330
+ return construct(value);
331
+ };
332
+
333
+ function packObjectPath(value: unknown): GLib.Variant {
334
+ return packValidatedString(
335
+ value,
336
+ (path) => GLib.Variant.isObjectPath(path),
337
+ "object path",
338
+ (path) => GLib.Variant.newObjectPath(path),
339
+ );
340
+ }
341
+
342
+ function packSignature(value: unknown): GLib.Variant {
343
+ return packValidatedString(
344
+ value,
345
+ (signature) => GLib.Variant.isSignature(signature),
346
+ "type signature",
347
+ (signature) => GLib.Variant.newSignature(signature),
348
+ );
349
+ }
350
+
351
+ const packEntry = (key: VariantTypeNode, value: VariantTypeNode, pair: [unknown, unknown]): GLib.Variant =>
352
+ GLib.Variant.newDictEntry(packNode(key, pair[0]), packNode(value, pair[1]));
353
+
354
+ const dictEntries = (value: unknown): [unknown, unknown][] =>
355
+ value instanceof Map ? [...value] : Object.entries(value as Record<string, unknown>);
356
+
357
+ const packDict = (
358
+ node: { entryTypeString: string; key: VariantTypeNode; value: VariantTypeNode },
359
+ value: unknown,
360
+ ): GLib.Variant =>
361
+ GLib.Variant.newArray(
362
+ GLib.VariantType.new(node.entryTypeString),
363
+ dictEntries(value).map((pair) => packEntry(node.key, node.value, pair)),
364
+ );
365
+
366
+ const packMaybe = (node: { elementTypeString: string; element: VariantTypeNode }, value: unknown): GLib.Variant =>
367
+ GLib.Variant.newMaybe(
368
+ GLib.VariantType.new(node.elementTypeString),
369
+ value === null ? null : packNode(node.element, value),
370
+ );
371
+
372
+ const packNode = (node: VariantTypeNode, value: unknown): GLib.Variant => {
373
+ switch (node.kind) {
374
+ case "basic": {
375
+ return packBasic[node.code](value);
376
+ }
377
+ case "array": {
378
+ return GLib.Variant.newArray(
379
+ GLib.VariantType.new(node.elementTypeString),
380
+ (value as unknown[]).map((item) => packNode(node.element, item)),
381
+ );
382
+ }
383
+ case "dict": {
384
+ return packDict(node, value);
385
+ }
386
+ case "entry": {
387
+ return packEntry(node.key, node.value, value as [unknown, unknown]);
388
+ }
389
+ case "tuple": {
390
+ return GLib.Variant.newTuple(
391
+ node.items.map((item, index) => packNode(item, (value as unknown[])[index])),
392
+ );
393
+ }
394
+ case "maybe": {
395
+ return packMaybe(node, value);
396
+ }
397
+ }
398
+ };
399
+
400
+ /**
401
+ * Packs a JavaScript value into the `GLib.Variant` a GVariant type string describes, building the
402
+ * nested arrays, dictionaries, tuples and maybes the type calls for. A type string given as a
403
+ * literal also types `value`, so `"as"` takes an array of strings, `"a{sv}"` a record of variants,
404
+ * `"(si)"` a string and a number pair, and a 64-bit type a `bigint`.
405
+ * @param typeString GVariant type of the variant to build, such as `"a{sv}"`.
406
+ * @param value Value to pack, shaped the way `typeString` describes.
407
+ * @returns The packed variant.
408
+ * @throws {Error} When the type string is not one complete GVariant type, or when a value packed as
409
+ * an object path or a type signature is not a valid one.
410
+ */
411
+ const toVariant = <S extends string>(typeString: S, value: VariantValue<S>): GLib.Variant =>
412
+ packNode(parseVariantType(typeString), value);
413
+
414
+ /**
415
+ * Unpacks a `GLib.Variant` into the JavaScript value a GVariant type string describes, the inverse
416
+ * of {@link toVariant}. A dictionary keyed by strings unpacks to a record and one keyed by anything
417
+ * else to a `Map`, an array to an array, a tuple to an array of its members, a maybe to its value or
418
+ * `null`, and a nested variant to the `GLib.Variant` itself.
419
+ * @param typeString GVariant type the variant holds, such as `"a{sv}"`.
420
+ * @param variant Variant to read, which has to hold that type.
421
+ * @returns The unpacked value, typed from `typeString` when it is given as a literal.
422
+ * @throws {Error} When the type string is not one complete GVariant type.
423
+ */
424
+ const fromVariant = <S extends string>(typeString: S, variant: GLib.Variant): VariantValue<S> =>
425
+ unpackNode(parseVariantType(typeString), variant) as VariantValue<S>;
426
+
427
+ export { fromVariant, toVariant, type VariantValue };