@yuneta/gobj-js 7.26.27 → 7.26.29

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
@@ -460,7 +460,10 @@ gobj_create_volatil(name, gclass_name, attrs, parent)
460
460
  gobj_create_pure_child(name, gclass_name, attrs, parent)
461
461
 
462
462
  // Start / Stop
463
- gobj_start(gobj) // calls mt_start
463
+ gobj_start(gobj) // calls mt_start; -1 with an ERROR if a
464
+ // SDF_REQUIRED attr is empty, as in C
465
+ // (BREAKING in 7.26.28: "" string, empty_json()
466
+ // list/dict/json, null pointer)
464
467
  gobj_stop(gobj) // calls mt_stop
465
468
  gobj_start_children(gobj)
466
469
  gobj_stop_children(gobj)
@@ -599,6 +602,9 @@ gobj_search_path(gobj, path)
599
602
 
600
603
  gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_data2)
601
604
  gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, user_data2)
605
+ // A callback may destroy or move (gobj_change_parent()) any gobj: a
606
+ // destroyed one is never handed, a moved one is handed where the walk
607
+ // finds it now, and no gobj is handed twice in one walk (7.26.28).
602
608
  ```
603
609
 
604
610
  ### Persistence
@@ -740,11 +746,16 @@ kw_remove_local_storage_value(key)
740
746
  // kw_set_local_storage_value("k", () => 1) → -1
741
747
  // kw_set_local_storage_value("k", {w: NaN}) → -1 (it was {"w":null})
742
748
  // kw_set_local_storage_value("k", [1, , 3]) → -1 (a hole: [1,null,3])
749
+ // A full store (read, not written) with `create`: the default, warned
750
+ // once per key (7.26.28: an ERROR on every read).
743
751
 
744
752
  // Misc utilities
745
753
  current_timestamp() // ISO string
746
754
  get_now() // Date object
747
- node_uuid() // generate UUID
755
+ node_uuid() // uuid of this browser, the SAME for the
756
+ // life of the page (kept in localStorage
757
+ // "host_uuid"; a page uuid if the store
758
+ // cannot keep it; never null: 7.26.28)
748
759
  parseBoolean(v) // coerce to boolean
749
760
  empty_string(s) // true if null/undefined/""
750
761
  str_in_list(list, str, ci) // case-insensitive optional
@@ -1106,7 +1117,8 @@ count as reached: the backoff goes on from where it was (7.26.25), so a server
1106
1117
  that breaks every session is not reconnected at the first delay for ever -- unless
1107
1118
  the session outlived `timeout_retry_max`: a session that long was served, and its
1108
1119
  fault starts the backoff over, as a clean end does (7.26.26). For the old fixed-interval behaviour set `timeout_retry_max` equal to
1109
- `timeout_retry`. The first delay is **at least 1000 ms** (7.26.27): a lower
1120
+ `timeout_retry`. The BASE of the first delay is **at least 1000 ms** (7.26.27;
1121
+ the ±20% jitter puts the first delay itself between 800 and 1200 ms): a lower
1110
1122
  `timeout_retry` is raised to 1000 with one WARNING, because 0 never doubled
1111
1123
  away from 0 and a server that closes before the ack was reconnected in a loop
1112
1124
  with no wait, hundreds of times a second.
@@ -1949,6 +1949,31 @@ function gobj_destroy_children(gobj) {
1949
1949
  }
1950
1950
  }
1951
1951
  /************************************************************
1952
+ * The SDF_REQUIRED attributes with no value, or null when there
1953
+ * is none: what is empty is asked type by type, as in C.
1954
+ ************************************************************/
1955
+ function gobj_check_required_attrs(gobj) {
1956
+ const hs = gobj_hsdata(gobj);
1957
+ const missing = [];
1958
+ if (!is_array(gobj.gclass.attrs_table)) return null;
1959
+ for (const it of gobj.gclass.attrs_table) {
1960
+ if (!it.name || !(it.flag & sdata_flag_t.SDF_REQUIRED)) continue;
1961
+ const value = hs[it.name];
1962
+ switch (it.type) {
1963
+ case data_type_t.DTP_STRING:
1964
+ if (!is_string(value) || empty_string(value)) missing.push(it.name);
1965
+ break;
1966
+ case data_type_t.DTP_LIST:
1967
+ case data_type_t.DTP_DICT:
1968
+ case data_type_t.DTP_JSON:
1969
+ if (empty_json(value)) missing.push(it.name);
1970
+ break;
1971
+ case data_type_t.DTP_POINTER: if (!value) missing.push(it.name);
1972
+ }
1973
+ }
1974
+ return missing.length ? missing : null;
1975
+ }
1976
+ /************************************************************
1952
1977
  *
1953
1978
  ************************************************************/
1954
1979
  function gobj_start(gobj) {
@@ -1964,6 +1989,11 @@ function gobj_start(gobj) {
1964
1989
  log_error(`${gobj_short_name(gobj)}: GObj DISABLED`);
1965
1990
  return -1;
1966
1991
  }
1992
+ const required_attrs = gobj_check_required_attrs(gobj);
1993
+ if (required_attrs) {
1994
+ log_error(`${gobj_short_name(gobj)}: Cannot start without all required attributes: ` + JSON.stringify(required_attrs));
1995
+ return -1;
1996
+ }
1967
1997
  if (__trace_gobj_start_stop__(gobj)) trace_machine(sprintf("⏺ ⏺ start: %s", gobj_full_name(gobj)));
1968
1998
  gobj.running = true;
1969
1999
  let ret = 0;
@@ -2121,7 +2151,8 @@ function gobj_play(gobj) {
2121
2151
  if (!gobj_is_running(gobj)) {
2122
2152
  if (!(gobj.gclass.gclass_flag & gclass_flag_t.gcflag_required_start_to_play)) {
2123
2153
  log_warning(`${gobj_short_name(gobj)}: GObj playing without previous start`);
2124
- gobj_start(gobj);
2154
+ if (start_destroys_it(gobj)) return -1;
2155
+ if (!gobj.running) return -1;
2125
2156
  } else {
2126
2157
  log_error(`${gobj_short_name(gobj)}: Cannot play, start not done`);
2127
2158
  return -1;
@@ -2350,7 +2381,7 @@ function gobj_walk_gobj_children(gobj, walk_type, cb_walking, user_data, user_da
2350
2381
  log_error(`gobj NULL, destroying, or bad type`);
2351
2382
  return 0;
2352
2383
  }
2353
- return rc_walk_by_list(gobj.dl_children, walk_type, cb_walking, user_data, user_data2);
2384
+ return rc_walk_by_list(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
2354
2385
  }
2355
2386
  /***************************************************************************
2356
2387
  *
@@ -2360,7 +2391,7 @@ function gobj_walk_gobj_children_tree(gobj, walk_type, cb_walking, user_data, us
2360
2391
  log_error(`gobj NULL, destroying, or bad type`);
2361
2392
  return 0;
2362
2393
  }
2363
- return rc_walk_by_tree(gobj.dl_children, walk_type, cb_walking, user_data, user_data2);
2394
+ return rc_walk_by_tree(gobj, /* @__PURE__ */ new Set(), walk_type, cb_walking, user_data, user_data2);
2364
2395
  }
2365
2396
  /***************************************************************
2366
2397
  * Walking
@@ -2387,18 +2418,33 @@ function walk_ret(ret) {
2387
2418
  log_error(`walk callback must return a NUMBER (-1 stop, 0 descend, >0 skip children), got ${typeof ret}: taken as 0`);
2388
2419
  return 0;
2389
2420
  }
2390
- function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2391
- let iter = [...iter_];
2421
+ /***************************************************************
2422
+ * Whether a walk steps over this gobj of the list of `owner`.
2423
+ *
2424
+ * Each list is walked over a COPY of it, because a callback may destroy
2425
+ * any gobj of the list, the current one included, or move one with
2426
+ * gobj_change_parent(). One it destroyed is stepped over, as the C walk
2427
+ * does; one it moved is no longer in this list, and is handed where the
2428
+ * walk finds it now (7.26.27 handed it here AND there); and a gobj is
2429
+ * never handed twice in one walk (`seen`), wherever it was moved.
2430
+ ***************************************************************/
2431
+ function walk_steps_over(child, owner, seen) {
2432
+ return gobj_is_destroying(child) || child.parent !== owner || seen.has(child);
2433
+ }
2434
+ function rc_walk_by_list(owner, seen, walk_type, cb_walking, user_data, user_data2) {
2435
+ let iter = [...owner.dl_children];
2392
2436
  let child;
2393
2437
  if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2394
2438
  child = iter[i - 1];
2395
- if (gobj_is_destroying(child)) continue;
2439
+ if (walk_steps_over(child, owner, seen)) continue;
2440
+ seen.add(child);
2396
2441
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2397
2442
  if (ret < 0) return ret;
2398
2443
  }
2399
2444
  else for (let i = 0; i < iter.length; i++) {
2400
2445
  child = iter[i];
2401
- if (gobj_is_destroying(child)) continue;
2446
+ if (walk_steps_over(child, owner, seen)) continue;
2447
+ seen.add(child);
2402
2448
  let ret = walk_ret(cb_walking(child, user_data, user_data2));
2403
2449
  if (ret < 0) return ret;
2404
2450
  }
@@ -2413,24 +2459,23 @@ function rc_walk_by_list(iter_, walk_type, cb_walking, user_data, user_data2) {
2413
2459
  * or
2414
2460
  * WALK_LAST2FIRST
2415
2461
  ***************************************************************/
2416
- function rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2) {
2417
- let iter = [...iter_];
2418
- let ret = rc_walk_by_list(iter, walk_type, cb_walking, user_data, user_data2);
2419
- if (ret < 0) return ret;
2420
- let child;
2421
- if (walk_type & walk_type_t.WALK_LAST2FIRST) for (let i = iter.length; i > 0; i--) {
2422
- child = iter[i - 1];
2423
- if (gobj_is_destroying(child)) continue;
2424
- ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2462
+ function rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2) {
2463
+ let iter = [...owner.dl_children];
2464
+ if (walk_type & walk_type_t.WALK_LAST2FIRST) iter.reverse();
2465
+ let handed = [];
2466
+ for (const child of iter) {
2467
+ if (walk_steps_over(child, owner, seen)) continue;
2468
+ seen.add(child);
2469
+ handed.push(child);
2470
+ let ret = walk_ret(cb_walking(child, user_data, user_data2));
2425
2471
  if (ret < 0) return ret;
2426
2472
  }
2427
- else for (let i = 0; i < iter.length; i++) {
2428
- child = iter[i];
2473
+ for (const child of handed) {
2429
2474
  if (gobj_is_destroying(child)) continue;
2430
- ret = rc_walk_by_level(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2475
+ let ret = rc_walk_by_level(child, seen, walk_type, cb_walking, user_data, user_data2);
2431
2476
  if (ret < 0) return ret;
2432
2477
  }
2433
- return ret;
2478
+ return 0;
2434
2479
  }
2435
2480
  /***************************************************************
2436
2481
  * Walking
@@ -2447,27 +2492,29 @@ function rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2) {
2447
2492
  * or
2448
2493
  * WALK_LAST2FIRST
2449
2494
  ***************************************************************/
2450
- function rc_walk_by_tree(iter_, walk_type, cb_walking, user_data, user_data2) {
2451
- if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(iter_, walk_type, cb_walking, user_data, user_data2);
2452
- let iter = [...iter_];
2495
+ function rc_walk_by_tree(owner, seen, walk_type, cb_walking, user_data, user_data2) {
2496
+ if (walk_type & walk_type_t.WALK_BYLEVEL) return rc_walk_by_level(owner, seen, walk_type, cb_walking, user_data, user_data2);
2497
+ let iter = [...owner.dl_children];
2453
2498
  let ret;
2454
2499
  let child;
2455
2500
  if (walk_type & walk_type_t.WALK_BOTTOM2TOP) for (let i = iter.length; i > 0; i--) {
2456
2501
  child = iter[i - 1];
2457
- if (gobj_is_destroying(child)) continue;
2458
- ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2502
+ if (walk_steps_over(child, owner, seen)) continue;
2503
+ ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
2459
2504
  if (ret < 0) return ret;
2460
2505
  if (gobj_is_destroying(child)) continue;
2506
+ seen.add(child);
2461
2507
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2462
2508
  if (ret < 0) return ret;
2463
2509
  }
2464
2510
  else for (let i = 0; i < iter.length; i++) {
2465
2511
  child = iter[i];
2466
- if (gobj_is_destroying(child)) continue;
2512
+ if (walk_steps_over(child, owner, seen)) continue;
2513
+ seen.add(child);
2467
2514
  ret = walk_ret(cb_walking(child, user_data, user_data2));
2468
2515
  if (ret < 0) return ret;
2469
2516
  else if (ret === 0 && !gobj_is_destroying(child)) {
2470
- ret = rc_walk_by_tree(child.dl_children, walk_type, cb_walking, user_data, user_data2);
2517
+ ret = rc_walk_by_tree(child, seen, walk_type, cb_walking, user_data, user_data2);
2471
2518
  if (ret < 0) return ret;
2472
2519
  }
2473
2520
  }
@@ -2930,12 +2977,20 @@ function gobj_current_state(gobj) {
2930
2977
  function gobj_change_parent(gobj, parent) {
2931
2978
  if (gobj_is_destroying(gobj)) {
2932
2979
  log_error("gobj NULL or DESTROYED");
2933
- return null;
2980
+ return -1;
2934
2981
  }
2935
2982
  if (!(parent instanceof GObj)) {
2936
2983
  log_error(`${gobj_short_name(gobj)}: gobj_change_parent() BAD TYPE of parent`);
2937
2984
  return -1;
2938
2985
  }
2986
+ if (gobj_is_destroying(parent)) {
2987
+ log_error(`${gobj_short_name(gobj)}: gobj_change_parent() parent NULL or DESTROYED`);
2988
+ return -1;
2989
+ }
2990
+ for (let p = parent; p; p = p.parent) if (p === gobj) {
2991
+ log_error(`${gobj_short_name(gobj)}: gobj_change_parent() a gobj cannot be its own ancestor: ${gobj_short_name(parent)}`);
2992
+ return -1;
2993
+ }
2939
2994
  if (gobj.parent && gobj.parent.gclass.gmt.mt_child_removed) gobj.parent.gclass.gmt.mt_child_removed(gobj.parent, gobj);
2940
2995
  if (gobj.parent) {
2941
2996
  _remove_child(gobj.parent, gobj);
@@ -6100,7 +6155,10 @@ function warn_local_storage_once(key, msg) {
6100
6155
  * for ever; a new corrupt entry is a new WARNING. Its text is never
6101
6156
  * shown (JSON.parse() quotes it in its error, and an entry can hold a
6102
6157
  * token). A store that cannot be read (blocked) is warned once per
6103
- * key, and the default is NOT written there. Outside a browser: the
6158
+ * key, and the default is NOT written there; one that cannot be
6159
+ * written (full) is warned once per key too: with `create` it was an
6160
+ * ERROR on every read, the default tried again each time. A key is
6161
+ * warned again after a read that goes well. Outside a browser: the
6104
6162
  * default.
6105
6163
  ********************************************/
6106
6164
  function kw_get_local_storage_value(key, default_value, create = false) {
@@ -6114,6 +6172,7 @@ function kw_get_local_storage_value(key, default_value, create = false) {
6114
6172
  warn_local_storage_once(key, `${where} cannot be read (storage blocked?): the default is used`);
6115
6173
  return default_value;
6116
6174
  }
6175
+ let went_well = true;
6117
6176
  let value = null;
6118
6177
  if (text !== null) try {
6119
6178
  value = JSON.parse(text);
@@ -6122,17 +6181,25 @@ function kw_get_local_storage_value(key, default_value, create = false) {
6122
6181
  try {
6123
6182
  store.removeItem(key);
6124
6183
  log_warning(`${where} does not hold json: removed, the default is used`);
6125
- local_storage_warned.delete(key);
6126
6184
  } catch (e2) {
6185
+ went_well = false;
6127
6186
  warn_local_storage_once(key, `${where} does not hold json and cannot be removed: the default is used`);
6128
6187
  }
6129
- text = void 0;
6130
6188
  }
6131
- if (text !== void 0) local_storage_warned.delete(key);
6132
6189
  if (value === null) {
6133
- if (create) kw_set_local_storage_value(key, default_value);
6134
- return default_value;
6190
+ value = default_value;
6191
+ if (create) {
6192
+ const default_text = json_text_of(default_value);
6193
+ if (default_text === void 0) log_error(`${where}: cannot write the default: a value with no json text`);
6194
+ else try {
6195
+ store.setItem(key, default_text);
6196
+ } catch (e) {
6197
+ went_well = false;
6198
+ warn_local_storage_once(key, `${where} cannot be written (storage full or blocked?): the default is used, not kept`);
6199
+ }
6200
+ }
6135
6201
  }
6202
+ if (went_well) local_storage_warned.delete(key);
6136
6203
  return value;
6137
6204
  }
6138
6205
  /********************************************
@@ -7372,29 +7439,68 @@ function index_in_list(list, elm) {
7372
7439
  return -1;
7373
7440
  }
7374
7441
  /************************************************************
7442
+ * A new uuid v4 (RFC 9562).
7443
+ *
7444
+ * crypto.randomUUID() exists only in a secure context, so a page
7445
+ * served over http without TLS has none; crypto.getRandomValues()
7446
+ * exists there too. With no crypto at all, Math.random(): not fit
7447
+ * for a secret, and a uuid that names a node is not one.
7448
+ ************************************************************/
7449
+ function new_uuid() {
7450
+ const c = typeof window !== "undefined" && window.crypto || globalThis.crypto;
7451
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
7452
+ const b = /* @__PURE__ */ new Uint8Array(16);
7453
+ if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
7454
+ else for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256);
7455
+ b[6] = b[6] & 15 | 64;
7456
+ b[8] = b[8] & 63 | 128;
7457
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
7458
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
7459
+ }
7460
+ /************************************************************
7461
+ * The uuid of this node: ONE for the life of the page.
7375
7462
  *
7463
+ * The one the store keeps ("host_uuid"), made and written there when
7464
+ * there is none. When the store cannot keep it (blocked, full, no
7465
+ * localStorage, outside a browser) the one this page made, warned
7466
+ * once. It goes in the node_uuid attribute of the yuno, the id of the
7467
+ * identity card and the `host` of every message the client sends, so
7468
+ * the first answer is kept and answered again: 7.26.27 made a new
7469
+ * uuid on every call when the store could not keep it, and answered
7470
+ * null without crypto.randomUUID(). A stored value that is not a
7471
+ * uuid (a foreign or corrupt write) is replaced, with a WARNING.
7376
7472
  ************************************************************/
7473
+ var uuid_of_this_page = null;
7474
+ var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7377
7475
  function node_uuid() {
7378
- let uuid = "";
7476
+ if (uuid_of_this_page) return uuid_of_this_page;
7379
7477
  const store = local_store();
7380
- if (store) {
7478
+ if (!store) {
7479
+ uuid_of_this_page = new_uuid();
7480
+ return uuid_of_this_page;
7481
+ }
7482
+ let uuid = null;
7483
+ try {
7484
+ uuid = store.getItem("host_uuid");
7485
+ } catch (e) {
7486
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7487
+ uuid_of_this_page = new_uuid();
7488
+ return uuid_of_this_page;
7489
+ }
7490
+ if (uuid && !UUID_SHAPE.test(uuid)) {
7491
+ log_warning("node_uuid(): the stored host_uuid is not a uuid: a new one replaces it");
7492
+ uuid = null;
7493
+ }
7494
+ if (!uuid) {
7495
+ uuid = new_uuid();
7381
7496
  try {
7382
- uuid = store.getItem("host_uuid");
7497
+ store.setItem("host_uuid", uuid);
7383
7498
  } catch (e) {
7384
- uuid = null;
7385
- warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be read (storage blocked?): a uuid of this page only");
7386
- }
7387
- if (!uuid && window.crypto?.randomUUID) {
7388
- uuid = window.crypto.randomUUID();
7389
- try {
7390
- store.setItem("host_uuid", uuid);
7391
- } catch (e) {
7392
- warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7393
- }
7499
+ warn_local_storage_once("host_uuid", "node_uuid(): localStorage cannot be written (storage full or blocked?): a uuid of this page only");
7394
7500
  }
7395
- } else if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) uuid = globalThis.crypto.randomUUID();
7396
- else uuid = "00000000-0000-0000-0000-000000000000";
7397
- return uuid;
7501
+ }
7502
+ uuid_of_this_page = uuid;
7503
+ return uuid_of_this_page;
7398
7504
  }
7399
7505
  /************************************************************
7400
7506
  * Build a cleaned name
@@ -8679,7 +8785,7 @@ var attrs_table = [
8679
8785
  SDATA(data_type_t.DTP_STRING, "cert_pem", sdata_flag_t.SDF_PERSIST, "", "SSL server certification, PEM str format"),
8680
8786
  SDATA(data_type_t.DTP_JSON, "extra_info", sdata_flag_t.SDF_RD, "{}", "dict data set by user, added to the identity card msg."),
8681
8787
  SDATA(data_type_t.DTP_LIST, "required_services", sdata_flag_t.SDF_RD, "[]", "Services this LINK requires (identity card). Empty = the yuno's `required_services`, as before. A yuno with several links to DIFFERENT backends must set it per link: the yuno-wide list is the union, so every backend is told the service names of all the others."),
8682
- SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "First reconnect delay (ms), at least 1000. It BACKS OFF from here, doubling up to timeout_retry_max, and resets when a session is reached"),
8788
+ SDATA(data_type_t.DTP_INTEGER, "timeout_retry", sdata_flag_t.SDF_RD, "5000", "Base of the first reconnect delay (ms, ±20% jitter), at least 1000. It BACKS OFF from here, doubling up to timeout_retry_max, and resets when a session is reached"),
8683
8789
  SDATA(data_type_t.DTP_INTEGER, "timeout_retry_max", sdata_flag_t.SDF_RD, "60000", "Cap of the reconnect backoff (ms). Set it equal to timeout_retry for the old fixed-interval behaviour"),
8684
8790
  SDATA(data_type_t.DTP_INTEGER, "timeout_idack", sdata_flag_t.SDF_RD, "5000", "timeout waiting idAck"),
8685
8791
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, 0, "subscriber of output-events. If null then subscriber is the parent"),