@yuneta/gobj-js 7.26.23 → 7.26.25

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 CHANGED
@@ -286,6 +286,48 @@ const attrs_table = [
286
286
 
287
287
  **A value is read as the attribute's type, as in C** (`gobj_write_attr()`, the kw of `gobj_create()`, `gobj_write_attrs()`, the persistent attrs; 7.26.22+): a string for a boolean, integer, real, list or dict is read from its text by the C command parser's grammar (`command_parameter_from_json()`: `"yes"` is `true`, `"42"` is 42, `"007"` and `"12abc"` are refused), a number or a boolean as the exact type (`2.7` to an integer is refused). A refused value is not written: one `log_error` names the attr, its type, why and the value (masked, capped; a `SDF_SECRET` one never shown). A key of a kw whose value is `undefined` is an absent key; `gobj_write_attr()` of `undefined` to a typed attr is refused. The typed writers read the value as THEIR type by the same grammar (`gobj_write_integer_attr(g, "x", 2.5)` is refused, `gobj_write_bool_attr(g, "on", "yes")` is `true`), and `gobj_write_str_attr()` writes `null`/`undefined` as `""`, never the text `"null"`.
288
288
 
289
+ **A refused write keeps the value the attribute had** (BREAKING in 7.26.22, kept
290
+ by decision: the C kernel's `json2item()` does the same). `undefined`, `null`, an
291
+ empty text or a text that is not a boolean, given to `gobj_write_bool_attr()`, is
292
+ not `false`: the function returns -1, logs one ERROR, and the attribute does not
293
+ change. The same for `gobj_write_attr()` of `null` or `""` to a `DTP_REAL`.
294
+ Compute the value before the write when it can be absent:
295
+
296
+ ```javascript
297
+ gobj_write_bool_attr(gobj, "selectable", true);
298
+ gobj_write_bool_attr(gobj, "selectable", kw.selectable); // kw has none: -1, ERROR,
299
+ // "selectable" is still true
300
+ gobj_write_bool_attr(gobj, "selectable", kw.selectable === true); // false
301
+ ```
302
+
303
+ A value with no json text (a circular object such as a gobj or a DOM node, a
304
+ `BigInt`) given to a string attribute is refused the same way, and is never
305
+ thrown (7.26.24). So is a value that is not json ALL THE WAY DOWN given to a
306
+ string, a `DTP_DICT` or a `DTP_LIST` (7.26.25): a cycle anywhere inside, a
307
+ `BigInt` inside (`[1n]`), a `Map`, a `Set`, a `Date`, a typed array, an instance
308
+ of a class. They were written, and burst (or turned into `"{}"`) when the value
309
+ was serialized, far from the write. A key whose value is `undefined` or a
310
+ function is dropped, as `JSON.stringify()` drops it:
311
+
312
+ ```javascript
313
+ gobj_write_attr(gobj, "settings", {a: 1, b: undefined}); // 0: b goes when serialized
314
+ gobj_write_attr(gobj, "settings", new Map([["a", 1]])); // -1, ERROR "a value with no json text"
315
+ gobj_write_str_attr(gobj, "name", new Map()); // -1: it was the text "{}"
316
+ ```
317
+
318
+ A `DTP_JSON` attribute is not checked: it takes any value, as before.
319
+
320
+ A json TEXT is read as the C parser reads it: one that C
321
+ refuses is refused in JS too -- an escaped NUL (`"\u0000"`), a lone surrogate,
322
+ an integer out of int64, a real that overflows (`1e309`; to a `DTP_JSON` it is
323
+ the text `"1e309"`, not `Infinity`), a text nested deeper than 2048 levels (a
324
+ scalar is a level too; 7.26.25). A number beyond int64 is "a real" to a dict,
325
+ as in C (`1e20`), and `9223372036854775807` is taken by an integer (JSON.parse()
326
+ makes it 2^63, kept as such). Two differences remain, because a JS number
327
+ cannot carry them: an integer beyond 2^53 is rounded, and `2.0` (and `-0.0`) is
328
+ the integer (see `tests/fixtures/attr_types_parity.json`, which the C test
329
+ `tests/c/attr_types` of yunetas reads too).
330
+
289
331
  ### Events & Pub-Sub
290
332
 
291
333
  GObjects communicate via events with JSON payloads:
@@ -463,6 +505,7 @@ gobj_current_state(gobj) // → state name string
463
505
  gobj_change_state(gobj, new_state) // trigger FSM transition
464
506
  gobj_has_event(gobj, event, event_flag) // → boolean; pass 0 to ignore flag
465
507
  gobj_has_output_event(gobj, event, event_flag) // same; checks EVF_OUTPUT_EVENT
508
+ gobj_find_event_type(event, event_flag, verbose) // the event type in ANY registered gclass, or null
466
509
  ```
467
510
 
468
511
  ### Attribute Access
@@ -720,6 +763,8 @@ mask_secrets_inline("sent Bearer mF_9.B5f-4.1JqM") // → "sent Bearer ********
720
763
  mask_secrets_inline("the bearer of this letter") // → null: prose
721
764
  mask_secrets_inline("\"password\": hunter2") // → "\"password\": ********"
722
765
  mask_secrets_inline("GET https://h/get?token=abc&x=1") // → "GET https://h/get?token=********&x=1"
766
+ mask_secrets_inline("a=1&user%5Bpassword%5D=x") // → "a=1&user%5Bpassword%5D=********"
767
+ mask_secrets_inline("https://bob:hunter2@host/x") // → "https://bob:********@host/x"
723
768
  json_mask_secrets({user: "a", password: "x"}) // → {user: "a", password: "********"}
724
769
  json_mask_secrets({note: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4In0.c2ln"})
725
770
  // → {note: "********"}: a JWT under any key
@@ -960,7 +1005,11 @@ Events published: `EV_TIMEOUT`, `EV_TIMEOUT_PERIODIC`. They go to the
960
1005
  `subscriber` attr, else, for a pure child, to its parent, which the timer
961
1006
  subscribes in `mt_create` — the C timer's rule. Up to 7.26.21 a pure child
962
1007
  SENT the timeout to its parent instead of publishing it (and to its parent
963
- even when it had a `subscriber`).
1008
+ even when it had a `subscriber`). BREAKING in 7.26.22: the parent's
1009
+ `EV_TIMEOUT` action now runs inside the timer's publication, so destroying
1010
+ the timer or the parent there is the ERROR *"Destroying a gobj while its tree
1011
+ is publishing"*. Defer it with `gobj_post_event()` to yourself; re-arming or
1012
+ clearing the timer in the action is fine.
964
1013
 
965
1014
  Attributes: `subscriber`, `periodic`, `msec`
966
1015
 
@@ -982,7 +1031,25 @@ let remote = gobj_create_service("backend", "C_IEVENT_CLI", {
982
1031
 
983
1032
  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`
984
1033
 
985
- Its events (`EV_ON_OPEN`, `EV_ON_CLOSE`, `EV_ON_OPEN_ERROR`, `EV_ON_ID_NAK` and every message that names no service) are PUBLISHED: to the `subscriber` attr, else, for a pure child, to its parent, which it subscribes in `mt_create` — one subscription, as the C transport. Up to 7.26.21 a pure child SENT three of them to its parent and subscribed nobody, so its parent never heard `EV_ON_CLOSE` / `EV_ON_OPEN_ERROR`.
1034
+ Its events (`EV_ON_OPEN`, `EV_ON_CLOSE`, `EV_ON_OPEN_ERROR`, `EV_ON_ID_NAK` and every message that names no service) are PUBLISHED: to the `subscriber` attr, else, for a pure child, to its parent, which it subscribes in `mt_create` — one subscription, as the C transport. Up to 7.26.21 a pure child SENT three of them to its parent and subscribed nobody, so its parent never heard `EV_ON_CLOSE` / `EV_ON_OPEN_ERROR`: a host of a pure child must declare both in its FSM (BREAKING in 7.26.22).
1035
+
1036
+ A message that names no service is published only when its event is declared PUBLIC by some registered gclass (`gobj_find_event_type(name, EVF_PUBLIC_EVENT)`, as C's `gclass_find_public_event()`; 7.26.24 took any declaration, 7.26.25 requires it public); any other is dropped with a WARNING, its name masked. The events the transport says itself (`EV_ON_OPEN`, `EV_ON_CLOSE`, `EV_ON_OPEN_ERROR`, `EV_ON_ID_NAK`, `EV_TIMEOUT`, `EV_ON_MESSAGE`, and `EV_IDENTITY_CARD_ACK` in session) are never taken from the server, in any case of letters and whatever service the message names: a WARNING, dropped (7.26.25). A backend event an app receives must therefore be declared `EVF_PUBLIC_EVENT` in the gclass that receives it:
1037
+
1038
+ ```javascript
1039
+ const event_types = [
1040
+ ["EV_MT_COMMAND_ANSWER", event_flag_t.EVF_PUBLIC_EVENT],
1041
+ ["EV_REALTIME_ALARM", event_flag_t.EVF_PUBLIC_EVENT],
1042
+ ];
1043
+ ``` What the server gets wrong is a WARNING, never an ERROR. Before the session only `EV_IDENTITY_CARD_ACK` is taken; a `dst_role` or `dst_yuno` that is not this yuno's closes the channel too (an empty one is legal: anybody's), as the C transport does; an event that the named service does not take as public is dropped (7.26.24). A channel closed for a fault of the server is closed with **4002**, not 1000 (a browser may send only 1000 or 3000-4999, so 1002 cannot be used), so `EV_ON_CLOSE` says the session was cut (7.26.25).
1044
+
1045
+ `EV_ON_CLOSE` carries `code`, `reason` (masked) and `was_clean`, and so does `EV_ON_OPEN_ERROR` (7.26.24). A session that ends cleanly (a stop, a logout, the server closing with 1000 or 1001) is logged by nobody; a session CUT -- not clean, or any other code (1006, 1008, 1011) -- is one WARNING with the code and the reason masked:
1046
+
1047
+ ```javascript
1048
+ // in the subscriber's EV_ON_CLOSE action
1049
+ if(!kw.was_clean || (kw.code !== 1000 && kw.code !== 1001)) {
1050
+ show_banner(t("connection lost")); // the WARNING is already logged
1051
+ }
1052
+ ```
986
1053
 
987
1054
  **`required_services`** (default `[]`) is the list this link advertises in its
988
1055
  identity_card, which the remote's `C_AUTHZ` uses to authorize commands. It is
@@ -997,7 +1064,9 @@ naming services it does not host.
997
1064
  what breaks lockstep, so N links that dropped together do not stampede a backend
998
1065
  that is just coming back. The backoff resets when a session is actually reached,
999
1066
  and on `mt_start` — a deliberate reconnect never inherits a previous run's
1000
- penalty. For the old fixed-interval behaviour set `timeout_retry_max` equal to
1067
+ penalty. A session the transport closes for a fault of the server (4002) does not
1068
+ count as reached: the backoff goes on from where it was (7.26.25), so a server
1069
+ that breaks every session is not reconnected at the first delay for ever. For the old fixed-interval behaviour set `timeout_retry_max` equal to
1001
1070
  `timeout_retry`.
1002
1071
 
1003
1072
  ---