@yuneta/gobj-js 7.26.21 → 7.26.23

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
@@ -282,7 +282,9 @@ const attrs_table = [
282
282
 
283
283
  **Data types (`data_type_t`):** `DTP_STRING`, `DTP_BOOLEAN`, `DTP_INTEGER`, `DTP_REAL`, `DTP_LIST`, `DTP_DICT`, `DTP_JSON`, `DTP_POINTER`
284
284
 
285
- **Flags (`sdata_flag_t`):** `SDF_RD`, `SDF_WR`, `SDF_PERSIST`, `SDF_STATS`, `SDF_AUTHZ_R`, `SDF_AUTHZ_W`, `SDF_REQUIRED`, `SDF_VOLATIL`
285
+ **Flags (`sdata_flag_t`):** `SDF_RD`, `SDF_WR`, `SDF_PERSIST`, `SDF_STATS`, `SDF_AUTHZ_R`, `SDF_AUTHZ_W`, `SDF_REQUIRED`, `SDF_VOLATIL`, `SDF_SECRET`
286
+
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"`.
286
288
 
287
289
  ### Events & Pub-Sub
288
290
 
@@ -415,6 +417,45 @@ gobj_is_volatil(gobj) // → boolean
415
417
  gobj_is_pure_child(gobj) // → boolean
416
418
  ```
417
419
 
420
+ **A gobj is not destroyed under code that still runs on it** (7.26.22+, as the
421
+ C kernel since 7.26.10). `gobj_publish_event()` and `gobj_send_event()` run
422
+ synchronously, and the code of the publisher (or of the outer action) goes on
423
+ when they return. So `gobj_destroy()` logs an ERROR with the stack when it
424
+ destroys the publisher of a publication in progress, or one of its ancestors —
425
+ *"Destroying a gobj while its tree is publishing"* — or a gobj whose outer
426
+ action is still running — *"Destroying a gobj while one of its actions is
427
+ still running"*. The line names `destroying`, `publisher`/`running`, `event`,
428
+ `destroyer` and `inner_event`. The destroy is done all the same. A gobj that
429
+ destroys itself in its own action, and a host that destroys its transport in
430
+ the transport's `EV_STOPPED`, are not errors.
431
+
432
+ The fix is to leave the stack: post an event to yourself, and destroy in its
433
+ action. The event is an INPUT event of your gclass (FSM and `event_types`):
434
+
435
+ ```javascript
436
+ // Wrong: EV_TIMEOUT comes from the C_TIMER child, inside its publication.
437
+ function ac_timeout(gobj, event, kw, src) {
438
+ gobj_stop(src);
439
+ gobj_destroy(src);
440
+ return 0;
441
+ }
442
+
443
+ // Right: the destroy runs on the next turn, from a clean stack.
444
+ function ac_timeout(gobj, event, kw, src) {
445
+ gobj_post_event(gobj, "EV_TIMER_DONE", {timer: gobj_name(src)}, gobj);
446
+ return 0;
447
+ }
448
+ function ac_timer_done(gobj, event, kw, src) {
449
+ let timer = gobj.priv.timer; // resolve the identity the kw carries
450
+ if(timer && gobj_name(timer) === kw.timer) {
451
+ gobj_stop(timer);
452
+ gobj_destroy(timer);
453
+ gobj.priv.timer = null;
454
+ }
455
+ return 0;
456
+ }
457
+ ```
458
+
418
459
  ### State Machine
419
460
 
420
461
  ```javascript
@@ -678,6 +719,7 @@ mask_secrets_inline("curl -H 'Authorization: Bearer abc' u")
678
719
  mask_secrets_inline("sent Bearer mF_9.B5f-4.1JqM") // → "sent Bearer ********"
679
720
  mask_secrets_inline("the bearer of this letter") // → null: prose
680
721
  mask_secrets_inline("\"password\": hunter2") // → "\"password\": ********"
722
+ mask_secrets_inline("GET https://h/get?token=abc&x=1") // → "GET https://h/get?token=********&x=1"
681
723
  json_mask_secrets({user: "a", password: "x"}) // → {user: "a", password: "********"}
682
724
  json_mask_secrets({note: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4In0.c2ln"})
683
725
  // → {note: "********"}: a JWT under any key
@@ -692,9 +734,13 @@ one a cap cut), and after `Bearer` or `Basic` the word that can be a credential
692
734
  -- quoted, after the scheme doubled, or not a word of prose (a digit, a symbol,
693
735
  a character that is no ASCII, its case mixed, 24 characters or more; for
694
736
  `Bearer` 8 at least, for `Basic` a run of base64 that decodes to a
695
- `user:password`). Each rule judges the text as it came, and a run of
696
- characters to mask is one `********`: nothing that the masking of 7.26.9 hid is
697
- shown, but the word after a bare `Bearer` that cannot be a credential. A key
737
+ `user:password`). The name of a `name=value` is also the run of name
738
+ characters before the `=`, as in a query string (`https://h/get?token=x`). A
739
+ name is judged by its last 128 bytes of UTF-8 and its case folded in ASCII, as
740
+ C judges it. Each rule judges the text as it came, and a run of characters to
741
+ mask is one `********`: nothing that 7.26.19 or 7.26.20 masked is shown, but
742
+ the word after a bare `Bearer` that cannot be a credential, and a name that C
743
+ judges otherwise (over 128 bytes, or with a letter that is no ASCII). A key
698
744
  is judged as a name, never as a value: a key that is itself a credential (a
699
745
  dict keyed by tokens, `{"eyJ...": 1}`) is shown as it is. A NUL in a string is
700
746
  read as a blank; linear and bounded (a text over 4 MB is not shown at all).
@@ -910,7 +956,11 @@ set_timeout_periodic(timer_gobj, 1000);
910
956
  clear_timeout(timer_gobj);
911
957
  ```
912
958
 
913
- Events published: `EV_TIMEOUT`, `EV_TIMEOUT_PERIODIC`
959
+ Events published: `EV_TIMEOUT`, `EV_TIMEOUT_PERIODIC`. They go to the
960
+ `subscriber` attr, else, for a pure child, to its parent, which the timer
961
+ subscribes in `mt_create` — the C timer's rule. Up to 7.26.21 a pure child
962
+ SENT the timeout to its parent instead of publishing it (and to its parent
963
+ even when it had a `subscriber`).
914
964
 
915
965
  Attributes: `subscriber`, `periodic`, `msec`
916
966
 
@@ -932,6 +982,8 @@ let remote = gobj_create_service("backend", "C_IEVENT_CLI", {
932
982
 
933
983
  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`
934
984
 
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`.
986
+
935
987
  **`required_services`** (default `[]`) is the list this link advertises in its
936
988
  identity_card, which the remote's `C_AUTHZ` uses to authorize commands. It is
937
989
  **per link**: empty falls back to the yuno's own `required_services`, so a