@yuneta/gobj-js 7.1.4 → 7.2.1
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/README.md +739 -38
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,71 +1,772 @@
|
|
|
1
|
-
#
|
|
1
|
+
# gobj-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JavaScript/ES6 implementation of the [Yuneta](https://yuneta.io) GObject runtime (v7).
|
|
4
4
|
|
|
5
|
-
This
|
|
5
|
+
This package gives you:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **GObjects** — lightweight, event-driven components organized in a parent-child tree.
|
|
8
|
+
- **Finite State Machines** — every component has an FSM that maps (state, event) pairs to action functions, enforcing clear lifecycle and communication rules.
|
|
9
|
+
- **Typed attributes** — schema-declared properties (`SDATA`) with access flags, defaults, and optional persistence to `localStorage`.
|
|
10
|
+
- **Pub/Sub event system** — components communicate exclusively through JSON events: direct sends, subscriptions, and publications. No direct method calls between components.
|
|
11
|
+
- **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.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
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](https://github.com/artgins/yunetas) (~12 000 LOC); this package mirrors the same API and patterns for browser and Node.js environments.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
Published as [`@yuneta/gobj-js`](https://www.npmjs.com/package/@yuneta/gobj-js).
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
v22.14.0
|
|
17
|
+
## License
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
Licensed under the [MIT License](http://www.opensource.org/licenses/mit-license).
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
---
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
## Table of Contents
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
- [Function-Oriented & Language-Portable](#function-oriented--language-portable)
|
|
26
|
+
- [Architecture Overview](#architecture-overview)
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Build & Develop](#build--develop)
|
|
29
|
+
- [Publish](#publish)
|
|
30
|
+
- [Quick Start](#quick-start)
|
|
31
|
+
- [Core Concepts](#core-concepts)
|
|
32
|
+
- [Public API](#public-api)
|
|
33
|
+
- [Built-in GClasses](#built-in-gclasses)
|
|
34
|
+
- [TreeDB Helpers](#treedb-helpers)
|
|
35
|
+
- [Writing a Custom GClass](#writing-a-custom-gclass)
|
|
36
|
+
- [Source Layout](#source-layout)
|
|
23
37
|
|
|
24
|
-
|
|
38
|
+
---
|
|
25
39
|
|
|
26
|
-
|
|
40
|
+
## Function-Oriented & Language-Portable
|
|
27
41
|
|
|
28
|
-
|
|
42
|
+
Yuneta is designed around a **function-oriented paradigm**. Every component (GClass) is defined by wiring together plain functions and data tables:
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
- **Action functions** handle events: `function ac_connect(gobj, event, kw, src)`
|
|
45
|
+
- **Lifecycle methods** manage creation and teardown: `mt_create`, `mt_start`, `mt_stop`, `mt_destroy`
|
|
46
|
+
- **FSM tables** map (state, event) pairs to action functions and next-state transitions
|
|
47
|
+
- **Attribute schemas** (`SDATA`) declare typed, flagged fields as data — not getter/setter methods
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
echo "//registry.npmjs.org/:_authToken=<your-token>" > ~/.npmrc
|
|
49
|
+
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.
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
npm version patch # or minor / major
|
|
51
|
+
### Why this matters
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
npm publish --access public
|
|
53
|
+
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:
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
| Concept | C | JavaScript |
|
|
56
|
+
|---------|---|------------|
|
|
57
|
+
| Create an instance | `gobj_create(name, gclass, kw, parent)` | `gobj_create(name, gclass_name, attrs, parent)` |
|
|
58
|
+
| Send an event | `gobj_send_event(gobj, event, kw, src)` | `gobj_send_event(gobj, event, kw, src)` |
|
|
59
|
+
| Subscribe | `gobj_subscribe_event(pub, event, kw, sub)` | `gobj_subscribe_event(pub, event, kw, sub)` |
|
|
60
|
+
| Attribute schema | `SDATA(DTP_STRING, "url", SDF_RD, "", "desc")` | `SDATA(DTP_STRING, "url", SDF_RD, "", "desc")` |
|
|
61
|
+
| FSM action row | `{EV_CONNECT, ac_connect, "ST_CONNECTED"}` | `["EV_CONNECT", ac_connect, "ST_CONNECTED"]` |
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Architecture Overview
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
┌──────────────────────────────────────────────────────────┐
|
|
71
|
+
│ Yuno │
|
|
72
|
+
│ (application root GObject) │
|
|
73
|
+
│ │
|
|
74
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
75
|
+
│ │ Service A │ │ Service B │ │
|
|
76
|
+
│ │ (GObject) │ │ (GObject) │ │
|
|
77
|
+
│ │ │ │ │ │
|
|
78
|
+
│ │ ┌────────┐ │ │ ┌────────┐ │ │
|
|
79
|
+
│ │ │Child 1 │ │ │ │Child 2 │ │ │
|
|
80
|
+
│ │ └────────┘ │ │ └────────┘ │ │
|
|
81
|
+
│ └──────────────┘ └──────────────┘ │
|
|
82
|
+
└──────────────────────────────────────────────────────────┘
|
|
83
|
+
│ events (JSON kw) ↕ pub/sub
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Every component is a **GObject** — an instance of a **GClass**. GClasses define:
|
|
87
|
+
- Typed **attributes** (schema via `SDATA`)
|
|
88
|
+
- A **Finite State Machine** (states + event-action table)
|
|
89
|
+
- **Lifecycle methods** (`mt_create`, `mt_start`, `mt_stop`, `mt_destroy`, …)
|
|
90
|
+
|
|
91
|
+
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.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# From npm (published package)
|
|
99
|
+
npm install @yuneta/gobj-js
|
|
100
|
+
|
|
101
|
+
# From source (local)
|
|
102
|
+
npm install /path/to/kernel/js/gobj-js
|
|
103
|
+
```
|
|
42
104
|
|
|
43
|
-
|
|
105
|
+
Import in your project:
|
|
44
106
|
|
|
45
|
-
|
|
107
|
+
```javascript
|
|
108
|
+
import { gobj_start_up, gobj_create_yuno, register_c_yuno } from "@yuneta/gobj-js";
|
|
109
|
+
```
|
|
46
110
|
|
|
47
|
-
|
|
111
|
+
---
|
|
48
112
|
|
|
49
|
-
|
|
113
|
+
## Build & Develop
|
|
50
114
|
|
|
51
|
-
|
|
115
|
+
Requires Node.js LTS (v22+):
|
|
52
116
|
|
|
53
|
-
|
|
117
|
+
```bash
|
|
118
|
+
nvm install --lts
|
|
119
|
+
npm install -g vite
|
|
120
|
+
```
|
|
54
121
|
|
|
55
|
-
|
|
122
|
+
```bash
|
|
123
|
+
cd gobj-js/
|
|
124
|
+
npm install # install dependencies
|
|
56
125
|
|
|
57
|
-
|
|
126
|
+
vite # dev server
|
|
127
|
+
vite build # build all output formats to dist/
|
|
128
|
+
npm test # run tests (vitest)
|
|
129
|
+
npm run test:coverage
|
|
130
|
+
npx vitest --watch # watch mode
|
|
131
|
+
```
|
|
58
132
|
|
|
59
|
-
|
|
133
|
+
To update dependencies:
|
|
60
134
|
|
|
61
|
-
|
|
135
|
+
```bash
|
|
136
|
+
npm install -g npm-check-updates
|
|
137
|
+
ncu -u && npm install
|
|
138
|
+
```
|
|
62
139
|
|
|
63
|
-
|
|
140
|
+
### Output formats
|
|
141
|
+
|
|
142
|
+
`vite build` produces files in `dist/`:
|
|
143
|
+
|
|
144
|
+
| File | Format | Use |
|
|
145
|
+
|------|--------|-----|
|
|
146
|
+
| `gobj-js.es.js` | ES modules | bundlers, modern browsers |
|
|
147
|
+
| `gobj-js.cjs.js` | CommonJS | Node.js |
|
|
148
|
+
| `gobj-js.umd.js` | UMD | legacy bundlers |
|
|
149
|
+
| `gobj-js.iife.js` | IIFE | `<script>` tag |
|
|
150
|
+
| `*.min.js` | minified variants | production |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Publish
|
|
155
|
+
|
|
156
|
+
To publish a new version of `@yuneta/gobj-js` to [npmjs.com](https://www.npmjs.com/package/@yuneta/gobj-js):
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# 1. Configure your npm token (only once)
|
|
160
|
+
echo "//registry.npmjs.org/:_authToken=<your-token>" > ~/.npmrc
|
|
161
|
+
|
|
162
|
+
# 2. Update the version in package.json
|
|
163
|
+
npm version patch # or minor / major
|
|
164
|
+
|
|
165
|
+
# 3. Publish (build runs automatically via prepublishOnly)
|
|
166
|
+
npm publish --access public
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
To create an npm token, go to [npmjs.com](https://www.npmjs.com) → Account → Access Tokens.
|
|
64
170
|
|
|
65
|
-
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Quick Start
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
import {
|
|
177
|
+
gobj_start_up,
|
|
178
|
+
gobj_create_yuno,
|
|
179
|
+
gobj_create_service,
|
|
180
|
+
gobj_start,
|
|
181
|
+
gobj_play,
|
|
182
|
+
gobj_yuno,
|
|
183
|
+
db_load_persistent_attrs,
|
|
184
|
+
db_save_persistent_attrs,
|
|
185
|
+
db_remove_persistent_attrs,
|
|
186
|
+
db_list_persistent_attrs,
|
|
187
|
+
register_c_yuno,
|
|
188
|
+
register_c_timer,
|
|
189
|
+
register_c_ievent_cli,
|
|
190
|
+
} from "@yuneta/gobj-js";
|
|
191
|
+
|
|
192
|
+
// 1. Register GClasses
|
|
193
|
+
register_c_yuno();
|
|
194
|
+
register_c_timer();
|
|
195
|
+
register_c_ievent_cli();
|
|
196
|
+
|
|
197
|
+
// 2. Initialize framework
|
|
198
|
+
gobj_start_up(
|
|
199
|
+
null, // jn_global_settings (JSON)
|
|
200
|
+
db_load_persistent_attrs, // load persistent attrs
|
|
201
|
+
db_save_persistent_attrs, // save persistent attrs
|
|
202
|
+
db_remove_persistent_attrs, // remove persistent attrs
|
|
203
|
+
db_list_persistent_attrs, // list persistent attrs
|
|
204
|
+
null, // global command parser fn
|
|
205
|
+
null // global stats parser fn
|
|
206
|
+
);
|
|
66
207
|
|
|
67
|
-
|
|
208
|
+
// 3. Create the Yuno (application root)
|
|
209
|
+
let yuno = gobj_create_yuno("yuno", "C_YUNO", {
|
|
210
|
+
yuno_name: "my_app",
|
|
211
|
+
yuno_role: "my_role",
|
|
212
|
+
yuno_version: "1.0.0",
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// 4. Create services under the yuno
|
|
216
|
+
gobj_create_service("main", "C_MY_SERVICE", {}, gobj_yuno());
|
|
217
|
+
|
|
218
|
+
// 5. Start everything
|
|
219
|
+
gobj_start(yuno);
|
|
220
|
+
gobj_play(yuno); // triggers mt_play on the default service
|
|
221
|
+
```
|
|
68
222
|
|
|
69
|
-
|
|
223
|
+
---
|
|
70
224
|
|
|
71
|
-
|
|
225
|
+
## Core Concepts
|
|
226
|
+
|
|
227
|
+
### GClass & GObject
|
|
228
|
+
|
|
229
|
+
A **GClass** is a class definition — registered once at startup. A **GObject** is an instance of a GClass.
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// Register a GClass
|
|
233
|
+
gclass_create(name, event_types, states, gmt, lmt,
|
|
234
|
+
attrs_table, private_data, authz_table,
|
|
235
|
+
command_table, s_user_trace_level, gclass_flag);
|
|
236
|
+
|
|
237
|
+
// Create an instance
|
|
238
|
+
let gobj = gobj_create(name, gclass_name, attributes, parent);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Finite State Machines
|
|
242
|
+
|
|
243
|
+
Each GClass defines:
|
|
244
|
+
- A list of **states** (strings, e.g. `"ST_IDLE"`, `"ST_CONNECTED"`)
|
|
245
|
+
- Per-state **event-action tables**: `[event_name, action_fn, next_state]`
|
|
246
|
+
|
|
247
|
+
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.
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const st_idle = [
|
|
251
|
+
["EV_CONNECT", ac_connect, "ST_CONNECTED"],
|
|
252
|
+
["EV_TIMEOUT", ac_timeout, null], // stay in ST_IDLE
|
|
253
|
+
];
|
|
254
|
+
const st_connected = [
|
|
255
|
+
["EV_DISCONNECT", ac_disconnect, "ST_IDLE"],
|
|
256
|
+
["EV_MESSAGE", ac_message, null],
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const states = [
|
|
260
|
+
["ST_IDLE", st_idle],
|
|
261
|
+
["ST_CONNECTED", st_connected],
|
|
262
|
+
];
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Attributes (SData)
|
|
266
|
+
|
|
267
|
+
Attributes are declared in a schema table using `SDATA()` macros. Each attribute has a type, name, flags, default value, and description.
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
import { SDATA, SDATA_END, data_type_t, sdata_flag_t } from "@yuneta/gobj-js";
|
|
271
|
+
|
|
272
|
+
const attrs_table = [
|
|
273
|
+
SDATA(data_type_t.DTP_STRING, "url", sdata_flag_t.SDF_RD, "", "Server URL"),
|
|
274
|
+
SDATA(data_type_t.DTP_INTEGER, "timeout", sdata_flag_t.SDF_RD, 0, "Timeout ms"),
|
|
275
|
+
SDATA(data_type_t.DTP_BOOLEAN, "connected", sdata_flag_t.SDF_RD, false, "Connection state"),
|
|
276
|
+
SDATA(data_type_t.DTP_STRING, "saved_key", sdata_flag_t.SDF_PERSIST, "", "Persisted value"),
|
|
277
|
+
SDATA_END()
|
|
278
|
+
];
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Data types (`data_type_t`):** `DTP_STRING`, `DTP_BOOLEAN`, `DTP_INTEGER`, `DTP_REAL`, `DTP_LIST`, `DTP_DICT`, `DTP_JSON`, `DTP_POINTER`
|
|
282
|
+
|
|
283
|
+
**Flags (`sdata_flag_t`):** `SDF_RD`, `SDF_WR`, `SDF_PERSIST`, `SDF_STATS`, `SDF_AUTHZ_R`, `SDF_AUTHZ_W`, `SDF_REQUIRED`, `SDF_VOLATIL`
|
|
284
|
+
|
|
285
|
+
### Events & Pub-Sub
|
|
286
|
+
|
|
287
|
+
GObjects communicate via events with JSON payloads:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
// Send directly to a specific GObject
|
|
291
|
+
gobj_send_event(target_gobj, "EV_CONNECT", { url: "ws://..." }, src_gobj);
|
|
292
|
+
|
|
293
|
+
// Publish to all subscribers
|
|
294
|
+
gobj_publish_event(gobj, "EV_DATA_READY", { data: [] });
|
|
295
|
+
|
|
296
|
+
// Subscribe to events from a source
|
|
297
|
+
gobj_subscribe_event(source_gobj, "EV_DATA_READY", {}, subscriber_gobj);
|
|
298
|
+
|
|
299
|
+
// Unsubscribe
|
|
300
|
+
gobj_unsubscribe_event(source_gobj, "EV_DATA_READY", {}, subscriber_gobj);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### GObject Tree (Yuno)
|
|
304
|
+
|
|
305
|
+
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).
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
Yuno
|
|
309
|
+
├── Service "auth" (C_IEVENT_CLI)
|
|
310
|
+
├── Service "main" (C_MY_SERVICE)
|
|
311
|
+
│ └── Child "sub" (C_SOME_GCLASS)
|
|
312
|
+
└── Service "timer" (C_TIMER)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Public API
|
|
318
|
+
|
|
319
|
+
### Framework Bootstrap
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
gobj_start_up(
|
|
323
|
+
jn_global_settings, // JSON object or null
|
|
324
|
+
load_persistent_attrs_fn, // fn(gobj, keys)
|
|
325
|
+
save_persistent_attrs_fn, // fn(gobj, keys)
|
|
326
|
+
remove_persistent_attrs_fn, // fn(gobj, keys)
|
|
327
|
+
list_persistent_attrs_fn, // fn(gobj, keys)
|
|
328
|
+
global_command_parser_fn, // fn or null
|
|
329
|
+
global_stats_parser_fn // fn or null
|
|
330
|
+
)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### GClass Registration
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
gclass_create(name, event_types, states, gmt, lmt, attrs_table,
|
|
337
|
+
private_data, authz_table, command_table,
|
|
338
|
+
s_user_trace_level, gclass_flag)
|
|
339
|
+
|
|
340
|
+
gclass_find_by_name(name) // → GClass or null
|
|
341
|
+
gclass_check_fsm(gclass) // validate FSM (returns error count)
|
|
342
|
+
gclass_add_event_type(gclass, event_name, flag)
|
|
343
|
+
gclass_add_state(gclass, state_name)
|
|
344
|
+
gclass_add_ev_action(gclass, state_name, event_name, action_fn, next_state)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### GObject Lifecycle
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
// Creation
|
|
351
|
+
gobj_create(name, gclass_name, attrs, parent) // generic child
|
|
352
|
+
gobj_create_yuno(name, gclass_name, attrs) // application root
|
|
353
|
+
gobj_create_service(name, gclass_name, attrs, yuno) // named service
|
|
354
|
+
gobj_create_default_service(name, gclass_name, attrs, yuno)
|
|
355
|
+
gobj_create_volatil(name, gclass_name, attrs, parent)
|
|
356
|
+
gobj_create_pure_child(name, gclass_name, attrs, parent)
|
|
357
|
+
|
|
358
|
+
// Start / Stop
|
|
359
|
+
gobj_start(gobj) // calls mt_start
|
|
360
|
+
gobj_stop(gobj) // calls mt_stop
|
|
361
|
+
gobj_start_children(gobj)
|
|
362
|
+
gobj_stop_children(gobj)
|
|
363
|
+
gobj_start_tree(gobj)
|
|
364
|
+
gobj_stop_tree(gobj)
|
|
365
|
+
|
|
366
|
+
// Play / Pause (applied to default service)
|
|
367
|
+
gobj_play(gobj)
|
|
368
|
+
gobj_pause(gobj)
|
|
369
|
+
|
|
370
|
+
// Destroy
|
|
371
|
+
gobj_destroy(gobj)
|
|
372
|
+
|
|
373
|
+
// Status
|
|
374
|
+
gobj_is_running(gobj) // → boolean
|
|
375
|
+
gobj_is_playing(gobj) // → boolean
|
|
376
|
+
gobj_is_destroying(gobj) // → boolean
|
|
377
|
+
gobj_is_volatil(gobj) // → boolean
|
|
378
|
+
gobj_is_pure_child(gobj) // → boolean
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### State Machine
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
gobj_current_state(gobj) // → state name string
|
|
385
|
+
gobj_change_state(gobj, new_state) // trigger FSM transition
|
|
386
|
+
gobj_has_event(gobj, event, event_flag) // → boolean; pass 0 to ignore flag
|
|
387
|
+
gobj_has_output_event(gobj, event, event_flag) // same; checks EVF_OUTPUT_EVENT
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Attribute Access
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
// Generic read/write
|
|
394
|
+
// `path` accepts back-tick navigation to walk into child gobjs: "child`subattr"
|
|
395
|
+
gobj_read_attr (gobj, name, src)
|
|
396
|
+
gobj_write_attr(gobj, path, value, src)
|
|
397
|
+
gobj_read_attrs (gobj, include_flag, src) // → JSON object of matching attrs
|
|
398
|
+
gobj_write_attrs(gobj, kw, include_flag, src) // include_flag filters which are written
|
|
399
|
+
gobj_has_attr(gobj, name) // → boolean
|
|
400
|
+
|
|
401
|
+
// Typed reads — name only (no src)
|
|
402
|
+
gobj_read_bool_attr (gobj, name)
|
|
403
|
+
gobj_read_integer_attr(gobj, name)
|
|
404
|
+
gobj_read_str_attr (gobj, name)
|
|
405
|
+
gobj_read_pointer_attr(gobj, name)
|
|
406
|
+
|
|
407
|
+
// Typed writes — (name, value), no src
|
|
408
|
+
gobj_write_bool_attr (gobj, name, value)
|
|
409
|
+
gobj_write_integer_attr(gobj, name, value)
|
|
410
|
+
gobj_write_str_attr (gobj, name, value)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Event System
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
// Sending
|
|
417
|
+
gobj_send_event(gobj, event, kw, src) // direct send
|
|
418
|
+
gobj_publish_event(gobj, event, kw) // publish to subscribers
|
|
419
|
+
gobj_post_event(gobj, event, kw, src) // post (deferred)
|
|
420
|
+
|
|
421
|
+
// Subscriptions
|
|
422
|
+
gobj_subscribe_event (publisher, event, kw, subscriber)
|
|
423
|
+
gobj_unsubscribe_event(publisher, event, kw, subscriber)
|
|
424
|
+
gobj_unsubscribe_list (publisher, dl_subs, force) // force=true also removes hard subs
|
|
425
|
+
gobj_find_subscriptions(publisher, event, kw, subscriber)
|
|
426
|
+
gobj_list_subscriptions(gobj)
|
|
427
|
+
gobj_find_subscribings (subscriber, event, kw, publisher)
|
|
428
|
+
|
|
429
|
+
// Commands & Stats
|
|
430
|
+
gobj_command(gobj, command, kw, src) // → response JSON
|
|
431
|
+
gobj_stats(gobj, stats, kw, src) // → response JSON
|
|
432
|
+
build_command_response(gobj, result, comment, schema, data)
|
|
433
|
+
build_stats_response(gobj, result, comment, schema, data)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Hierarchy & Navigation
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
gobj_parent(gobj)
|
|
440
|
+
gobj_yuno() // → the Yuno root
|
|
441
|
+
gobj_default_service() // → default service under yuno
|
|
442
|
+
|
|
443
|
+
gobj_name(gobj)
|
|
444
|
+
gobj_short_name(gobj)
|
|
445
|
+
gobj_full_name(gobj)
|
|
446
|
+
gobj_gclass_name(gobj)
|
|
447
|
+
gobj_yuno_name(gobj)
|
|
448
|
+
gobj_yuno_role(gobj)
|
|
449
|
+
gobj_yuno_id(gobj)
|
|
450
|
+
|
|
451
|
+
gobj_find_child(gobj, kw_filter)
|
|
452
|
+
gobj_find_service(name, verbose)
|
|
453
|
+
gobj_find_gobj(gobj, path)
|
|
454
|
+
gobj_search_path(gobj, path)
|
|
455
|
+
|
|
456
|
+
gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_data2)
|
|
457
|
+
gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, user_data2)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Persistence
|
|
461
|
+
|
|
462
|
+
Backed by `localStorage` (browser) via `dbsimple.js`:
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
// Low-level (dbsimple)
|
|
466
|
+
db_load_persistent_attrs(gobj, keys)
|
|
467
|
+
db_save_persistent_attrs(gobj, keys)
|
|
468
|
+
db_remove_persistent_attrs(gobj, keys)
|
|
469
|
+
db_list_persistent_attrs(gobj, keys)
|
|
470
|
+
|
|
471
|
+
// Via gobj (delegates to registered persistence fns)
|
|
472
|
+
gobj_load_persistent_attrs(gobj, keys)
|
|
473
|
+
gobj_save_persistent_attrs(gobj, keys)
|
|
474
|
+
gobj_remove_persistent_attrs(gobj, keys)
|
|
475
|
+
gobj_list_persistent_attrs(gobj, keys)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Attributes marked `SDF_PERSIST` are automatically saved/loaded.
|
|
479
|
+
|
|
480
|
+
### Helpers & Utilities
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
// JSON / Object operations
|
|
484
|
+
json_deep_copy(obj)
|
|
485
|
+
json_is_identical(a, b)
|
|
486
|
+
json_object_update(dst, src) // merge src into dst
|
|
487
|
+
json_object_update_existing(dst, src) // only existing keys
|
|
488
|
+
json_object_update_missing(dst, src) // only missing keys
|
|
489
|
+
json_object_get(obj, key)
|
|
490
|
+
json_object_set(obj, key, value)
|
|
491
|
+
json_object_del(obj, key)
|
|
492
|
+
json_array_append(arr, value)
|
|
493
|
+
json_array_remove(arr, index)
|
|
494
|
+
json_array_extend(dst, src)
|
|
495
|
+
json_object_size(obj)
|
|
496
|
+
json_array_size(arr)
|
|
497
|
+
|
|
498
|
+
// Type checking
|
|
499
|
+
is_object(v), is_array(v), is_string(v), is_number(v),
|
|
500
|
+
is_boolean(v), is_null(v), is_date(v), is_function(v), is_gobj(v)
|
|
501
|
+
|
|
502
|
+
// Keyword (kw) operations
|
|
503
|
+
//
|
|
504
|
+
// Most kw_* helpers take `gobj` as first argument (for error logging)
|
|
505
|
+
// and a dot/back-tick path instead of a single key. Exceptions that do
|
|
506
|
+
// NOT take gobj: kw_has_key, kw_pop, kw_match_simple.
|
|
507
|
+
|
|
508
|
+
kw_has_key(kw, key) // → boolean
|
|
509
|
+
kw_pop(kw1, kw2) // delete from kw1 the keys listed in kw2
|
|
510
|
+
kw_delete(gobj, kw, path)
|
|
511
|
+
kw_find_path(gobj, kw, path, verbose) // back-tick path: "a`b`c"
|
|
512
|
+
|
|
513
|
+
kw_get_bool(gobj, kw, path, default_value, flag)
|
|
514
|
+
kw_get_int (gobj, kw, path, default_value, flag)
|
|
515
|
+
kw_get_real(gobj, kw, path, default_value, flag)
|
|
516
|
+
kw_get_str (gobj, kw, path, default_value, flag)
|
|
517
|
+
kw_get_dict(gobj, kw, path, default_value, flag)
|
|
518
|
+
kw_get_list(gobj, kw, path, default_value, flag)
|
|
519
|
+
kw_get_dict_value(gobj, kw, path, default_value, flag)
|
|
520
|
+
|
|
521
|
+
kw_set_dict_value(gobj, kw, path, value)
|
|
522
|
+
kw_set_subdict_value(gobj, kw, path, key, value)
|
|
523
|
+
|
|
524
|
+
kw_match_simple(kw, filter) // → boolean
|
|
525
|
+
kw_select (gobj, kw, jn_filter, match_fn) // filter list
|
|
526
|
+
kw_collect(gobj, kw, jn_filter, match_fn) // extract subset
|
|
527
|
+
kw_clone_by_keys (gobj, kw, keys, verbose)
|
|
528
|
+
kw_clone_by_not_keys(gobj, kw, keys, verbose)
|
|
529
|
+
|
|
530
|
+
// Flags: KW_REQUIRED, KW_CREATE, KW_EXTRACT, KW_RECURSIVE, KW_WILD_NUMBER
|
|
531
|
+
|
|
532
|
+
// Local storage helpers (browser localStorage, no "store" parameter)
|
|
533
|
+
kw_get_local_storage_value(key, default_value, create) // create=false
|
|
534
|
+
kw_set_local_storage_value(key, value)
|
|
535
|
+
kw_remove_local_storage_value(key)
|
|
536
|
+
|
|
537
|
+
// Misc utilities
|
|
538
|
+
current_timestamp() // ISO string
|
|
539
|
+
get_now() // Date object
|
|
540
|
+
node_uuid() // generate UUID
|
|
541
|
+
parseBoolean(v) // coerce to boolean
|
|
542
|
+
empty_string(s) // true if null/undefined/""
|
|
543
|
+
str_in_list(list, str, ci) // case-insensitive optional
|
|
544
|
+
index_in_list(list, value)
|
|
545
|
+
delete_from_list(list, value)
|
|
546
|
+
jwtDecode(token) // → header/payload/signature
|
|
547
|
+
jwt2json(token) // → payload JSON
|
|
548
|
+
debounce(fn, delay)
|
|
549
|
+
timeTracker()
|
|
550
|
+
|
|
551
|
+
// Inter-event message stack
|
|
552
|
+
msg_iev_push_stack (gobj, kw, stack, jn_data) // push jn_data onto named stack
|
|
553
|
+
msg_iev_get_stack (gobj, kw, stack, verbose) // peek top of named stack
|
|
554
|
+
msg_iev_set_msg_type(gobj, kw, msg_type) // "" to delete
|
|
555
|
+
msg_iev_get_msg_type(gobj, kw)
|
|
556
|
+
msg_iev_write_key(kw, key, value) // no gobj
|
|
557
|
+
msg_iev_read_key (kw, key) // no gobj
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Logging
|
|
561
|
+
|
|
562
|
+
All log functions take a single `format` argument and use `printf`-style
|
|
563
|
+
substitution (`%s`, `%d`, `%i`, `%f`, `%o`/`%O`). There is no `gobj` or
|
|
564
|
+
error-code argument (that's the C API — the JS runtime is simpler).
|
|
565
|
+
|
|
566
|
+
```javascript
|
|
567
|
+
log_error(format, ...args) // red, prefixed "ERROR"
|
|
568
|
+
log_warning(format, ...args) // yellow, prefixed "WARNING"
|
|
569
|
+
log_info(format, ...args) // cyan, prefixed "INFO"
|
|
570
|
+
log_debug(format, ...args) // silver, prefixed "DEBUG"
|
|
571
|
+
trace_msg(format, ...args) // cyan, prefixed "MSG"
|
|
572
|
+
trace_json(json, msg) // dir-dump a JSON object
|
|
573
|
+
|
|
574
|
+
// Redirect error/warning output to a single remote handler
|
|
575
|
+
// (info/debug always go to the browser console)
|
|
576
|
+
set_remote_log_functions(remote_log_fn) // fn(message) — single function
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### String Formatting
|
|
580
|
+
|
|
581
|
+
```javascript
|
|
582
|
+
sprintf(format, ...args) // printf-style
|
|
583
|
+
vsprintf(fmt, argv) // variadic version
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
Format specifiers: `%s` `%d` `%i` `%f` `%e` `%g` `%o` `%x` `%X` `%b` `%c` `%j` (JSON) `%t` (boolean) `%T` (type) `%v` (value) `%u` (unsigned)
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Built-in GClasses
|
|
591
|
+
|
|
592
|
+
### C_YUNO
|
|
593
|
+
|
|
594
|
+
The application root. Always the first GClass registered.
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
import { register_c_yuno } from "@yuneta/gobj-js";
|
|
598
|
+
register_c_yuno();
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Key attributes: `yuno_name`, `yuno_role`, `yuno_id`, `yuno_version`, `yuno_release`, `yuneta_version`, `required_services`, `tracing`, `start_date`, `node_uuid`, `__username__`
|
|
602
|
+
|
|
603
|
+
### C_TIMER
|
|
604
|
+
|
|
605
|
+
Manages timeouts and periodic timers.
|
|
606
|
+
|
|
607
|
+
```javascript
|
|
608
|
+
import { register_c_timer, set_timeout, set_timeout_periodic, clear_timeout } from "@yuneta/gobj-js";
|
|
609
|
+
register_c_timer();
|
|
610
|
+
|
|
611
|
+
// One-shot timeout (ms)
|
|
612
|
+
set_timeout(timer_gobj, 5000);
|
|
613
|
+
|
|
614
|
+
// Periodic timeout
|
|
615
|
+
set_timeout_periodic(timer_gobj, 1000);
|
|
616
|
+
|
|
617
|
+
// Cancel
|
|
618
|
+
clear_timeout(timer_gobj);
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Events published: `EV_TIMEOUT`, `EV_TIMEOUT_PERIODIC`
|
|
622
|
+
|
|
623
|
+
Attributes: `subscriber`, `periodic`, `msec`
|
|
624
|
+
|
|
625
|
+
### C_IEVENT_CLI
|
|
626
|
+
|
|
627
|
+
Inter-event client — proxies a remote Yuneta service over WebSocket so it looks like a local GObject. Used to communicate with backend yunos.
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
import { register_c_ievent_cli } from "@yuneta/gobj-js";
|
|
631
|
+
register_c_ievent_cli();
|
|
632
|
+
|
|
633
|
+
let remote = gobj_create_service("backend", "C_IEVENT_CLI", {
|
|
634
|
+
url: "ws://localhost:1991",
|
|
635
|
+
wanted_yuno_role: "agent",
|
|
636
|
+
wanted_yuno_service: "agent",
|
|
637
|
+
jwt: "...",
|
|
638
|
+
}, gobj_yuno());
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Key attributes: `url`, `jwt`, `wanted_yuno_role`, `wanted_yuno_name`, `wanted_yuno_service`, `remote_yuno_role`, `remote_yuno_name`, `remote_yuno_service`
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## TreeDB Helpers
|
|
646
|
+
|
|
647
|
+
Utilities for interacting with the Yuneta TreeDB (schema-driven graph database):
|
|
648
|
+
|
|
649
|
+
```javascript
|
|
650
|
+
import {
|
|
651
|
+
treedb_hook_data_size,
|
|
652
|
+
treedb_decoder_fkey,
|
|
653
|
+
treedb_encoder_fkey,
|
|
654
|
+
treedb_decoder_hook,
|
|
655
|
+
treedb_get_field_desc,
|
|
656
|
+
template_get_field_desc,
|
|
657
|
+
create_template_record,
|
|
658
|
+
} from "@yuneta/gobj-js";
|
|
659
|
+
|
|
660
|
+
treedb_hook_data_size(value) // count with caching
|
|
661
|
+
treedb_decoder_fkey(col, fkey) // parse foreign key reference
|
|
662
|
+
treedb_encoder_fkey(col, fkey) // build fkey string "topic^id^hook"
|
|
663
|
+
treedb_decoder_hook(col, hook) // parse hook reference
|
|
664
|
+
treedb_get_field_desc(col) // build field descriptor from column
|
|
665
|
+
template_get_field_desc(key, value) // build field descriptor from template
|
|
666
|
+
create_template_record(template, kw) // instantiate from template
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## Writing a Custom GClass
|
|
672
|
+
|
|
673
|
+
```javascript
|
|
674
|
+
import {
|
|
675
|
+
SDATA, SDATA_END,
|
|
676
|
+
data_type_t, sdata_flag_t,
|
|
677
|
+
gclass_create,
|
|
678
|
+
gobj_subscribe_event, gobj_yuno,
|
|
679
|
+
trace_msg,
|
|
680
|
+
} from "@yuneta/gobj-js";
|
|
681
|
+
|
|
682
|
+
const GCLASS_NAME = "C_MY_CLASS";
|
|
683
|
+
|
|
684
|
+
// 1. Attribute schema
|
|
685
|
+
const attrs_table = [
|
|
686
|
+
SDATA(data_type_t.DTP_STRING, "url", sdata_flag_t.SDF_RD, "", "Remote URL"),
|
|
687
|
+
SDATA(data_type_t.DTP_INTEGER, "retries", sdata_flag_t.SDF_RD, 3, "Max retries"),
|
|
688
|
+
SDATA_END()
|
|
689
|
+
];
|
|
690
|
+
|
|
691
|
+
// 2. Private data (per-instance, copied from this template)
|
|
692
|
+
const PRIVATE_DATA = {
|
|
693
|
+
retry_count: 0,
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// 3. Lifecycle methods
|
|
697
|
+
function mt_create(gobj) {
|
|
698
|
+
// instance created, attrs not yet populated
|
|
699
|
+
}
|
|
700
|
+
function mt_start(gobj) {
|
|
701
|
+
// subscribe to events from other gobjs here
|
|
702
|
+
gobj_subscribe_event(gobj_yuno(), "EV_TIMEOUT_PERIODIC", {}, gobj);
|
|
703
|
+
}
|
|
704
|
+
function mt_stop(gobj) { }
|
|
705
|
+
function mt_destroy(gobj) { }
|
|
706
|
+
|
|
707
|
+
// 4. Action functions
|
|
708
|
+
function ac_timeout(gobj, event, kw, src) {
|
|
709
|
+
trace_msg(`Tick! retry_count=${gobj.priv.retry_count}`);
|
|
710
|
+
gobj.priv.retry_count++;
|
|
711
|
+
return 0; // kw consumed
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// 5. FSM
|
|
715
|
+
const st_idle = [
|
|
716
|
+
["EV_TIMEOUT_PERIODIC", ac_timeout, null],
|
|
717
|
+
];
|
|
718
|
+
const states = [
|
|
719
|
+
["ST_IDLE", st_idle],
|
|
720
|
+
];
|
|
721
|
+
const event_types = [
|
|
722
|
+
["EV_TIMEOUT_PERIODIC", 0],
|
|
723
|
+
];
|
|
724
|
+
|
|
725
|
+
// 6. Methods table
|
|
726
|
+
const gmt = { mt_create, mt_start, mt_stop, mt_destroy };
|
|
727
|
+
|
|
728
|
+
// 7. Register
|
|
729
|
+
function register_c_my_class() {
|
|
730
|
+
return gclass_create(
|
|
731
|
+
GCLASS_NAME,
|
|
732
|
+
event_types,
|
|
733
|
+
states,
|
|
734
|
+
gmt,
|
|
735
|
+
0, // lmt (low-level methods)
|
|
736
|
+
attrs_table,
|
|
737
|
+
PRIVATE_DATA,
|
|
738
|
+
0, // authz_table
|
|
739
|
+
0, // command_table
|
|
740
|
+
0, // s_user_trace_level
|
|
741
|
+
0 // gclass_flag
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export { register_c_my_class };
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## Source Layout
|
|
751
|
+
|
|
752
|
+
```
|
|
753
|
+
kernel/js/gobj-js/
|
|
754
|
+
├── README.md ← this file
|
|
755
|
+
├── package.json
|
|
756
|
+
├── vite.config.js
|
|
757
|
+
├── dist/ ← compiled outputs (ES, CJS, UMD, IIFE)
|
|
758
|
+
├── tests/
|
|
759
|
+
│ └── kw_delete.test.js
|
|
760
|
+
└── src/
|
|
761
|
+
├── index.js ← public API entry point (barrel export)
|
|
762
|
+
├── gobj.js ← GObject/FSM runtime (~4 500 LOC)
|
|
763
|
+
├── helpers.js ← utilities, JSON, logging (~3 300 LOC)
|
|
764
|
+
├── c_ievent_cli.js ← remote service proxy (~1 350 LOC)
|
|
765
|
+
├── lib_treedb.js ← TreeDB helpers (~ 540 LOC)
|
|
766
|
+
├── c_timer.js ← Timer GClass (~ 330 LOC)
|
|
767
|
+
├── c_yuno.js ← Yuno GClass (~ 290 LOC)
|
|
768
|
+
├── dbsimple.js ← localStorage persistence (~ 140 LOC)
|
|
769
|
+
├── sprintf.js ← printf-style formatting (~ 210 LOC)
|
|
770
|
+
├── command_parser.js ← command parsing
|
|
771
|
+
└── stats_parser.js ← stats parsing
|
|
772
|
+
```
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuneta/gobj-js",
|
|
3
|
-
"version": "7.1
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "7.2.1",
|
|
4
|
+
"description": "GObject + FSM runtime for JavaScript — event-driven components with typed attributes, pub/sub, and WebSocket inter-event client",
|
|
5
|
+
"author": "ArtGins",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/gobj-js.cjs.js",
|