@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 tolocalStorage. - 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
- Architecture Overview
- Installation
- Build & Develop
- Publish
- Quick Start
- Core Concepts
- Public API
- Built-in GClasses
- TreeDB Helpers
- Writing a Custom GClass
- Source Layout
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/subEvery 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-jsImport 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 vitecd 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 modeTo update dependencies:
npm install -g npm-check-updates
ncu -u && npm installOutput 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 publicTo 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 serviceCore 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) // → booleanState 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_EVENTAttribute 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 gobjLogging
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 printsThree 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 = legacyTwo 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: 0One 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_stoppredate 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 versionFormat 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 templateWriting 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