npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gjsify/node-gi

v0.13.0

Published

GObject-Introspection runtime for Node.js — load gi:// namespaces (GLib/GObject/Gio/…) with GJS-compatible semantics. Vendored + rewritten from node-gtk under MIT.

Readme

@gjsify/node-gi

GObject-Introspection runtime for Node.js — the native engine that lets unchanged GJS / GObject-Introspection code run under Node.js, the inverse of gjsify's Node/Web/DOM → GJS direction (see the gjsify AGENTS.md ### Axis 5 active track).

It loads gi:// namespaces (GLib, GObject, Gio, …) via libgirepository and exposes them with GJS-compatible semantics, so the same source builds and runs on both GJS and Node via gjsify build --app {gjs,node}.

Status: milestone 1 (headless core) — in progress. The native engine over the modern girepository-2.0 API now does: resolve the default repository, require a namespace, enumerate its infos; call namespace-level functions and instance methods (own + implemented-interface methods, up the parent chain) with value marshalling (numbers, booleans, strings, GObjects, enums/flags); construct GObjects and read/write properties (GValue round-trip); connect / emit / disconnect signals (incl. detailed names like notify::prop); and register GObject subclasses (subtype + construct-by-type, inheriting the parent's properties/methods, plus custom properties + signals declared on the subclass). Ownership rides N-API finalizers (no V8-GC reentrancy). On top of the engine, an L1 GJS-compatibility layer (@gjsify/node-gi/gi, requireGi) surfaces a GJS-shaped namespace: new Gio.SimpleAction({ name }), action.name property access, action.get_name() methods, .connect()/.emit()/.disconnect(), and enums / flags / constants (Gio.BusType.SESSION, GLib.PRIORITY_DEFAULT); constructor/static methods (Gio.File.new_for_path(...)); and both snake_case and camelCase accessors. A libuv↔GLib mainloop bridge (startMainLoop, auto-attached by requireGi) nests Node's libuv loop inside the GLib loop, so a blocking GLib.MainLoop.run() keeps Node's timers/I/O alive — including the boxed/struct slice that needs (GLib.MainLoop.new(...) → a boxed handle → .run()/.quit()). JS functions marshal as GI callbacks via an ffi closure (GLib.timeout_add/idle_add fire from the loop, the boolean return drives source continuation; the hidden user_data/destroy slots are auto-filled). register GObject subclasses (subtype + construct-by-type, inheriting the parent's properties/methods, plus custom properties + signals, plus vfunc overrides — a JS function overriding a parent GObject vfunc, hooked into the new type's class vtable). The gjsify --app node bundler integration already rewrites gi:// onto the L1 layer. vfunc chain-up to the parent implementation (with the toggle-ref GC bridge) and general struct field access land in subsequent drops — for now a vfunc override fully replaces the inherited implementation.

Provenance

Derived from node-gtk (romgrk and contributors, MIT) — vendored and rewritten under MIT (see LICENSE). The native binding is retargeted to girepository-2.0 (the GIRepository merged into GLib ≥ 2.80); the standalone libgirepository-1.0 node-gtk linked is no longer shipped on modern systems. GJS's own gi/repo.cpp is the reference for the girepository-2.0 API surface. node-gtk's own examples and tests are not vendored as-is — gjsify ships its own dual (GJS + Node) example/test infra.

Requirements

  • Node.js ≥ 20
  • A C++ toolchain (g++/clang, make), node-gyp
  • GLib ≥ 2.80 development headers exposing girepository-2.0 (Fedora: glib2-devel gobject-introspection-devel gcc-c++; Debian/Ubuntu: libglib2.0-dev libgirepository-2.0-dev g++)
  • At runtime, the target libraries' typelibs must be installed (same as gi:// under GJS).

Build & test

npm install          # builds the native addon via node-gyp (install script)
npm test             # node --test (smoke tests)
# or rebuild explicitly:
npm run rebuild

Usage (milestone 1)

import {
  requireNamespace, listInfoNames, callFunction,
  newObject, getProperty, setProperty, callMethod,
  connectSignal, emitSignal,
} from '@gjsify/node-gi';

requireNamespace('GLib', '2.0');
console.log(listInfoNames('GLib').includes('MainLoop')); // true
console.log(callFunction('GLib', 'get_host_name'));      // namespace function

requireNamespace('Gio', '2.0');
const action = newObject('Gio', 'SimpleAction', { name: 'greet', enabled: true });
console.log(getProperty(action, 'name'));     // 'greet'  (GValue round-trip)
console.log(callMethod(action, 'get_name'));  // 'greet'  (interface method)
callMethod(action, 'set_enabled', [false]);   // method with an IN argument

const c = newObject('Gio', 'Cancellable', {});
connectSignal(c, 'cancelled', () => console.log('cancelled'));
emitSignal(c, 'cancelled');

Register a GObject subclass and construct it (inherited properties + methods):

import { registerClass, constructType, callMethod } from '@gjsify/node-gi';

const MyAction = registerClass('MyAction', 'Gio', 'SimpleAction');
const a = constructType(MyAction, { name: 'greet', enabled: true });
console.log(callMethod(a, 'get_name')); // 'greet'  (inherited GAction method)

Declare custom properties and signals on the subclass:

import { registerClass, constructType, getProperty, setProperty,
         connectSignal, emitSignal } from '@gjsify/node-gi';

const Counter = registerClass('Counter', 'GObject', 'Object', {
  properties: [{ name: 'count', type: 'int', default: 0, minimum: 0, maximum: 100 }],
  signals: [{ name: 'changed', paramTypes: ['int'] }],
});

const c = constructType(Counter, { count: 1 });
connectSignal(c, 'notify::count', (pspec) => console.log('changed:', pspec.name));
connectSignal(c, 'changed', (n) => console.log('count is now', n));
setProperty(c, 'count', 5);          // fires notify::count
emitSignal(c, 'changed', [5]);
console.log(getProperty(c, 'count')); // 5

Override a parent GObject vfunc with a JS function (the override runs as a method on the instance — this is the GObject handle). Chain-up to the parent vfunc lands in a later drop, so an override fully replaces the inherited implementation:

import { registerClass, constructType, getProperty } from '@gjsify/node-gi';

const Greeter = registerClass('Greeter', 'Gio', 'SimpleAction', {
  vfuncs: {
    // GObject's `constructed` vfunc — runs once, after construct properties are
    // set. `name` is a CONSTRUCT_ONLY property already available on `this`.
    constructed() {
      console.log('constructed:', getProperty(this, 'name'));
    },
  },
});

constructType(Greeter, { name: 'greet' }); // logs "constructed: greet"

L1 — GJS-shaped surface (@gjsify/node-gi/gi)

The ergonomic layer the gjsify --app node build rewrites gi:// imports onto. This is the same code you would write under GJS:

import { requireGi } from '@gjsify/node-gi/gi';

const GLib = requireGi('GLib', '2.0');
console.log(GLib.get_host_name());

const Gio = requireGi('Gio', '2.0');
const action = new Gio.SimpleAction({ name: 'greet', enabled: true });
console.log(action.name);          // 'greet'   (property accessor)
console.log(action.get_name());    // 'greet'   (method)
action.enabled = false;            // property set → set_property

const c = new Gio.Cancellable();
c.connect('cancelled', () => console.log('cancelled'));
c.cancel();                        // fires the signal

// enums, flags and constants (GJS-style UPPER_CASE members)
console.log(GLib.PRIORITY_DEFAULT);        // 0
console.log(Gio.BusType.SESSION);          // 2
console.log(Gio.ApplicationFlags.HANDLES_OPEN);  // 4

// constructor/static methods + camelCase aliases
const file = Gio.File.new_for_path('/usr/bin/gjs');
console.log(file.get_path());      // '/usr/bin/gjs'
console.log(file.getBasename());   // 'gjs'  (camelCase alias)

// mainloop: a blocking GLib loop, with Node's libuv kept alive underneath
const loop = GLib.MainLoop.new(null, false);
setTimeout(() => loop.quit(), 100); // a libuv timer that fires during run()
loop.run();                         // blocks like under GJS; returns on quit()

// GI callbacks: a JS function passed where a GI callback is expected. The
// GLib source fires from the loop; returning false (G_SOURCE_REMOVE) stops it.
const ticker = GLib.MainLoop.new(null, false);
let n = 0;
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => {
  if (++n >= 3) { ticker.quit(); return false; }
  return true;
});
ticker.run();

The mainloop bridge (startMainLoop) is auto-attached the first time requireGi loads a namespace, so GLib.MainLoop.run() / Gio.Application.run() block as they do under GJS while Node's timers, I/O and signal handlers keep running.

GJS ambient globals (@gjsify/node-gi/globals)

GJS source relies on globals that exist implicitly under gjsprint, printerr, log, logError, ARGV, and the legacy imports object. Importing @gjsify/node-gi/globals (a side effect) installs Node-backed equivalents that route through the same backend:

import '@gjsify/node-gi/globals';

print('hello', 1, true);                 // → stdout, GJS String()-join
const GLib = imports.gi.GLib;            // legacy imports.gi (honours .versions)
imports.gi.versions.Gtk = '4.0';
console.log(imports.gettext.gettext('x')); // no-translation passthrough

A follow-up --app node build step will inject this automatically for any bundle that references those globals (so const Gtk = imports.gi.Gtk / print(...) GJS source runs unmodified on Node); today it is an explicit import.

The remaining GJS-compatible surface (import GLib from 'gi://GLib?version=2.0', const GLib = imports.gi.GLib, the core overrides, _promisify, the legacy imports.* modules) is layered on top of this engine in the gjsify bundler integration and subsequent drops.