@gjsify/node-gi 0.13.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -4,32 +4,158 @@
4
4
  // Reference: refs/node-gtk (romgrk, MIT). Hand-authored loader shim — a native
5
5
  // package's JS entry is a loader, not a tsc artifact, and the repo ignores
6
6
  // `lib/`, so it lives at the package root. The native binary is built by
7
- // node-gyp into build/{Release,Debug}.
7
+ // node-gyp into build/{Release,Debug}; a published package instead ships a
8
+ // prebuild under prebuilds/<platform>-<arch>/.
9
+ //
10
+ // The addon is Node-API (ABI-stable), so it loads unchanged on Node, Bun and
11
+ // Deno — the three runtimes that implement Node-API. Runtime-specific behaviour
12
+ // (the libuv main-loop bridge is Node-only) is gated in gi.js off RUNTIME below.
8
13
  import { createRequire } from 'node:module';
9
14
  import { fileURLToPath } from 'node:url';
10
15
  import { dirname, join } from 'node:path';
11
16
  import { existsSync } from 'node:fs';
17
+ import {
18
+ activateBundledGtkRuntime,
19
+ maybePrependGtkRuntimeDllPath,
20
+ maybeReexecForGtkRuntime,
21
+ maybeWireGtkWindowingEnv,
22
+ } from './gtk-runtime.js';
23
+
24
+ // macOS batteries-included GTK: if a relocated bundle ships for this platform,
25
+ // re-exec ONCE with DYLD_FALLBACK_LIBRARY_PATH pointed at it BEFORE the addon (and
26
+ // its GTK dependency closure) is dlopen'd — dyld only reads that var at launch, and
27
+ // GObject-Introspection needs it to g_module_open type backers (get_type / Pango /
28
+ // Gdk / Graphene) by leaf soname. No-op off darwin, without a bundle, or once the
29
+ // fallback already covers the bundle (the re-exec'd child). Never returns on re-exec.
30
+ maybeReexecForGtkRuntime();
31
+
32
+ // Windows batteries-included GTK: if a bundle ships for this platform, prepend its
33
+ // gtk/bin DLL dir to process.env.PATH BEFORE the addon is require()'d below. Windows
34
+ // re-reads the DLL search path at each LoadLibrary (unlike dyld's launch-time
35
+ // capture), so this in-process PATH mutation is enough for the addon's static DLL
36
+ // imports AND the runtime g_module_open of typelib backers — no re-exec needed.
37
+ // No-op off win32 / without a bundle.
38
+ maybePrependGtkRuntimeDllPath();
39
+
40
+ // Windows FULL-WINDOWING GTK: when the bundle carries the runtime data a real GTK
41
+ // WINDOW needs (compiled GSettings schemas, gdk-pixbuf loaders, icon themes,
42
+ // fontconfig — the `--windowing` superset), wire the env vars that locate it
43
+ // (GSETTINGS_SCHEMA_DIR / GDK_PIXBUF_MODULE_FILE / XDG_DATA_DIRS / FONTCONFIG_*).
44
+ // Strict no-op off win32, without a bundle, or for the display-free bundle (no
45
+ // windowing data) — so a display-free bundle load is byte-unchanged.
46
+ maybeWireGtkWindowingEnv();
12
47
 
13
48
  const require = createRequire(import.meta.url);
14
49
  const here = dirname(fileURLToPath(import.meta.url)); // package root
15
50
 
51
+ /**
52
+ * Which JS runtime we are on. The Node-API addon loads on all three, but the
53
+ * libuv-backed main-loop bridge (startMainLoop) is Node-only — Deno exports no
54
+ * libuv symbols and Bun panics on uv_backend_fd — so Bun/Deno use the portable
55
+ * GLib-iteration pump instead (see gi.js). Detection order matters: Bun and Deno
56
+ * both expose a `process` shim, so probe their own globals first.
57
+ * @type {'bun' | 'deno' | 'node'}
58
+ */
59
+ export const RUNTIME =
60
+ typeof globalThis.Bun !== 'undefined'
61
+ ? 'bun'
62
+ : typeof globalThis.Deno !== 'undefined'
63
+ ? 'deno'
64
+ : 'node';
65
+
66
+ /** Whether we are on Node.js (the only runtime with the libuv main-loop bridge). */
67
+ export const isNodeRuntime = RUNTIME === 'node';
68
+
69
+ function nativeCandidates() {
70
+ const prebuild = join(here, 'prebuilds', `${process.platform}-${process.arch}`, 'node_gi.node');
71
+ const release = join(here, 'build', 'Release', 'node_gi.node');
72
+ const debug = join(here, 'build', 'Debug', 'node_gi.node');
73
+ // NODE_GI_NATIVE pins which binary loads. The package's own test scripts set
74
+ // `build` so local verification always exercises the JUST-BUILT addon — a stale
75
+ // staged prebuild would otherwise shadow build/Release and silently validate
76
+ // the wrong binary (the consumer-facing default below prefers the prebuild).
77
+ const prefer = process.env.NODE_GI_NATIVE;
78
+ if (prefer === 'build') return [release, debug, prebuild];
79
+ if (prefer === 'prebuild') return [prebuild];
80
+ if (prefer) return [prefer]; // an explicit path to a node_gi.node
81
+ // Default: prefer a shipped prebuild so a consumer needs no C toolchain and no
82
+ // node-gyp — the only install path Deno supports (it runs no postinstall build
83
+ // script). Fall back to a locally built addon (Release, then Debug).
84
+ return [prebuild, release, debug];
85
+ }
86
+
16
87
  function loadNative() {
17
- const candidates = [
18
- join(here, 'build', 'Release', 'node_gi.node'),
19
- join(here, 'build', 'Debug', 'node_gi.node'),
20
- ];
21
- for (const candidate of candidates) {
88
+ for (const candidate of nativeCandidates()) {
22
89
  if (existsSync(candidate)) return require(candidate);
23
90
  }
24
91
  throw new Error(
25
- '@gjsify/node-gi: native addon not built. Run `node-gyp rebuild` in ' +
92
+ `@gjsify/node-gi: native addon not found for ${RUNTIME} on ${process.platform}-${process.arch}. ` +
93
+ `Expected a prebuild at prebuilds/${process.platform}-${process.arch}/node_gi.node or a local build. ` +
94
+ 'Run `node-gyp rebuild` in ' +
26
95
  here +
27
- ' (requires a C++ toolchain and the girepository-2.0 / glib-2.0 development headers).',
96
+ ' (requires a C++ toolchain and the girepository-2.0 / glib-2.0 development headers), ' +
97
+ 'or install a package build that ships a prebuild for your platform.',
28
98
  );
29
99
  }
30
100
 
31
101
  const native = loadNative();
32
102
 
103
+ // ---- batteries-included GTK runtime (macOS) ---------------------------------
104
+ //
105
+ // Before any namespace is required, activate a relocated GTK/GI runtime bundle
106
+ // if one ships for this platform (@gjsify/gtk-runtime-<platform>-<arch>, or a
107
+ // staged prebuilds/<platform>-<arch>/gtk/). This prepends the bundle's typelib
108
+ // dir to the GIRepository search path (env-free) so gi:// namespaces load with
109
+ // no Homebrew/system GTK. No-op on platforms without a bundle, and harmless when
110
+ // none is present (the addon then uses the host's GTK as before). Darwin-only
111
+ // today — see gtk-runtime.js + @gjsify/gtk-runtime-darwin-arm64.
112
+ try {
113
+ activateBundledGtkRuntime(native);
114
+ } catch {
115
+ // Never fatal: a missing/partial bundle just leaves the host GTK in charge.
116
+ }
117
+
118
+ // ---- cross-runtime microtask checkpoint (Bun/Deno) --------------------------
119
+ //
120
+ // Node's napi_make_callback runs the nextTick + microtask checkpoint when its
121
+ // callback scope closes, so promise continuations queued by a loop-dispatched
122
+ // GLib→JS callback (a DBus method-call closure, a GLib timeout, a GTK signal)
123
+ // drain at the callback boundary even while a blocking
124
+ // GLib.MainLoop.run()/Gio.Application.run() owns the thread. Bun's and Deno's
125
+ // N-API implementations perform NO checkpoint there (Deno's napi_make_callback
126
+ // is a plain Function::Call; Deno's own FFI trampoline drains explicitly for
127
+ // exactly this reason), and with the runtime's event loop paused for the
128
+ // blocking run's lifetime the queue never drained: an async (Promise-returning)
129
+ // DBus method handler never sent its reply — the client timed out — while sync
130
+ // handlers worked. Register the runtime's own drain primitive with the engine;
131
+ // the loop-dispatched trampolines (src/signals.cc / calls.cc / class.cc) invoke
132
+ // it after each OUTERMOST callback returns (NodeGiMaybeDrainMicrotasks),
133
+ // matching Node's checkpoint and GJS (SpiderMonkey drains the promise-job queue
134
+ // when the last JS frame exits). Node never registers — its checkpoint already
135
+ // runs natively, so the Node path is byte-identical to before.
136
+ if (!isNodeRuntime) {
137
+ try {
138
+ let drain = null;
139
+ if (RUNTIME === 'bun') {
140
+ // JSC: drains the VM's microtask queue; callable mid-stack by design
141
+ // (Bun's own processTicksAndRejections calls it from JS).
142
+ ({ drainMicrotasks: drain } = require('bun:jsc'));
143
+ } else if (typeof globalThis.Deno?.internal === 'symbol') {
144
+ // V8: core.runMicrotasks → op_run_microtasks →
145
+ // Isolate::PerformMicrotaskCheckpoint (a reentrant op, explicitly
146
+ // safe with JS frames on the stack).
147
+ const core = globalThis.Deno[globalThis.Deno.internal]?.core;
148
+ if (typeof core?.runMicrotasks === 'function') drain = () => core.runMicrotasks();
149
+ }
150
+ if (typeof drain === 'function') native.setMicrotaskDrain(drain);
151
+ } catch {
152
+ // Unregistered ⇒ pre-fix behaviour on this runtime build: promise
153
+ // continuations drain only when the runtime's own loop runs
154
+ // (startMainContextPump); async DBus replies inside a BLOCKING run stay
155
+ // unavailable. Never fatal — the sync surface is unaffected.
156
+ }
157
+ }
158
+
33
159
  /**
34
160
  * Require a GObject-Introspection namespace and report its resolved version
35
161
  * and top-level info count. The Node twin of GJS's gi:// / imports.gi load step.
@@ -74,6 +200,27 @@ export const getConstantValue = native.getConstantValue;
74
200
  */
75
201
  export const getEnumValues = native.getEnumValues;
76
202
 
203
+ /**
204
+ * For an enum type registered as a GError domain (e.g. `Gio.IOErrorEnum`), report
205
+ * its domain quark name + numeric quark; `null` for a plain enum. The L1 wrapper
206
+ * attaches this to the enum object so `error.matches(Gio.IOErrorEnum, code)` can
207
+ * resolve the enum to its error domain.
208
+ * @param {string} namespace
209
+ * @param {string} name
210
+ * @returns {{ name: string, quark: number } | null}
211
+ */
212
+ export const getErrorDomain = native.getErrorDomain;
213
+
214
+ /**
215
+ * Register the L1 GLib.Error factory the engine calls when a GI invoke fails, so
216
+ * a failed sync call throws a real `GLib.Error` (instanceof, with `.matches()`).
217
+ * The builder receives `(domainName, domainQuark, code, message)` and returns the
218
+ * Error to throw. Called once by the L1 layer at load.
219
+ * @param {(domainName: string, domainQuark: number, code: number, message: string) => Error} builder
220
+ * @returns {void}
221
+ */
222
+ export const setErrorBuilder = native.setErrorBuilder;
223
+
77
224
  /**
78
225
  * Prepend a directory to the GIRepository typelib search path (call before
79
226
  * requireNamespace for non-system typelibs).
@@ -105,6 +252,18 @@ export const callFunction = native.callFunction;
105
252
  */
106
253
  export const callMethod = native.callMethod;
107
254
 
255
+ /**
256
+ * `true` iff {@link callMethod} would resolve an invocable instance method for
257
+ * `methodName` (literal introspected name or snake_case alias, walking the same
258
+ * own/interface/parent chain). Feature detection — never throws for a merely
259
+ * unknown name; a non-introspectable instance reports `false`. Backs the L1
260
+ * wrapper's GJS-parity property access (`obj.nonexistentMethod === undefined`).
261
+ * @param {unknown} handle a handle from {@link newObject}
262
+ * @param {string} methodName GI method name, e.g. "get_name"
263
+ * @returns {boolean}
264
+ */
265
+ export const hasMethod = native.hasMethod;
266
+
108
267
  /**
109
268
  * Invoke a type-level constructor/static function (e.g. `Gio.File.new_for_path`,
110
269
  * `Gtk.Label.new`) — a function found on a type but taking no instance. The Node
@@ -117,6 +276,18 @@ export const callMethod = native.callMethod;
117
276
  */
118
277
  export const callStaticMethod = native.callStaticMethod;
119
278
 
279
+ /**
280
+ * Construct a boxed/plain struct instance — the `new <Struct>()` path (GJS
281
+ * gi/boxed.cpp parity). A struct WITH a 'new' constructor routes to it with
282
+ * `args` (e.g. `GLib.MainLoop`); a struct WITHOUT one and ZERO args is
283
+ * zero-allocated (`Graphene.Rect`, `Gdk.RGBA`); args without a 'new' throw.
284
+ * @param {string} namespace e.g. "Graphene"
285
+ * @param {string} typeName e.g. "Rect"
286
+ * @param {unknown[]} [args]
287
+ * @returns {unknown} boxed handle
288
+ */
289
+ export const constructStruct = native.constructStruct;
290
+
120
291
  /**
121
292
  * Construct a GObject of `namespace.typeName` with optional construct/settable
122
293
  * properties, returning an opaque handle owned by node-gi (released on GC).
@@ -135,8 +306,8 @@ export const newObject = native.newObject;
135
306
  * signals, and vfunc overrides in the new type's `class_init` — the Node twin of
136
307
  * (the engine half of) GJS's `GObject.registerClass`. A `vfuncs` entry maps a
137
308
  * parent vfunc name (e.g. `constructed`) to a JS function that overrides it; the
138
- * override runs as a method on the instance (`this` is the GObject handle). vfunc
139
- * chain-up to the parent implementation lands in a later milestone.
309
+ * override runs as a method on the instance (`this` is the GObject handle) and can
310
+ * chain up to the parent implementation via {@link callParentVfunc}.
140
311
  * @param {string} name unique GType name, e.g. "MyAction"
141
312
  * @param {string} parentNamespace e.g. "Gio"
142
313
  * @param {string} parentTypeName e.g. "SimpleAction"
@@ -145,15 +316,61 @@ export const newObject = native.newObject;
145
316
  */
146
317
  export const registerClass = native.registerClass;
147
318
 
319
+ /**
320
+ * Register a new GObject subclass of an ALREADY-REGISTERED parent type, given the
321
+ * parent's GType handle (from a previous {@link registerClass} / its `$gtype`)
322
+ * rather than a `namespace.typeName`. This is the multi-level registered-of-
323
+ * registered path: a registered (dynamic) parent has no introspection entry, so it
324
+ * cannot be resolved by name. Inherited custom properties, signals and vfunc slots
325
+ * of registered ancestors compose for free through normal GObject inheritance. The
326
+ * L1 `GObject.registerClass` picks this variant (over {@link registerClass}) when
327
+ * the nearest base is itself a registered class.
328
+ * @param {string} name unique GType name, e.g. "AdwStorybookClamp"
329
+ * @param {unknown} parentGType a GType handle from {@link registerClass} (the parent's `$gtype`)
330
+ * @param {{ properties?, signals?, vfuncs?, template?, cssName?, children?, internalChildren? }} [options]
331
+ * @returns {unknown} opaque type handle
332
+ */
333
+ export const registerClassFromGType = native.registerClassFromGType;
334
+
148
335
  /**
149
336
  * Construct a GObject of a registered type handle (from {@link registerClass})
150
- * with optional construct/settable properties, returning an owned handle.
337
+ * with optional construct/settable properties, returning an owned handle. For a
338
+ * templated type the engine runs `gtk_widget_init_template` before returning.
151
339
  * @param {unknown} typeHandle a handle from {@link registerClass}
152
340
  * @param {Record<string, unknown>} [props]
153
341
  * @returns {unknown} opaque GObject handle
154
342
  */
155
343
  export const constructType = native.constructType;
156
344
 
345
+ /**
346
+ * Chain up to the parent implementation of an overridden vfunc — the engine half
347
+ * of `super.vfunc_<name>(...)`. Invokes the function that was in the instance
348
+ * type's vtable slot BEFORE the registerClass override was installed (the C
349
+ * default, or a JS override further up the chain), with `handle` as the instance
350
+ * and `args` as the vfunc's declared arguments; returns the marshalled vfunc
351
+ * return (or undefined for a void vfunc). The L1 layer wires it to
352
+ * `super.vfunc_<name>` via the introspected base class's prototype chain. Throws
353
+ * if no overridden vfunc by that name owns a slot on the instance's type.
354
+ * @param {unknown} handle a GObject handle (the instance / `this` in the override)
355
+ * @param {string} vfuncName the vfunc name without the `vfunc_` prefix, e.g. "constructed"
356
+ * @param {unknown[]} [args] the vfunc's declared IN arguments
357
+ * @returns {unknown}
358
+ */
359
+ export const callParentVfunc = native.callParentVfunc;
360
+
361
+ /**
362
+ * Resolve a Gtk.Widget composite-template child (bound via the registerClass
363
+ * Children/InternalChildren options) by id on a templated instance —
364
+ * `gtk_widget_get_template_child(widget, G_OBJECT_TYPE(widget), name)`. Returns the
365
+ * child as a wrapped GObject handle (borrowed; owned by the parent via the
366
+ * template) or `null`. The L1 layer assigns it onto the instance (`this.<name>` /
367
+ * `this._<name>`).
368
+ * @param {unknown} handle a templated GObject handle from {@link constructType}
369
+ * @param {string} name the template child id
370
+ * @returns {unknown} opaque child GObject handle, or null
371
+ */
372
+ export const getTemplateChild = native.getTemplateChild;
373
+
157
374
  /**
158
375
  * Read a GObject property.
159
376
  * @param {unknown} handle a handle from {@link newObject}
@@ -188,6 +405,30 @@ export const hasProperty = native.hasProperty;
188
405
  */
189
406
  export const getTypeName = native.getTypeName;
190
407
 
408
+ /**
409
+ * The runtime GType of an introspected registered type, as an opaque node-gi
410
+ * GType handle (a tag-distinct External carrying the GType — NOT a number/pointer).
411
+ * The L1 layer surfaces it as a lazy `Ns.Type.$gtype` getter and feeds it to
412
+ * GType-typed GI arguments (`GObject.type_ensure`, `g_param_spec_object`'s value
413
+ * type, …). Returns `null` for an unknown/unregistered name.
414
+ * @param {string} namespace e.g. "Adw"
415
+ * @param {string} name e.g. "Clamp"
416
+ * @returns {unknown} opaque GType handle, or null
417
+ */
418
+ export const getGType = native.getGType;
419
+
420
+ /**
421
+ * Whether a GObject handle's GType is-a `namespace.typeName` (g_type_is_a — also
422
+ * true when the type implements an interface). Used by the L1 wrapper to pick the
423
+ * right `Gio._promisify` registration when two classes promisify a method of the
424
+ * same name.
425
+ * @param {unknown} handle
426
+ * @param {string} namespace
427
+ * @param {string} typeName
428
+ * @returns {boolean}
429
+ */
430
+ export const isInstanceOf = native.isInstanceOf;
431
+
191
432
  /**
192
433
  * Whether `value` is one of node-gi's GObject-instance handles (tag-checked, no
193
434
  * dereference). Lets the L1 wrapper wrap object-typed return values for chaining
@@ -197,6 +438,16 @@ export const getTypeName = native.getTypeName;
197
438
  */
198
439
  export const isGObjectHandle = native.isGObjectHandle;
199
440
 
441
+ /**
442
+ * Allocate a fresh, zero-initialised GValue and return it as a node-gi boxed
443
+ * handle (GType G_TYPE_VALUE, owned → freed on GC). GObject.Value has no
444
+ * `g_value_new()`, so the L1 layer (gi.js `makeValueClass`) uses this to build a
445
+ * `new GObject.Value()` and then drives `.init(gtype)` / `.set_*` / `.get_*` /
446
+ * `.copy` / `.unset` through the boxed-method path.
447
+ * @returns {unknown} opaque GObject.Value boxed handle
448
+ */
449
+ export const newGValue = native.newGValue;
450
+
200
451
  /**
201
452
  * Invoke an instance method on a boxed/struct handle (e.g. `mainLoop.run()` /
202
453
  * `mainLoop.quit()`). The method is resolved against the boxed GType's
@@ -217,17 +468,163 @@ export const callBoxedMethod = native.callBoxedMethod;
217
468
  */
218
469
  export const isBoxedHandle = native.isBoxedHandle;
219
470
 
471
+ /**
472
+ * Classify a name on a boxed/struct/union handle: 0 (neither) | 1 (method) |
473
+ * 2 (field). Methods take priority (gjs renames a colliding field to `_name`), so
474
+ * the L1 wrapBoxed uses this to route method-dispatch vs field get/set.
475
+ * @param {unknown} handle a node-gi boxed handle
476
+ * @param {string} name GI member name (snake_case)
477
+ * @returns {number}
478
+ */
479
+ export const boxedMemberKind = native.boxedMemberKind;
480
+
481
+ /**
482
+ * Read a named field of a boxed/struct/union handle (`simpleStruct.long_`).
483
+ * Throws if `name` is not a field, or the field is unreadable/unsupported.
484
+ * @param {unknown} handle a node-gi boxed handle
485
+ * @param {string} name GI field name (snake_case)
486
+ * @returns {unknown}
487
+ */
488
+ export const getBoxedField = native.getBoxedField;
489
+
490
+ /**
491
+ * Write a simple-typed field of a boxed/struct/union handle (`union.long_ = 5`).
492
+ * Throws if `name` is not a field, or the field is unwritable/unsupported.
493
+ * @param {unknown} handle a node-gi boxed handle
494
+ * @param {string} name GI field name (snake_case)
495
+ * @param {unknown} value
496
+ * @returns {void}
497
+ */
498
+ export const setBoxedField = native.setBoxedField;
499
+
500
+ /**
501
+ * The boxed handle's GType name (e.g. "GBytes"), or null when it carries no
502
+ * registered GType. Lets the L1 layer attach type-specific conveniences (the
503
+ * GLib.Bytes `toArray` shim) without a per-type wrapper.
504
+ * @param {unknown} value
505
+ * @returns {string | null}
506
+ */
507
+ export const boxedTypeName = native.boxedTypeName;
508
+
509
+ /**
510
+ * Whether `value` is a node-gi GParamSpec handle (tag-checked, no dereference).
511
+ * The L1 wrapper uses it to give a notify handler's pspec / a GParamSpec-typed
512
+ * value the GObject.ParamSpec ergonomics.
513
+ * @param {unknown} value
514
+ * @returns {boolean}
515
+ */
516
+ export const isParamSpecHandle = native.isParamSpecHandle;
517
+
518
+ /**
519
+ * True when `value` is a node-gi non-GObject GObject-fundamental handle (e.g. a
520
+ * GskRenderNode from Gtk.Snapshot.to_node, a GdkEvent) — introspected as object
521
+ * info but ref-counted via its own ref/unref funcs (NOT g_object_ref). The L1
522
+ * wrapper surfaces it as an opaque, round-trippable pass-through handle.
523
+ * @param {unknown} value
524
+ * @returns {boolean}
525
+ */
526
+ export const isFundamentalHandle = native.isFundamentalHandle;
527
+
528
+ /**
529
+ * Read a GParamSpec accessor by name: name | nick | blurb | flags | valueType |
530
+ * ownerType | defaultValue. Backs the L1 GObject.ParamSpec getters + methods.
531
+ * @param {unknown} handle a node-gi GParamSpec handle
532
+ * @param {string} which the accessor name
533
+ * @returns {unknown}
534
+ */
535
+ export const paramSpecProp = native.paramSpecProp;
536
+
537
+ /**
538
+ * Build a GVariant from a GVariant type signature + JS value, returning an owned
539
+ * boxed GLib.Variant handle (the floating ref is sunk; released on GC). The
540
+ * recursive native packer behind `new GLib.Variant(signature, value)`. Supports
541
+ * the basics `b y n q i u x t h d s o g`, `v`, `m*` maybe, `a*` arrays (incl.
542
+ * `as`, `ay`, `a{..}` dicts), `(...)` tuples and `{kv}` dict-entries.
543
+ * @param {string} signature a GVariant type string, e.g. "a{sv}", "(si)", "s"
544
+ * @param {unknown} [value]
545
+ * @returns {unknown} boxed GLib.Variant handle
546
+ */
547
+ export const variantNew = native.variantNew;
548
+
549
+ /**
550
+ * Unpack a GLib.Variant handle to a JS value. `deep` unpacks container children
551
+ * (one level for nested `v` variants, which stay Variants unless `recursive`);
552
+ * `recursive` additionally unwraps nested `v` variants to plain JS. Drives the
553
+ * L1 `.unpack()` (deep=false), `.deepUnpack()` (deep=true) and
554
+ * `.recursiveUnpack()` (deep=true, recursive=true).
555
+ * @param {unknown} handle a handle from {@link variantNew} (or a returned Variant)
556
+ * @param {boolean} [deep]
557
+ * @param {boolean} [recursive]
558
+ * @returns {unknown}
559
+ */
560
+ export const variantUnpack = native.variantUnpack;
561
+
562
+ /**
563
+ * The GVariant type string of a GLib.Variant handle (e.g. "a{sv}").
564
+ * @param {unknown} handle
565
+ * @returns {string}
566
+ */
567
+ export const variantGetTypeString = native.variantGetTypeString;
568
+
569
+ /**
570
+ * Whether `value` is one of node-gi's GLib.Variant handles (a boxed handle
571
+ * tagged with the GVariant GType; tag-checked, no dereference). The L1 wrapper
572
+ * uses it to give returned/unpacked variants the GLib.Variant ergonomics.
573
+ * @param {unknown} value
574
+ * @returns {boolean}
575
+ */
576
+ export const isVariantHandle = native.isVariantHandle;
577
+
220
578
  /**
221
579
  * Attach the libuv-backed GSource to the default GLib main context so a blocking
222
580
  * GLib main loop (`GLib.MainLoop.run()`, `Gio.Application.run()`) keeps Node's
223
581
  * timers, promises and I/O alive — the Node twin of GJS running the GLib loop as
224
- * the process loop. Idempotent and harmless until a GLib loop actually runs (it
225
- * adds no libuv handle, so it neither keeps Node alive nor pumps libuv on its
226
- * own). The L1 layer calls it once when a namespace is first required.
582
+ * the process loop. Also arms the uv-driven GLib AUTO-PUMP for the non-blocking
583
+ * case: pending GLib sources (Gio async completions, GLib timeouts/idles, DBus)
584
+ * dispatch from Node's own libuv loop, so a plain `node bundle.mjs` that awaits a
585
+ * Gio async op needs NO explicit mainloop (matching GJS, where the GLib loop is
586
+ * the process loop). An in-flight scope=async callback (GAsyncReadyCallback) and
587
+ * an armed GLib timeout keep the process alive like Node's own pending I/O and
588
+ * timers; a purely-sync program still exits immediately. Idempotent. The L1 layer
589
+ * calls it once when a namespace is first required.
227
590
  * @returns {void}
228
591
  */
229
592
  export const startMainLoop = native.startMainLoop;
230
593
 
594
+ /**
595
+ * Iterate the default GLib main context once, dispatching any ready sources (GIO
596
+ * async callbacks, GLib timeouts/idles, DBus). Pure GLib — no libuv — so it is the
597
+ * portable main-loop primitive on Bun/Deno, where {@link startMainLoop} can't run.
598
+ * The L1 layer drives it from a JS timer to co-pump GLib while the runtime's own
599
+ * event loop stays in control. Returns true if a source was dispatched.
600
+ * @param {boolean} [mayBlock] block until a source is ready (default false)
601
+ * @returns {boolean}
602
+ */
603
+ export const iterateMainContext = native.iterateMainContext;
604
+
605
+ /**
606
+ * Drain ready GLib sources + re-arm the auto-pump's libuv wake-ups NOW (no-op
607
+ * unless {@link startMainLoop} armed the pump, i.e. Node's main env). The L1
608
+ * layer wires this to `process.on('beforeExit')`: with only unref'd handles
609
+ * active, `uv_run` exits without ever running the pump's prepare/check phase, so
610
+ * an otherwise-empty loop would never arm GLib's timer wake-up — beforeExit
611
+ * fires exactly then, and the kick revives the loop while GLib still has
612
+ * scheduled work. Rarely needed directly.
613
+ * @returns {void}
614
+ */
615
+ export const pumpKick = native.pumpKick;
616
+
617
+ /**
618
+ * Register the runtime-native microtask drain the engine runs after each
619
+ * OUTERMOST loop-dispatched GLib→JS callback returns (the cross-runtime
620
+ * microtask checkpoint — see the registration block above). Registered
621
+ * automatically at addon load on Bun/Deno; never call it on Node (its
622
+ * napi_make_callback already checkpoints natively). Exposed for completeness.
623
+ * @param {() => void} drain
624
+ * @returns {void}
625
+ */
626
+ export const setMicrotaskDrain = native.setMicrotaskDrain;
627
+
231
628
  /**
232
629
  * Connect a JS callback to a GObject signal. Returns a handler id for
233
630
  * {@link disconnectSignal}. The callback receives the signal arguments
@@ -258,4 +655,40 @@ export const emitSignal = native.emitSignal;
258
655
  */
259
656
  export const disconnectSignal = native.disconnectSignal;
260
657
 
658
+ /**
659
+ * Register the L1 resolver that maps a Gtk.Template `<signal handler="…">` handler
660
+ * name to the instance's bound JS method, for the engine's template-callback scope.
661
+ * @param {(handle: unknown, handlerName: string) => ((...args: unknown[]) => unknown) | undefined} resolver
662
+ * @returns {void}
663
+ */
664
+ export const setTemplateCallbackResolver = native.setTemplateCallbackResolver;
665
+
666
+ /**
667
+ * Install (or clear, with `null`) the structured-log writer function — the
668
+ * engine's twin of GjsPrivate.log_set_writer_func backing GLib.log_set_writer_func.
669
+ * The writer receives (logLevel: number, fields: Record<string, Uint8Array|null>)
670
+ * and returns a GLib.LogWriterOutput. NOTE: GLib allows at most ONE
671
+ * g_log_set_writer_func install per process (a second install aborts in GLib).
672
+ */
673
+ export const logSetWriterFunc = native.logSetWriterFunc;
674
+
675
+ /**
676
+ * Route structured logs back to the default writer (the engine's twin of
677
+ * GjsPrivate.log_set_writer_default backing GLib.log_set_writer_default).
678
+ */
679
+ export const logSetWriterDefault = native.logSetWriterDefault;
680
+
681
+ /**
682
+ * g_object_bind_property_full with JS transform functions — the engine's twin of
683
+ * GjsPrivate.g_object_bind_property_full backing
684
+ * GObject.Object.prototype.bind_property_full.
685
+ */
686
+ export const bindPropertyFull = native.bindPropertyFull;
687
+
688
+ /**
689
+ * g_binding_group_bind_full with JS transform functions — the engine's twin of
690
+ * GjsPrivate.g_binding_group_bind_full backing GObject.BindingGroup.bind_full.
691
+ */
692
+ export const bindingGroupBindFull = native.bindingGroupBindFull;
693
+
261
694
  export default native;