@gjsify/node-gi 0.26.0 → 0.27.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.d.ts CHANGED
@@ -3,12 +3,12 @@
3
3
 
4
4
  /** Result of requiring a GObject-Introspection namespace. */
5
5
  export interface RequiredNamespace {
6
- /** The namespace name, e.g. "GLib". */
7
- namespace: string;
8
- /** The resolved typelib version, e.g. "2.0". */
9
- version: string;
10
- /** Number of top-level introspection infos in the namespace. */
11
- infoCount: number;
6
+ /** The namespace name, e.g. "GLib". */
7
+ namespace: string;
8
+ /** The resolved typelib version, e.g. "2.0". */
9
+ version: string;
10
+ /** Number of top-level introspection infos in the namespace. */
11
+ infoCount: number;
12
12
  }
13
13
 
14
14
  /**
@@ -22,16 +22,16 @@ export function listInfoNames(namespace: string): string[];
22
22
 
23
23
  /** The introspection kind of a top-level namespace member. */
24
24
  export type InfoKind =
25
- | 'function'
26
- | 'object'
27
- | 'interface'
28
- | 'struct'
29
- | 'union'
30
- | 'enum'
31
- | 'flags'
32
- | 'constant'
33
- | 'callback'
34
- | 'other';
25
+ | 'function'
26
+ | 'object'
27
+ | 'interface'
28
+ | 'struct'
29
+ | 'union'
30
+ | 'enum'
31
+ | 'flags'
32
+ | 'constant'
33
+ | 'callback'
34
+ | 'other';
35
35
 
36
36
  /**
37
37
  * Classify a top-level namespace member so the L1 wrapper can decide how to
@@ -53,17 +53,14 @@ export function getEnumValues(namespace: string, name: string): Record<string, n
53
53
  * For an enum type registered as a GError domain (e.g. `Gio.IOErrorEnum`), report
54
54
  * its domain quark name + numeric quark; `null` for a plain enum.
55
55
  */
56
- export function getErrorDomain(
57
- namespace: string,
58
- name: string,
59
- ): { name: string; quark: number } | null;
56
+ export function getErrorDomain(namespace: string, name: string): { name: string; quark: number } | null;
60
57
 
61
58
  /**
62
59
  * Register the L1 GLib.Error factory the engine calls when a GI invoke fails, so
63
60
  * a failed sync call throws a real `GLib.Error` (instanceof, with `.matches()`).
64
61
  */
65
62
  export function setErrorBuilder(
66
- builder: (domainName: string, domainQuark: number, code: number, message: string) => Error,
63
+ builder: (domainName: string, domainQuark: number, code: number, message: string) => Error,
67
64
  ): void;
68
65
 
69
66
  /** Prepend a directory to the GIRepository typelib search path. */
@@ -106,12 +103,7 @@ export function hasMethod(handle: GObjectHandle, methodName: string): boolean;
106
103
  * twin of `Ns.Class.method(...)`. OUT/INOUT params follow {@link callFunction}'s
107
104
  * return-tuple convention.
108
105
  */
109
- export function callStaticMethod(
110
- namespace: string,
111
- typeName: string,
112
- methodName: string,
113
- args?: unknown[],
114
- ): unknown;
106
+ export function callStaticMethod(namespace: string, typeName: string, methodName: string, args?: unknown[]): unknown;
115
107
 
116
108
  /**
117
109
  * Construct a boxed/plain struct instance — the `new <Struct>()` path (GJS
@@ -133,11 +125,7 @@ export type GObjectHandle = { readonly __nodeGiGObject: unique symbol };
133
125
  * properties. Milestone 1: fundamental-typed properties (numbers, booleans,
134
126
  * strings, enums/flags).
135
127
  */
136
- export function newObject(
137
- namespace: string,
138
- typeName: string,
139
- props?: Record<string, unknown>,
140
- ): GObjectHandle;
128
+ export function newObject(namespace: string, typeName: string, props?: Record<string, unknown>): GObjectHandle;
141
129
 
142
130
  /**
143
131
  * Opaque handle to a registered GType (from {@link registerClass}). Pass it to
@@ -147,30 +135,30 @@ export type TypeHandle = { readonly __nodeGiGType: unique symbol };
147
135
 
148
136
  /** A custom GObject property declaration for {@link registerClass}. */
149
137
  export interface PropertySpec {
150
- /** Property name (canonical, e.g. "my-prop"). */
151
- name: string;
152
- /** Value type. */
153
- type: 'string' | 'boolean' | 'int' | 'uint' | 'int64' | 'uint64' | 'double' | 'float';
154
- /** `GParamFlags` bitfield (default `G_PARAM_READWRITE`). */
155
- flags?: number;
156
- /** Default value (type-appropriate). */
157
- default?: string | number | boolean;
158
- /** Minimum (numeric types). */
159
- minimum?: number;
160
- /** Maximum (numeric types). */
161
- maximum?: number;
138
+ /** Property name (canonical, e.g. "my-prop"). */
139
+ name: string;
140
+ /** Value type. */
141
+ type: 'string' | 'boolean' | 'int' | 'uint' | 'int64' | 'uint64' | 'double' | 'float';
142
+ /** `GParamFlags` bitfield (default `G_PARAM_READWRITE`). */
143
+ flags?: number;
144
+ /** Default value (type-appropriate). */
145
+ default?: string | number | boolean;
146
+ /** Minimum (numeric types). */
147
+ minimum?: number;
148
+ /** Maximum (numeric types). */
149
+ maximum?: number;
162
150
  }
163
151
 
164
152
  /** A custom GObject signal declaration for {@link registerClass}. */
165
153
  export interface SignalSpec {
166
- /** Signal name (e.g. "my-signal"). */
167
- name: string;
168
- /** Parameter types (same vocabulary as {@link PropertySpec.type}, plus "object"). */
169
- paramTypes?: string[];
170
- /** Return type ("void" by default). */
171
- returnType?: string;
172
- /** `GSignalFlags` bitfield (default `G_SIGNAL_RUN_LAST`). */
173
- flags?: number;
154
+ /** Signal name (e.g. "my-signal"). */
155
+ name: string;
156
+ /** Parameter types (same vocabulary as {@link PropertySpec.type}, plus "object"). */
157
+ paramTypes?: string[];
158
+ /** Return type ("void" by default). */
159
+ returnType?: string;
160
+ /** `GSignalFlags` bitfield (default `G_SIGNAL_RUN_LAST`). */
161
+ flags?: number;
174
162
  }
175
163
 
176
164
  /**
@@ -185,23 +173,23 @@ export type VFuncMap = Record<string, (this: GObjectHandle, ...args: unknown[])
185
173
 
186
174
  /** Custom properties, signals + vfunc overrides installed on a {@link registerClass} subtype. */
187
175
  export interface RegisterClassOptions {
188
- properties?: PropertySpec[];
189
- signals?: SignalSpec[];
190
- vfuncs?: VFuncMap;
191
- /**
192
- * A Gtk.Widget composite template. Either inline UI-XML (a `Uint8Array`/Buffer
193
- * or a string) installed via `gtk_widget_class_set_template`, or a
194
- * `"resource:///…"` path string installed via
195
- * `gtk_widget_class_set_template_from_resource`. Installed in the subtype's
196
- * `class_init`; `gtk_widget_init_template` runs at construction.
197
- */
198
- template?: Uint8Array | string;
199
- /** `gtk_widget_class_set_css_name` for the subtype (optional). */
200
- cssName?: string;
201
- /** Template child ids to bind + expose publicly (via `gtk_widget_get_template_child`). */
202
- children?: string[];
203
- /** Template child ids to bind as internal children + expose privately. */
204
- internalChildren?: string[];
176
+ properties?: PropertySpec[];
177
+ signals?: SignalSpec[];
178
+ vfuncs?: VFuncMap;
179
+ /**
180
+ * A Gtk.Widget composite template. Either inline UI-XML (a `Uint8Array`/Buffer
181
+ * or a string) installed via `gtk_widget_class_set_template`, or a
182
+ * `"resource:///…"` path string installed via
183
+ * `gtk_widget_class_set_template_from_resource`. Installed in the subtype's
184
+ * `class_init`; `gtk_widget_init_template` runs at construction.
185
+ */
186
+ template?: Uint8Array | string;
187
+ /** `gtk_widget_class_set_css_name` for the subtype (optional). */
188
+ cssName?: string;
189
+ /** Template child ids to bind + expose publicly (via `gtk_widget_get_template_child`). */
190
+ children?: string[];
191
+ /** Template child ids to bind as internal children + expose privately. */
192
+ internalChildren?: string[];
205
193
  }
206
194
 
207
195
  /**
@@ -214,10 +202,10 @@ export interface RegisterClassOptions {
214
202
  * parent vtable pointer it displaces, so it can chain up via {@link callParentVfunc}.
215
203
  */
216
204
  export function registerClass(
217
- name: string,
218
- parentNamespace: string,
219
- parentTypeName: string,
220
- options?: RegisterClassOptions,
205
+ name: string,
206
+ parentNamespace: string,
207
+ parentTypeName: string,
208
+ options?: RegisterClassOptions,
221
209
  ): TypeHandle;
222
210
 
223
211
  /**
@@ -231,11 +219,7 @@ export function registerClass(
231
219
  * OUT/INOUT argument (chain-up of those is not yet supported — a catchable throw,
232
220
  * never a crash).
233
221
  */
234
- export function callParentVfunc(
235
- handle: GObjectHandle,
236
- vfuncName: string,
237
- args?: unknown[],
238
- ): unknown;
222
+ export function callParentVfunc(handle: GObjectHandle, vfuncName: string, args?: unknown[]): unknown;
239
223
 
240
224
  /**
241
225
  * Construct a GObject of a registered type handle (from {@link registerClass})
@@ -463,10 +447,10 @@ export function setMicrotaskDrain(drain: () => void): void;
463
447
  * milestone).
464
448
  */
465
449
  export function connectSignal(
466
- handle: GObjectHandle,
467
- signalName: string,
468
- callback: (...args: unknown[]) => unknown,
469
- after?: boolean,
450
+ handle: GObjectHandle,
451
+ signalName: string,
452
+ callback: (...args: unknown[]) => unknown,
453
+ after?: boolean,
470
454
  ): number;
471
455
 
472
456
  /** Emit a signal; returns the signal's return value (undefined for void signals). */
@@ -480,10 +464,7 @@ export function disconnectSignal(handle: GObjectHandle, handlerId: number): void
480
464
  * name to the instance's bound JS method (engine template-callback scope).
481
465
  */
482
466
  export function setTemplateCallbackResolver(
483
- resolver: (
484
- handle: GObjectHandle,
485
- handlerName: string,
486
- ) => ((...args: unknown[]) => unknown) | undefined,
467
+ resolver: (handle: GObjectHandle, handlerName: string) => ((...args: unknown[]) => unknown) | undefined,
487
468
  ): void;
488
469
 
489
470
  /**
@@ -494,7 +475,7 @@ export function setTemplateCallbackResolver(
494
475
  * process (a second aborts in GLib).
495
476
  */
496
477
  export function logSetWriterFunc(
497
- writer: ((logLevel: number, fields: Record<string, Uint8Array | null>) => number) | null,
478
+ writer: ((logLevel: number, fields: Record<string, Uint8Array | null>) => number) | null,
498
479
  ): void;
499
480
 
500
481
  /** Route structured logs back to the default writer (GLib.log_set_writer_default). */
@@ -506,13 +487,13 @@ export function logSetWriterDefault(): void;
506
487
  * `(binding, sourceValue) => [ok: boolean, targetValue]`. Returns the GBinding handle.
507
488
  */
508
489
  export function bindPropertyFull(
509
- source: GObjectHandle,
510
- sourceProperty: string,
511
- target: GObjectHandle,
512
- targetProperty: string,
513
- flags: number,
514
- transformTo: ((...args: unknown[]) => unknown) | null,
515
- transformFrom: ((...args: unknown[]) => unknown) | null,
490
+ source: GObjectHandle,
491
+ sourceProperty: string,
492
+ target: GObjectHandle,
493
+ targetProperty: string,
494
+ flags: number,
495
+ transformTo: ((...args: unknown[]) => unknown) | null,
496
+ transformFrom: ((...args: unknown[]) => unknown) | null,
516
497
  ): GObjectHandle;
517
498
 
518
499
  /**
@@ -520,66 +501,66 @@ export function bindPropertyFull(
520
501
  * GObject.BindingGroup.bind_full). Same transform contract as {@link bindPropertyFull}.
521
502
  */
522
503
  export function bindingGroupBindFull(
523
- group: GObjectHandle,
524
- sourceProperty: string,
525
- target: GObjectHandle,
526
- targetProperty: string,
527
- flags: number,
528
- transformTo: ((...args: unknown[]) => unknown) | null,
529
- transformFrom: ((...args: unknown[]) => unknown) | null,
504
+ group: GObjectHandle,
505
+ sourceProperty: string,
506
+ target: GObjectHandle,
507
+ targetProperty: string,
508
+ flags: number,
509
+ transformTo: ((...args: unknown[]) => unknown) | null,
510
+ transformFrom: ((...args: unknown[]) => unknown) | null,
530
511
  ): void;
531
512
 
532
513
  declare const native: {
533
- requireNamespace: typeof requireNamespace;
534
- listInfoNames: typeof listInfoNames;
535
- findInfo: typeof findInfo;
536
- getConstantValue: typeof getConstantValue;
537
- getEnumValues: typeof getEnumValues;
538
- getErrorDomain: typeof getErrorDomain;
539
- setErrorBuilder: typeof setErrorBuilder;
540
- prependSearchPath: typeof prependSearchPath;
541
- callFunction: typeof callFunction;
542
- callMethod: typeof callMethod;
543
- hasMethod: typeof hasMethod;
544
- callStaticMethod: typeof callStaticMethod;
545
- constructStruct: typeof constructStruct;
546
- newObject: typeof newObject;
547
- registerClass: typeof registerClass;
548
- constructType: typeof constructType;
549
- getTemplateChild: typeof getTemplateChild;
550
- getProperty: typeof getProperty;
551
- setProperty: typeof setProperty;
552
- hasProperty: typeof hasProperty;
553
- getTypeName: typeof getTypeName;
554
- getGType: typeof getGType;
555
- isInstanceOf: typeof isInstanceOf;
556
- isGObjectHandle: typeof isGObjectHandle;
557
- newGValue: typeof newGValue;
558
- callBoxedMethod: typeof callBoxedMethod;
559
- isBoxedHandle: typeof isBoxedHandle;
560
- boxedMemberKind: typeof boxedMemberKind;
561
- getBoxedField: typeof getBoxedField;
562
- setBoxedField: typeof setBoxedField;
563
- boxedTypeName: typeof boxedTypeName;
564
- isParamSpecHandle: typeof isParamSpecHandle;
565
- paramSpecProp: typeof paramSpecProp;
566
- variantNew: typeof variantNew;
567
- variantUnpack: typeof variantUnpack;
568
- variantGetTypeString: typeof variantGetTypeString;
569
- isVariantHandle: typeof isVariantHandle;
570
- startMainLoop: typeof startMainLoop;
571
- iterateMainContext: typeof iterateMainContext;
572
- mainContextHasPending: typeof mainContextHasPending;
573
- makePumpPendingCount: typeof makePumpPendingCount;
574
- pumpKick: typeof pumpKick;
575
- setMicrotaskDrain: typeof setMicrotaskDrain;
576
- connectSignal: typeof connectSignal;
577
- emitSignal: typeof emitSignal;
578
- disconnectSignal: typeof disconnectSignal;
579
- setTemplateCallbackResolver: typeof setTemplateCallbackResolver;
580
- logSetWriterFunc: typeof logSetWriterFunc;
581
- logSetWriterDefault: typeof logSetWriterDefault;
582
- bindPropertyFull: typeof bindPropertyFull;
583
- bindingGroupBindFull: typeof bindingGroupBindFull;
514
+ requireNamespace: typeof requireNamespace;
515
+ listInfoNames: typeof listInfoNames;
516
+ findInfo: typeof findInfo;
517
+ getConstantValue: typeof getConstantValue;
518
+ getEnumValues: typeof getEnumValues;
519
+ getErrorDomain: typeof getErrorDomain;
520
+ setErrorBuilder: typeof setErrorBuilder;
521
+ prependSearchPath: typeof prependSearchPath;
522
+ callFunction: typeof callFunction;
523
+ callMethod: typeof callMethod;
524
+ hasMethod: typeof hasMethod;
525
+ callStaticMethod: typeof callStaticMethod;
526
+ constructStruct: typeof constructStruct;
527
+ newObject: typeof newObject;
528
+ registerClass: typeof registerClass;
529
+ constructType: typeof constructType;
530
+ getTemplateChild: typeof getTemplateChild;
531
+ getProperty: typeof getProperty;
532
+ setProperty: typeof setProperty;
533
+ hasProperty: typeof hasProperty;
534
+ getTypeName: typeof getTypeName;
535
+ getGType: typeof getGType;
536
+ isInstanceOf: typeof isInstanceOf;
537
+ isGObjectHandle: typeof isGObjectHandle;
538
+ newGValue: typeof newGValue;
539
+ callBoxedMethod: typeof callBoxedMethod;
540
+ isBoxedHandle: typeof isBoxedHandle;
541
+ boxedMemberKind: typeof boxedMemberKind;
542
+ getBoxedField: typeof getBoxedField;
543
+ setBoxedField: typeof setBoxedField;
544
+ boxedTypeName: typeof boxedTypeName;
545
+ isParamSpecHandle: typeof isParamSpecHandle;
546
+ paramSpecProp: typeof paramSpecProp;
547
+ variantNew: typeof variantNew;
548
+ variantUnpack: typeof variantUnpack;
549
+ variantGetTypeString: typeof variantGetTypeString;
550
+ isVariantHandle: typeof isVariantHandle;
551
+ startMainLoop: typeof startMainLoop;
552
+ iterateMainContext: typeof iterateMainContext;
553
+ mainContextHasPending: typeof mainContextHasPending;
554
+ makePumpPendingCount: typeof makePumpPendingCount;
555
+ pumpKick: typeof pumpKick;
556
+ setMicrotaskDrain: typeof setMicrotaskDrain;
557
+ connectSignal: typeof connectSignal;
558
+ emitSignal: typeof emitSignal;
559
+ disconnectSignal: typeof disconnectSignal;
560
+ setTemplateCallbackResolver: typeof setTemplateCallbackResolver;
561
+ logSetWriterFunc: typeof logSetWriterFunc;
562
+ logSetWriterDefault: typeof logSetWriterDefault;
563
+ bindPropertyFull: typeof bindPropertyFull;
564
+ bindingGroupBindFull: typeof bindingGroupBindFull;
584
565
  };
585
566
  export default native;
package/index.js CHANGED
@@ -11,9 +11,8 @@
11
11
  // Deno — the three runtimes that implement Node-API. Runtime-specific behaviour
12
12
  // (the libuv main-loop bridge is Node-only) is gated in gi.js off RUNTIME below.
13
13
  import { createRequire } from 'node:module';
14
- import { fileURLToPath } from 'node:url';
15
- import { dirname, join } from 'node:path';
16
14
  import { existsSync } from 'node:fs';
15
+ import { hostTarget, nativeCandidates, packageRoot } from './native-paths.js';
17
16
  import {
18
17
  activateBundledGtkRuntime,
19
18
  maybePrependGtkRuntimeDllPath,
@@ -46,7 +45,7 @@ maybePrependGtkRuntimeDllPath();
46
45
  maybeWireGtkWindowingEnv();
47
46
 
48
47
  const require = createRequire(import.meta.url);
49
- const here = dirname(fileURLToPath(import.meta.url)); // package root
48
+ const here = packageRoot; // the package root, from the shared native-paths module
50
49
 
51
50
  /**
52
51
  * Which JS runtime we are on. The Node-API addon loads on all four: Node, Bun
@@ -61,35 +60,17 @@ const here = dirname(fileURLToPath(import.meta.url)); // package root
61
60
  * @type {'bun' | 'deno' | 'gjs' | 'node'}
62
61
  */
63
62
  export const RUNTIME =
64
- typeof globalThis.imports !== 'undefined' && typeof globalThis.print === 'function'
65
- ? 'gjs'
66
- : typeof globalThis.Bun !== 'undefined'
67
- ? 'bun'
68
- : typeof globalThis.Deno !== 'undefined'
69
- ? 'deno'
70
- : 'node';
63
+ typeof globalThis.imports !== 'undefined' && typeof globalThis.print === 'function'
64
+ ? 'gjs'
65
+ : typeof globalThis.Bun !== 'undefined'
66
+ ? 'bun'
67
+ : typeof globalThis.Deno !== 'undefined'
68
+ ? 'deno'
69
+ : 'node';
71
70
 
72
71
  /** Whether we are on Node.js (the only runtime with the libuv main-loop bridge). */
73
72
  export const isNodeRuntime = RUNTIME === 'node';
74
73
 
75
- function nativeCandidates() {
76
- const prebuild = join(here, 'prebuilds', `${process.platform}-${process.arch}`, 'node_gi.node');
77
- const release = join(here, 'build', 'Release', 'node_gi.node');
78
- const debug = join(here, 'build', 'Debug', 'node_gi.node');
79
- // NODE_GI_NATIVE pins which binary loads. The package's own test scripts set
80
- // `build` so local verification always exercises the JUST-BUILT addon — a stale
81
- // staged prebuild would otherwise shadow build/Release and silently validate
82
- // the wrong binary (the consumer-facing default below prefers the prebuild).
83
- const prefer = process.env.NODE_GI_NATIVE;
84
- if (prefer === 'build') return [release, debug, prebuild];
85
- if (prefer === 'prebuild') return [prebuild];
86
- if (prefer) return [prefer]; // an explicit path to a node_gi.node
87
- // Default: prefer a shipped prebuild so a consumer needs no C toolchain and no
88
- // node-gyp — the only install path Deno supports (it runs no postinstall build
89
- // script). Fall back to a locally built addon (Release, then Debug).
90
- return [prebuild, release, debug];
91
- }
92
-
93
74
  // GJS host mode: the addon is loaded through the @gjsify/napi shim, not
94
75
  // `require()`. The loader native is `globalThis.__gjsifyNapiLoadAddon`,
95
76
  // installed by `GjsifyNapi.init()`; if another consumer (the @gjsify/napi L1)
@@ -97,40 +78,40 @@ function nativeCandidates() {
97
78
  // GjsifyNapi typelib must be resolvable (GI_TYPELIB_PATH/LD_LIBRARY_PATH →
98
79
  // the shim prebuild).
99
80
  function gjsLoadAddon(path) {
100
- let load = globalThis.__gjsifyNapiLoadAddon;
101
- if (typeof load !== 'function') {
102
- const GjsifyNapi = globalThis.imports?.gi?.GjsifyNapi;
103
- if (!GjsifyNapi || !GjsifyNapi.init()) {
104
- throw new Error(
105
- '@gjsify/node-gi: GJS host mode needs the @gjsify/napi shim — the GjsifyNapi ' +
106
- 'typelib is not loadable (point GI_TYPELIB_PATH and LD_LIBRARY_PATH at its prebuild).',
107
- );
108
- }
109
- load = globalThis.__gjsifyNapiLoadAddon;
81
+ let load = globalThis.__gjsifyNapiLoadAddon;
110
82
  if (typeof load !== 'function') {
111
- throw new Error('@gjsify/node-gi: GjsifyNapi.init() did not install the addon loader.');
83
+ const GjsifyNapi = globalThis.imports?.gi?.GjsifyNapi;
84
+ if (!GjsifyNapi || !GjsifyNapi.init()) {
85
+ throw new Error(
86
+ '@gjsify/node-gi: GJS host mode needs the @gjsify/napi shim — the GjsifyNapi ' +
87
+ 'typelib is not loadable (point GI_TYPELIB_PATH and LD_LIBRARY_PATH at its prebuild).',
88
+ );
89
+ }
90
+ load = globalThis.__gjsifyNapiLoadAddon;
91
+ if (typeof load !== 'function') {
92
+ throw new Error('@gjsify/node-gi: GjsifyNapi.init() did not install the addon loader.');
93
+ }
94
+ // Capture-then-delete, the same no-side-channel discipline as the L1.
95
+ delete globalThis.__gjsifyNapiLoadAddon;
112
96
  }
113
- // Capture-then-delete, the same no-side-channel discipline as the L1.
114
- delete globalThis.__gjsifyNapiLoadAddon;
115
- }
116
- return load(path);
97
+ return load(path);
117
98
  }
118
99
 
119
100
  function loadNative() {
120
- for (const candidate of nativeCandidates()) {
121
- if (existsSync(candidate)) {
122
- return RUNTIME === 'gjs' ? gjsLoadAddon(candidate) : require(candidate);
101
+ for (const candidate of nativeCandidates()) {
102
+ if (existsSync(candidate)) {
103
+ return RUNTIME === 'gjs' ? gjsLoadAddon(candidate) : require(candidate);
104
+ }
123
105
  }
124
- }
125
- throw new Error(
126
- `@gjsify/node-gi: native addon not found for ${RUNTIME} on ${process.platform}-${process.arch}. ` +
127
- `Expected a prebuild at prebuilds/${process.platform}-${process.arch}/node_gi.node or a local build. ` +
128
- 'Run `node-gyp rebuild` in ' +
129
- here +
130
- ' (requires a C++ toolchain and the girepository-2.0 / glib-2.0 development headers), ' +
131
- 'or install a package build that ships a prebuild for your platform. ' +
132
- 'Under a bundled GJS app (import.meta anchors at the bundle), set NODE_GI_NATIVE to the addon path.',
133
- );
106
+ throw new Error(
107
+ `@gjsify/node-gi: native addon not found for ${RUNTIME} on ${hostTarget()}. ` +
108
+ `Expected a prebuild at prebuilds/${hostTarget()}/node_gi.node or a local build. ` +
109
+ 'Run `node-gyp rebuild` in ' +
110
+ here +
111
+ ' (requires a C++ toolchain and the girepository-2.0 / glib-2.0 development headers), ' +
112
+ 'or install a package build that ships a prebuild for your platform. ' +
113
+ 'Under a bundled GJS app (import.meta anchors at the bundle), set NODE_GI_NATIVE to the addon path.',
114
+ );
134
115
  }
135
116
 
136
117
  const native = loadNative();
@@ -142,12 +123,13 @@ const native = loadNative();
142
123
  // staged prebuilds/<platform>-<arch>/gtk/). This prepends the bundle's typelib
143
124
  // dir to the GIRepository search path (env-free) so gi:// namespaces load with
144
125
  // no Homebrew/system GTK. No-op on platforms without a bundle, and harmless when
145
- // none is present (the addon then uses the host's GTK as before). Darwin-only
146
- // today — see gtk-runtime.js + @gjsify/gtk-runtime-darwin-arm64.
126
+ // none is present (the addon then uses the host's GTK as before). Scoped to
127
+ // darwin + win32 — see gtk-runtime.js + @gjsify/gtk-runtime-{darwin-arm64,
128
+ // darwin-x64,win32-x64}.
147
129
  try {
148
- activateBundledGtkRuntime(native);
130
+ activateBundledGtkRuntime(native);
149
131
  } catch {
150
- // Never fatal: a missing/partial bundle just leaves the host GTK in charge.
132
+ // Never fatal: a missing/partial bundle just leaves the host GTK in charge.
151
133
  }
152
134
 
153
135
  // ---- cross-runtime microtask checkpoint (Bun/Deno) --------------------------
@@ -172,47 +154,47 @@ try {
172
154
  // own main-loop source whenever the last JS frame exits — the exact semantic
173
155
  // this checkpoint recreates for Bun/Deno.
174
156
  if (!isNodeRuntime && RUNTIME !== 'gjs') {
175
- try {
176
- let drain = null;
177
- if (RUNTIME === 'bun') {
178
- // JSC: drains the VM's microtask queue; callable mid-stack by design
179
- // (Bun's own processTicksAndRejections calls it from JS).
180
- ({ drainMicrotasks: drain } = require('bun:jsc'));
181
- } else if (typeof globalThis.Deno?.internal === 'symbol') {
182
- // V8: the checkpoint needs BOTH queues. core.runMicrotasks is only
183
- // Isolate::PerformMicrotaskCheckpoint; Deno's node-compat
184
- // process.nextTick queue is a SEPARATE deno_core queue
185
- // (ext:deno_node/_next_tick.ts → core.queueNextTick) that only the
186
- // runtime's own event loop drains — and that loop is paused for the
187
- // whole lifetime of a blocking run(). node:stream delivers stream
188
- // 'end' via process.nextTick (endReadableNT), so with a
189
- // microtask-only drain any body consumption over a Readable
190
- // (@gjsify/fetch consumeBody → XHR arrayBuffer/text) got its chunks
191
- // (microtask-delivered) but never the end: every asset load hung at
192
- // readyState 3 forever on Deno while the SAME bundle settled on Bun,
193
- // whose nextTick rides JSC's microtask queue (bun:jsc
194
- // drainMicrotasks covers it). core.runNextTicks mirrors node's
195
- // task_queues.js runNextTicks: microtask checkpoint when no ticks
196
- // are scheduled, full processTicksAndRejections (ticks + microtasks
197
- // interleaved) when there are — one call, both queues. Regression:
198
- // test/blocking-run-checkpoint.test.mjs.
199
- const core = globalThis.Deno[globalThis.Deno.internal]?.core;
200
- if (typeof core?.runNextTicks === 'function') {
201
- drain = () => core.runNextTicks();
202
- } else if (typeof core?.runMicrotasks === 'function') {
203
- // Older deno_core without runNextTicks: microtask-only drain
204
- // (pre-fix behaviour — promise continuations settle, nextTick
205
- // consumers like node:stream 'end' still starve).
206
- drain = () => core.runMicrotasks();
207
- }
157
+ try {
158
+ let drain = null;
159
+ if (RUNTIME === 'bun') {
160
+ // JSC: drains the VM's microtask queue; callable mid-stack by design
161
+ // (Bun's own processTicksAndRejections calls it from JS).
162
+ ({ drainMicrotasks: drain } = require('bun:jsc'));
163
+ } else if (typeof globalThis.Deno?.internal === 'symbol') {
164
+ // V8: the checkpoint needs BOTH queues. core.runMicrotasks is only
165
+ // Isolate::PerformMicrotaskCheckpoint; Deno's node-compat
166
+ // process.nextTick queue is a SEPARATE deno_core queue
167
+ // (ext:deno_node/_next_tick.ts → core.queueNextTick) that only the
168
+ // runtime's own event loop drains — and that loop is paused for the
169
+ // whole lifetime of a blocking run(). node:stream delivers stream
170
+ // 'end' via process.nextTick (endReadableNT), so with a
171
+ // microtask-only drain any body consumption over a Readable
172
+ // (@gjsify/fetch consumeBody → XHR arrayBuffer/text) got its chunks
173
+ // (microtask-delivered) but never the end: every asset load hung at
174
+ // readyState 3 forever on Deno while the SAME bundle settled on Bun,
175
+ // whose nextTick rides JSC's microtask queue (bun:jsc
176
+ // drainMicrotasks covers it). core.runNextTicks mirrors node's
177
+ // task_queues.js runNextTicks: microtask checkpoint when no ticks
178
+ // are scheduled, full processTicksAndRejections (ticks + microtasks
179
+ // interleaved) when there are — one call, both queues. Regression:
180
+ // test/blocking-run-checkpoint.test.mjs.
181
+ const core = globalThis.Deno[globalThis.Deno.internal]?.core;
182
+ if (typeof core?.runNextTicks === 'function') {
183
+ drain = () => core.runNextTicks();
184
+ } else if (typeof core?.runMicrotasks === 'function') {
185
+ // Older deno_core without runNextTicks: microtask-only drain
186
+ // (pre-fix behaviour — promise continuations settle, nextTick
187
+ // consumers like node:stream 'end' still starve).
188
+ drain = () => core.runMicrotasks();
189
+ }
190
+ }
191
+ if (typeof drain === 'function') native.setMicrotaskDrain(drain);
192
+ } catch {
193
+ // Unregistered ⇒ pre-fix behaviour on this runtime build: promise
194
+ // continuations drain only when the runtime's own loop runs
195
+ // (startMainContextPump); async DBus replies inside a BLOCKING run stay
196
+ // unavailable. Never fatal — the sync surface is unaffected.
208
197
  }
209
- if (typeof drain === 'function') native.setMicrotaskDrain(drain);
210
- } catch {
211
- // Unregistered ⇒ pre-fix behaviour on this runtime build: promise
212
- // continuations drain only when the runtime's own loop runs
213
- // (startMainContextPump); async DBus replies inside a BLOCKING run stay
214
- // unavailable. Never fatal — the sync surface is unaffected.
215
- }
216
198
  }
217
199
 
218
200
  /**