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

@yuneta/gobj-js

v7.16.5

Published

GObject + FSM runtime for JavaScript — event-driven components with typed attributes, pub/sub, and WebSocket inter-event client

Readme

gobj-js

JavaScript/ES6 implementation of the Yuneta GObject runtime (v7).

This package gives you:

  • GObjects — lightweight, event-driven components organized in a parent-child tree.
  • Finite State Machines — every component has an FSM that maps (state, event) pairs to action functions, enforcing clear lifecycle and communication rules.
  • Typed attributes — schema-declared properties (SDATA) with access flags, defaults, and optional persistence to localStorage.
  • Pub/Sub event system — components communicate exclusively through JSON events: direct sends, subscriptions, and publications. No direct method calls between components.
  • Inter-event client (C_IEVENT_CLI) — a built-in WebSocket proxy that lets browser components talk to C-based Yuneta backend services as if they were local GObjects.

Yuneta is a function-oriented framework: components (GClasses) are defined by wiring together plain functions and data tables — not class hierarchies — making the architecture portable across languages. The reference implementation is in C (~12 000 LOC); this package mirrors the same API and patterns for browser and Node.js environments.

Published as @yuneta/gobj-js.

License

Licensed under the MIT License.


Table of Contents


Function-Oriented & Language-Portable

Yuneta is designed around a function-oriented paradigm. Every component (GClass) is defined by wiring together plain functions and data tables:

  • Action functions handle events: function ac_connect(gobj, event, kw, src)
  • Lifecycle methods manage creation and teardown: mt_create, mt_start, mt_stop, mt_destroy
  • FSM tables map (state, event) pairs to action functions and next-state transitions
  • Attribute schemas (SDATA) declare typed, flagged fields as data — not getter/setter methods

There is no class inheritance, no method overriding, and no language-specific constructs at the core. A GClass is a bag of functions plus a data description, registered at startup.

Why this matters

This design makes Yuneta portable to any programming language that supports functions, arrays, and structured data — which is virtually all of them. The reference implementation is in C (~12,000 LOC in gobj.c), and this JavaScript package mirrors the same API and patterns:

| Concept | C | JavaScript | |---------|---|------------| | Create an instance | gobj_create(name, gclass, kw, parent) | gobj_create(name, gclass_name, attrs, parent) | | Send an event | gobj_send_event(gobj, event, kw, src) | gobj_send_event(gobj, event, kw, src) | | Subscribe | gobj_subscribe_event(pub, event, kw, sub) | gobj_subscribe_event(pub, event, kw, sub) | | Attribute schema | SDATA(DTP_STRING, "url", SDF_RD, "", "desc") | SDATA(DTP_STRING, "url", SDF_RD, "", "desc") | | FSM action row | {EV_CONNECT, ac_connect, "ST_CONNECTED"} | ["EV_CONNECT", ac_connect, "ST_CONNECTED"] |

The same GClass structure translates directly between languages. Different implementations can also interoperate over the network via the inter-event protocol: the built-in C_IEVENT_CLI GClass proxies a remote Yuneta service over WebSocket, so a JavaScript frontend can communicate with a C backend as if it were a local GObject.


Architecture Overview

┌──────────────────────────────────────────────────────────┐
│                         Yuno                             │
│   (application root GObject)                             │
│                                                          │
│   ┌──────────────┐   ┌──────────────┐                    │
│   │   Service A  │   │   Service B  │                    │
│   │  (GObject)   │   │  (GObject)   │                    │
│   │              │   │              │                    │
│   │  ┌────────┐  │   │  ┌────────┐  │                    │
│   │  │Child 1 │  │   │  │Child 2 │  │                    │
│   │  └────────┘  │   │  └────────┘  │                    │
│   └──────────────┘   └──────────────┘                    │
└──────────────────────────────────────────────────────────┘
         │ events (JSON kw)  ↕  pub/sub

Every component is a GObject — an instance of a GClass. GClasses define:

  • Typed attributes (schema via SDATA)
  • A Finite State Machine (states + event-action table)
  • Lifecycle methods (mt_create, mt_start, mt_stop, mt_destroy, …)

GObjects communicate exclusively via events carrying JSON key-value payloads (kw). There is no direct method calling between components — everything goes through the FSM event dispatcher.


Installation

# From npm (published package)
npm install @yuneta/gobj-js

# From source (local)
npm install /path/to/kernel/js/gobj-js

Import in your project:

import { gobj_start_up, gobj_create_yuno, register_c_yuno } from "@yuneta/gobj-js";

Build & Develop

Requires Node.js LTS (v22+):

nvm install --lts
npm install -g vite
cd gobj-js/
npm install        # install dependencies

vite               # dev server
vite build         # build all output formats to dist/
npm test           # run tests (vitest)
npm run test:coverage
npx vitest --watch # watch mode

To update dependencies:

npm install -g npm-check-updates
ncu -u && npm install

Output formats

vite build produces files in dist/:

| File | Format | Use | |------|--------|-----| | gobj-js.es.js | ES modules | bundlers, modern browsers | | gobj-js.cjs.js | CommonJS | Node.js | | gobj-js.umd.js | UMD | legacy bundlers | | gobj-js.iife.js | IIFE | <script> tag | | *.min.js | minified variants | production |


Publish

To publish a new version of @yuneta/gobj-js to npmjs.com:

# 1. Configure your npm token (only once)
echo "//registry.npmjs.org/:_authToken=<your-token>" > ~/.npmrc

# 2. Update the version in package.json
npm version patch   # or minor / major

# 3. Publish (build runs automatically via prepublishOnly)
npm publish --access public

To create an npm token, go to npmjs.com → Account → Access Tokens.


Quick Start

import {
    gobj_start_up,
    gobj_create_yuno,
    gobj_create_service,
    gobj_start,
    gobj_play,
    gobj_yuno,
    db_load_persistent_attrs,
    db_save_persistent_attrs,
    db_remove_persistent_attrs,
    db_list_persistent_attrs,
    register_c_yuno,
    register_c_timer,
    register_c_ievent_cli,
} from "@yuneta/gobj-js";

// 1. Register GClasses
register_c_yuno();
register_c_timer();
register_c_ievent_cli();

// 2. Initialize framework
gobj_start_up(
    null,                           // jn_global_settings (JSON)
    db_load_persistent_attrs,       // load  persistent attrs
    db_save_persistent_attrs,       // save  persistent attrs
    db_remove_persistent_attrs,     // remove persistent attrs
    db_list_persistent_attrs,       // list  persistent attrs
    null,                           // global command parser fn
    null                            // global stats parser fn
);

// 3. Create the Yuno (application root)
let yuno = gobj_create_yuno("yuno", "C_YUNO", {
    yuno_name: "my_app",
    yuno_role: "my_role",
    yuno_version: "1.0.0",
});

// 4. Create services under the yuno
gobj_create_service("main", "C_MY_SERVICE", {}, gobj_yuno());

// 5. Start everything
gobj_start(yuno);
gobj_play(yuno);   // triggers mt_play on the default service

Core Concepts

GClass & GObject

A GClass is a class definition — registered once at startup. A GObject is an instance of a GClass.

// Register a GClass
gclass_create(name, event_types, states, gmt, lmt,
              attrs_table, private_data, authz_table,
              command_table, s_user_trace_level, gclass_flag);

// Create an instance
let gobj = gobj_create(name, gclass_name, attributes, parent);

Finite State Machines

Each GClass defines:

  • A list of states (strings, e.g. "ST_IDLE", "ST_CONNECTED")
  • Per-state event-action tables: [event_name, action_fn, next_state]

When an event is sent to a GObject, the FSM looks up the current state → finds the matching event → calls the action function. next_state (or null to stay) controls state transitions.

const st_idle = [
    ["EV_CONNECT",    ac_connect,    "ST_CONNECTED"],
    ["EV_TIMEOUT",    ac_timeout,    null],           // stay in ST_IDLE
];
const st_connected = [
    ["EV_DISCONNECT", ac_disconnect, "ST_IDLE"],
    ["EV_MESSAGE",    ac_message,    null],
];

const states = [
    ["ST_IDLE",      st_idle],
    ["ST_CONNECTED", st_connected],
];

Attributes (SData)

Attributes are declared in a schema table using SDATA() macros. Each attribute has a type, name, flags, default value, and description.

import { SDATA, SDATA_END, data_type_t, sdata_flag_t } from "@yuneta/gobj-js";

const attrs_table = [
    SDATA(data_type_t.DTP_STRING,  "url",        sdata_flag_t.SDF_RD,      "", "Server URL"),
    SDATA(data_type_t.DTP_INTEGER, "timeout",    sdata_flag_t.SDF_RD,       0, "Timeout ms"),
    SDATA(data_type_t.DTP_BOOLEAN, "connected",  sdata_flag_t.SDF_RD,   false, "Connection state"),
    SDATA(data_type_t.DTP_STRING,  "saved_key",  sdata_flag_t.SDF_PERSIST,  "", "Persisted value"),
    SDATA_END()
];

Data types (data_type_t): DTP_STRING, DTP_BOOLEAN, DTP_INTEGER, DTP_REAL, DTP_LIST, DTP_DICT, DTP_JSON, DTP_POINTER

Flags (sdata_flag_t): SDF_RD, SDF_WR, SDF_PERSIST, SDF_STATS, SDF_AUTHZ_R, SDF_AUTHZ_W, SDF_REQUIRED, SDF_VOLATIL

Events & Pub-Sub

GObjects communicate via events with JSON payloads:

// Send directly to a specific GObject
gobj_send_event(target_gobj, "EV_CONNECT", { url: "ws://..." }, src_gobj);

// Publish to all subscribers
gobj_publish_event(gobj, "EV_DATA_READY", { data: [] });

// Subscribe to events from a source
gobj_subscribe_event(source_gobj, "EV_DATA_READY", {}, subscriber_gobj);

// Unsubscribe
gobj_unsubscribe_event(source_gobj, "EV_DATA_READY", {}, subscriber_gobj);

GObject Tree (Yuno)

GObjects form a parent-child tree. The root is the Yuno. Services live directly under the Yuno. Each GObject has exactly one parent (except the Yuno itself).

Yuno
 ├── Service "auth"     (C_IEVENT_CLI)
 ├── Service "main"     (C_MY_SERVICE)
 │    └── Child "sub"   (C_SOME_GCLASS)
 └── Service "timer"    (C_TIMER)

Public API

Framework Bootstrap

gobj_start_up(
    jn_global_settings,         // JSON object or null
    load_persistent_attrs_fn,   // fn(gobj, keys)
    save_persistent_attrs_fn,   // fn(gobj, keys)
    remove_persistent_attrs_fn, // fn(gobj, keys)
    list_persistent_attrs_fn,   // fn(gobj, keys)
    global_command_parser_fn,   // fn or null
    global_stats_parser_fn      // fn or null
)

GClass Registration

gclass_create(name, event_types, states, gmt, lmt, attrs_table,
              private_data, authz_table, command_table,
              s_user_trace_level, gclass_flag)

gclass_find_by_name(name)        // → GClass or null
gclass_check_fsm(gclass)         // validate FSM (returns error count)
gclass_add_event_type(gclass, event_name, flag)
gclass_add_state(gclass, state_name)
gclass_add_ev_action(gclass, state_name, event_name, action_fn, next_state)

GObject Lifecycle

// Creation
gobj_create(name, gclass_name, attrs, parent)       // generic child
gobj_create_yuno(name, gclass_name, attrs)           // application root
gobj_create_service(name, gclass_name, attrs, yuno)  // named service
gobj_create_default_service(name, gclass_name, attrs, yuno)
gobj_create_volatil(name, gclass_name, attrs, parent)
gobj_create_pure_child(name, gclass_name, attrs, parent)

// Start / Stop
gobj_start(gobj)           // calls mt_start
gobj_stop(gobj)            // calls mt_stop
gobj_start_children(gobj)
gobj_stop_children(gobj)
gobj_start_tree(gobj)
gobj_stop_tree(gobj)

// Play / Pause (applied to default service)
gobj_play(gobj)
gobj_pause(gobj)

// Destroy
gobj_destroy(gobj)

// Status
gobj_is_running(gobj)      // → boolean
gobj_is_playing(gobj)      // → boolean
gobj_is_destroying(gobj)   // → boolean
gobj_is_volatil(gobj)      // → boolean
gobj_is_pure_child(gobj)   // → boolean

State Machine

gobj_current_state(gobj)                         // → state name string
gobj_change_state(gobj, new_state)               // trigger FSM transition
gobj_has_event(gobj, event, event_flag)          // → boolean; pass 0 to ignore flag
gobj_has_output_event(gobj, event, event_flag)   // same; checks EVF_OUTPUT_EVENT

Attribute Access

// Generic read/write
// `path` accepts back-tick navigation to walk into child gobjs: "child`subattr"
gobj_read_attr (gobj, name, src)
gobj_write_attr(gobj, path, value, src)
gobj_read_attrs (gobj, include_flag, src)                // → JSON object of matching attrs
gobj_write_attrs(gobj, kw, include_flag, src)            // include_flag filters which are written
gobj_has_attr(gobj, name)                                // → boolean

// Typed reads — name only (no src)
gobj_read_bool_attr   (gobj, name)
gobj_read_integer_attr(gobj, name)
gobj_read_str_attr    (gobj, name)
gobj_read_pointer_attr(gobj, name)

// Typed writes — (name, value), no src
gobj_write_bool_attr   (gobj, name, value)
gobj_write_integer_attr(gobj, name, value)
gobj_write_str_attr    (gobj, name, value)

Event System

// Sending
gobj_send_event(gobj, event, kw, src)         // direct send
gobj_publish_event(gobj, event, kw)           // publish to subscribers
gobj_post_event(gobj, event, kw, src)         // post (deferred)

// Subscriptions
gobj_subscribe_event  (publisher, event, kw, subscriber)
gobj_unsubscribe_event(publisher, event, kw, subscriber)
gobj_unsubscribe_list (publisher, dl_subs, force)     // force=true also removes hard subs
gobj_find_subscriptions(publisher, event, kw, subscriber)
gobj_list_subscriptions(gobj)
gobj_find_subscribings (subscriber, event, kw, publisher)

// Commands & Stats
gobj_command(gobj, command, kw, src)          // → response JSON
gobj_stats(gobj, stats, kw, src)              // → response JSON
build_command_response(gobj, result, comment, schema, data)
build_stats_response(gobj, result, comment, schema, data)

Hierarchy & Navigation

gobj_parent(gobj)
gobj_yuno()                        // → the Yuno root
gobj_default_service()             // → default service under yuno

gobj_name(gobj)
gobj_short_name(gobj)
gobj_full_name(gobj)
gobj_gclass_name(gobj)
gobj_yuno_name(gobj)
gobj_yuno_role(gobj)
gobj_yuno_id(gobj)

gobj_find_child(gobj, kw_filter)
gobj_find_service(name, verbose)
gobj_find_gobj(gobj, path)
gobj_search_path(gobj, path)

gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_data2)
gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, user_data2)

Persistence

Backed by localStorage (browser) via dbsimple.js:

// Low-level (dbsimple)
db_load_persistent_attrs(gobj, keys)
db_save_persistent_attrs(gobj, keys)
db_remove_persistent_attrs(gobj, keys)
db_list_persistent_attrs(gobj, keys)

// Via gobj (delegates to registered persistence fns)
gobj_load_persistent_attrs(gobj, keys)
gobj_save_persistent_attrs(gobj, keys)
gobj_remove_persistent_attrs(gobj, keys)
gobj_list_persistent_attrs(gobj, keys)

Attributes marked SDF_PERSIST are automatically saved/loaded.

Always name what you save: gobj_save_persistent_attrs(gobj, "attr_name") (a string, or a list/dict of names). The bare call saves every SDF_PERSIST attr, which is wasteful and can clobber attrs the caller never touched.

Check the result. db_save_persistent_attrs() / gobj_save_persistent_attrs() return 0 on success, -1 on failure, and a failure is logged through log_error. localStorage can refuse a write (quota exceeded, private mode, storage blocked): treating that as saved leaves the in-memory attr and the UI showing a value the store rejected, which the next reload silently loses.

Helpers & Utilities

// JSON / Object operations
json_deep_copy(obj)
json_is_identical(a, b)
json_object_update(dst, src)            // merge src into dst
json_object_update_existing(dst, src)   // only existing keys
json_object_update_missing(dst, src)    // only missing keys
json_object_get(obj, key)
json_object_set(obj, key, value)
json_object_del(obj, key)
json_array_append(arr, value)
json_array_remove(arr, index)
json_array_extend(dst, src)
json_object_size(obj)
json_array_size(arr)

// Type checking
is_object(v), is_array(v), is_string(v), is_number(v),
is_boolean(v), is_null(v), is_date(v), is_function(v), is_gobj(v)

// Keyword (kw) operations
//
// Most kw_* helpers take `gobj` as first argument (for error logging)
// and a dot/back-tick path instead of a single key. Exceptions that do
// NOT take gobj: kw_has_key, kw_pop, kw_match_simple.

kw_has_key(kw, key)                                      // → boolean
kw_pop(kw1, kw2)                                         // delete from kw1 the keys listed in kw2
kw_delete(gobj, kw, path)
kw_find_path(gobj, kw, path, verbose)                    // back-tick path: "a`b`c"

kw_get_bool(gobj, kw, path, default_value, flag)
kw_get_int (gobj, kw, path, default_value, flag)
kw_get_real(gobj, kw, path, default_value, flag)
kw_get_str (gobj, kw, path, default_value, flag)
kw_get_dict(gobj, kw, path, default_value, flag)
kw_get_list(gobj, kw, path, default_value, flag)
kw_get_dict_value(gobj, kw, path, default_value, flag)

kw_set_dict_value(gobj, kw, path, value)
kw_set_subdict_value(gobj, kw, path, key, value)

kw_match_simple(kw, filter)                              // → boolean
kw_select (gobj, kw, jn_filter, match_fn)                // filter list
kw_collect(gobj, kw, jn_filter, match_fn)                // extract subset
kw_clone_by_keys    (gobj, kw, keys, verbose)
kw_clone_by_not_keys(gobj, kw, keys, verbose)

// Flags: KW_REQUIRED, KW_CREATE, KW_EXTRACT, KW_RECURSIVE, KW_WILD_NUMBER

// Local storage helpers (browser localStorage, no "store" parameter)
kw_get_local_storage_value(key, default_value, create)   // create=false
kw_set_local_storage_value(key, value)
kw_remove_local_storage_value(key)

// Misc utilities
current_timestamp()          // ISO string
get_now()                    // Date object
node_uuid()                  // generate UUID
parseBoolean(v)              // coerce to boolean
empty_string(s)              // true if null/undefined/""
str_in_list(list, str, ci)   // case-insensitive optional
index_in_list(list, value)
delete_from_list(list, value)
jwtDecode(token)             // → header/payload/signature
jwt2json(token)              // → payload JSON
debounce(fn, delay)
timeTracker()

// Inter-event message stack
msg_iev_push_stack  (gobj, kw, stack, jn_data)    // push jn_data onto named stack
msg_iev_get_stack   (gobj, kw, stack, verbose)    // peek top of named stack
msg_iev_set_msg_type(gobj, kw, msg_type)          // "" to delete
msg_iev_get_msg_type(gobj, kw)
msg_iev_write_key(kw, key, value)                 // no gobj
msg_iev_read_key (kw, key)                        // no gobj

Logging

All log functions take a single format argument and use printf-style substitution (%s, %d, %i, %f, %o/%O). There is no gobj or error-code argument (that's the C API — the JS runtime is simpler).

log_error(format, ...args)       // red, prefixed "ERROR"
log_warning(format, ...args)     // yellow, prefixed "WARNING"
log_info(format, ...args)        // cyan, prefixed "INFO"
log_debug(format, ...args)       // silver, prefixed "DEBUG"
trace_msg(format, ...args)       // cyan, prefixed "MSG"
trace_json(json, msg)            // dir-dump a JSON object

// Redirect error/warning output to a single remote handler
set_remote_log_functions(remote_log_fn)   // fn(message) — single function

// Receive every log record (any level) in your own sink
set_log_callback(fn)                      // fn(level, message)

// Gate the DIRECT browser-console writes of all the functions above
set_console_log_enabled(enabled)          // default: true (unchanged behaviour)

// ...and a per-LINE say over them, on top of that switch (7.13.6)
set_console_log_filter(fn)                // fn(level, msg) -> true to print
set_console_log_filter(null)              // default: everything prints

Three independent output paths. set_console_log_enabled(false) silences only the console.* writes: the log-sink callback (set_log_callback) and the remote log functions (set_remote_log_functions) still fire. That is what lets a GUI route framework output — including the automata/FSM trace, which arrives as debug — into its own monitor window while keeping the browser console clean (gobj-ui's dev-window "Output" selector does exactly this).

Why a filter and not just the switch. The console write happens before the sink is called, so nothing downstream can un-print a line: a consumer that wants one CLASS of lines kept off the console — the dev monitor hiding the machine trace's timer traffic, two EV_TIMEOUT lines a second for ever — can only say so here. The filter decides the console alone: the sink still receives every line, so a monitor keeps a complete record and decides separately what to show. The master switch still wins, and a filter that throws is ignored — a broken one must never be able to silence the log.

Tracing: the machine trace, and the levels around it

The FSM is where things happen, so the trace of the FSM is the execution log. The level model is the C kernel's — same names, same bits — so a level means one thing on both sides and a habit learned on a node transfers here.

gobj_set_global_trace("machine", true)     // everything
gobj_set_gclass_trace("C_MY_VIEW", "machine", true)
gobj_set_gobj_trace(gobj, "ev_kw", true)   // + the kw of each event
gobj_set_gobj_no_trace(noisy, "machine", true)   // veto, by the SOURCE gobj

gobj_global_trace_level()                  // the mask in force
gobj_trace_level(gobj)                     // global | gclass | gobj, combined
gobj_repr_global_trace_levels()            // [{name, bit, description, set}]
gobj_set_deep_trace(1)                     // everything, everywhere
gobj_set_trace_machine_format(1)           // 1 = simpler (DEFAULT), 0 = legacy

Two shapes for the machine trace, the C kernel's two, and the same default (1, since 7.13.7):

1  🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t
0  🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: fi(), from(C_TIMER^t)
   🔀🔀 mach(C_TIMER^t), new st(ST_IDLE), prev st(ST_IDLE)
   <- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0

One line per transition against three. Both name the event, so anything that reads the trace back by event name works across the switch. Every line is prefixed by its nesting depth (two spaces per level of __inside__), so an event sent from inside another one's action is indented under it — keep that indentation when you render the line somewhere else; it is the only thing that says a transition happened during another.

Levels: machine, create_delete, create_delete2, subscriptions, start_stop, ev_kw, authzs, states, gbuffers, timer, fs, liburing, timer_periodic, liburing_timer, commands. The ones that only exist on a node keep their bit rather than being dropped — removing them would shift every bit above and break that alignment.

A gobj's effective level is the union of global, gclass and gobj. timer / timer_periodic light up for their own event only. And the veto is asked of the source, so one noisy sender can be silenced without turning the destination off.

The trace arrives as debug, so set_log_callback() is how you read it — that is how a dev panel shows the machine inside the app, and how doc.yuneta.io/navigation feeds the panel under each of its demos.

The yuno attrs tracing / trace_timer / trace_creation / trace_start_stop predate this and still work (gobj-ui's dev panel writes them); they fold in as one more source of bits.

i18n: refresh_language and the data-i18n-* family

The runtime does not translate anything itself — the app owns the translator (i18next, typically) and passes it in as t. What the runtime provides is the mechanism to re-translate live DOM when the language changes:

refresh_language(element, t)     // element defaults to `document`

It only re-translates a node that carries its key, so passing a string through t() once at build time is not enough — that text is frozen in the mount-time language for the life of the view. Tag the DOM instead:

| Attribute | Re-translates | |---|---| | data-i18n | the element's first text node | | data-i18n-title | the title attribute (hover tooltip) | | data-i18n-aria-label | the aria-label (assistive-tech name) | | data-i18n-placeholder | the placeholder of an input/textarea |

The three attribute variants exist because data-i18n walks text nodes, which cannot reach an attribute. Anything a third-party widget draws is reachable by none of them — re-render it from the language-change handler instead.

A missing key is invisible: i18next answers an unknown key with the key itself, so it renders as lower-case English and never changes language. Do not compose data into a key either (`${key} · ${t(mode)}`) — split it so each translatable half carries its own key.

String Formatting

sprintf(format, ...args)    // printf-style
vsprintf(fmt, argv)         // variadic version

Format specifiers: %s %d %i %f %e %g %o %x %X %b %c %j (JSON) %t (boolean) %T (type) %v (value) %u (unsigned)


Built-in GClasses

C_YUNO

The application root. Always the first GClass registered.

import { register_c_yuno } from "@yuneta/gobj-js";
register_c_yuno();

Key attributes: yuno_name, yuno_role, yuno_id, yuno_version, yuno_release, yuneta_version, required_services, tracing, start_date, node_uuid, __username__

The yuno's required_services is the fallback for links that declare none of their own. A multi-link yuno should set the list on each C_IEVENT_CLI instead — see C_IEVENT_CLI below.

C_TIMER

Manages timeouts and periodic timers.

import { register_c_timer, set_timeout, set_timeout_periodic, clear_timeout } from "@yuneta/gobj-js";
register_c_timer();

// One-shot timeout (ms)
set_timeout(timer_gobj, 5000);

// Periodic timeout
set_timeout_periodic(timer_gobj, 1000);

// Cancel
clear_timeout(timer_gobj);

Events published: EV_TIMEOUT, EV_TIMEOUT_PERIODIC

Attributes: subscriber, periodic, msec

C_IEVENT_CLI

Inter-event client — proxies a remote Yuneta service over WebSocket so it looks like a local GObject. Used to communicate with backend yunos.

import { register_c_ievent_cli } from "@yuneta/gobj-js";
register_c_ievent_cli();

let remote = gobj_create_service("backend", "C_IEVENT_CLI", {
    url: "ws://localhost:1991",
    wanted_yuno_role: "agent",
    wanted_yuno_service: "agent",
    jwt: "...",
}, gobj_yuno());

Key attributes: url, jwt, wanted_yuno_role, wanted_yuno_name, wanted_yuno_service, remote_yuno_role, remote_yuno_name, remote_yuno_service, required_services, timeout_retry, timeout_retry_max

required_services (default []) is the list this link advertises in its identity_card, which the remote's C_AUTHZ uses to authorize commands. It is per link: empty falls back to the yuno's own required_services, so a single-link yuno needs nothing. A multi-link yuno must set it per link — a yuno-wide list can only be the union of every backend's services, so each backend would be handed the service names of all the others, and get a card naming services it does not host.

Reconnect backoff. A dropped link retries after timeout_retry (5s), then doubles up to timeout_retry_max (60s), with ±20% jitter. The jitter is what breaks lockstep, so N links that dropped together do not stampede a backend that is just coming back. The backoff resets when a session is actually reached, and on mt_start — a deliberate reconnect never inherits a previous run's penalty. For the old fixed-interval behaviour set timeout_retry_max equal to timeout_retry.


TreeDB Helpers

Utilities for interacting with the Yuneta TreeDB (schema-driven graph database):

import {
    treedb_hook_data_size,
    treedb_decoder_fkey,
    treedb_encoder_fkey,
    treedb_decoder_hook,
    treedb_get_field_desc,
    template_get_field_desc,
    create_template_record,
} from "@yuneta/gobj-js";

treedb_hook_data_size(value)                // count with caching
treedb_decoder_fkey(col, fkey)              // parse foreign key reference
treedb_encoder_fkey(col, fkey)              // build fkey string "topic^id^hook"
treedb_decoder_hook(col, hook)              // parse hook reference
treedb_get_field_desc(col)                  // build field descriptor from column
template_get_field_desc(key, value)         // build field descriptor from template
create_template_record(template, kw)        // instantiate from template

Writing a Custom GClass

import {
    SDATA, SDATA_END,
    data_type_t, sdata_flag_t,
    gclass_create,
    gobj_subscribe_event, gobj_yuno,
    trace_msg,
} from "@yuneta/gobj-js";

const GCLASS_NAME = "C_MY_CLASS";

// 1. Attribute schema
const attrs_table = [
    SDATA(data_type_t.DTP_STRING,  "url",      sdata_flag_t.SDF_RD, "", "Remote URL"),
    SDATA(data_type_t.DTP_INTEGER, "retries",  sdata_flag_t.SDF_RD,  3, "Max retries"),
    SDATA_END()
];

// 2. Private data (per-instance, copied from this template)
const PRIVATE_DATA = {
    retry_count: 0,
};

// 3. Lifecycle methods
function mt_create(gobj) {
    // instance created, attrs not yet populated
}
function mt_start(gobj) {
    // subscribe to events from other gobjs here
    gobj_subscribe_event(gobj_yuno(), "EV_TIMEOUT_PERIODIC", {}, gobj);
}
function mt_stop(gobj) { }
function mt_destroy(gobj) { }

// 4. Action functions
function ac_timeout(gobj, event, kw, src) {
    trace_msg(`Tick! retry_count=${gobj.priv.retry_count}`);
    gobj.priv.retry_count++;
    return 0;  // kw consumed
}

// 5. FSM
const st_idle = [
    ["EV_TIMEOUT_PERIODIC", ac_timeout, null],
];
const states = [
    ["ST_IDLE", st_idle],
];
const event_types = [
    ["EV_TIMEOUT_PERIODIC", 0],
];

// 6. Methods table
const gmt = { mt_create, mt_start, mt_stop, mt_destroy };

// 7. Register
function register_c_my_class() {
    return gclass_create(
        GCLASS_NAME,
        event_types,
        states,
        gmt,
        0,              // lmt (low-level methods)
        attrs_table,
        PRIVATE_DATA,
        0,              // authz_table
        0,              // command_table
        0,              // s_user_trace_level
        0               // gclass_flag
    );
}

export { register_c_my_class };

Source Layout

kernel/js/gobj-js/
├── README.md                          ← this file
├── package.json
├── vite.config.js
├── dist/                              ← compiled outputs (ES, CJS, UMD, IIFE)
├── tests/
│   └── kw_delete.test.js
└── src/
    ├── index.js                       ← public API entry point (barrel export)
    ├── gobj.js                        ← GObject/FSM runtime      (~4 500 LOC)
    ├── helpers.js                     ← utilities, JSON, logging (~3 300 LOC)
    ├── c_ievent_cli.js                ← remote service proxy     (~1 350 LOC)
    ├── lib_treedb.js                  ← TreeDB helpers           (~  540 LOC)
    ├── c_timer.js                     ← Timer GClass             (~  330 LOC)
    ├── c_yuno.js                      ← Yuno GClass              (~  290 LOC)
    ├── dbsimple.js                    ← localStorage persistence (~  140 LOC)
    ├── sprintf.js                     ← printf-style formatting  (~  210 LOC)
    ├── command_parser.js              ← command parsing
    └── stats_parser.js                ← stats parsing