Skip to main content

Resource management

Every ntk object that owns a server-side X resource supports explicit resource management (using declarations, available since Node 24; the Symbol.dispose methods themselves work on Node 20+ when called directly):

objectexplicitusing support
App (connection)await app.close()Symbol.asyncDispose (flush + close), Symbol.dispose (terminate)
Windowwnd.destroy()Symbol.dispose
Pixmappixmap.destroy()Symbol.dispose
Picturepicture.destroy()Symbol.dispose
GlyphSetglyphset.destroy()Symbol.dispose
import { createClient } from 'ntk';

{
await using app = await createClient();
using pixmap = app.createPixmap({ width: 256, height: 256, depth: 24 });
const ctx = pixmap.getContext('2d');
// ... draw, read pixels ...
} // pixmap freed, connection flushed and closed — process can exit

GC fallback

If you never destroy an object explicitly, the server resource is freed when the JS wrapper is garbage collected, via FinalizationRegistry (this replaced the old native weak-napi dependency). This is a safety net, not a strategy: GC timing is unpredictable and the X server keeps resources alive in the meantime. For long-running apps, destroy what you create — or let using do it.

Objects wrapping ids ntk did not create (new Window(app, { id }), new Pixmap(app, { id })) are not owned and are never freed by ntk.

Cleanup — explicit or GC-driven — becomes a silent no-op once the connection is closing or closed: the X server frees all of a client's resources on disconnect, so late finalizers after app.close() have nothing left to do.

The connection keeps the process alive

An open X connection holds the node event loop open. Call await app.close() (or use await using) when your program is done, or the process will not exit.