@genrojs/bag 0.11.1 → 0.12.0

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
@@ -1,6 +1,6 @@
1
1
  # genro-bag-js
2
2
 
3
- Current release: **0.11.1**. Fixes deletion origin propagation in `popNode`, `pop` and `delItem`.
3
+ Current release: **0.12.0**. Resolver inputs are the declared parameters; read-time kwargs are a one-off read. See `BREAKING_CHANGES.md`.
4
4
 
5
5
  > **0.11.0 — breaking API:** read the [ingestion and scheduling migration note](docs/migration-ingestion.md) before upgrading dependent projects.
6
6
 
@@ -162,7 +162,8 @@ Bag does not own a scheduler in either language. Resolver `interval` and
162
162
  `reactive` options and subscription `timer` / `interval` options raise TypeError.
163
163
  `reset()` only invalidates the cache, with no refresh argument. Attachment,
164
164
  attribute changes and TTL expiry do not start background loads. Node attributes
165
- override resolver defaults on the next read; explicit call parameters have the
166
- highest priority. Applications schedule polling and publish results with
165
+ override resolver defaults on the next read, for declared parameters only;
166
+ explicit call parameters have the highest priority and make a one-off read that
167
+ is never cached. Applications schedule polling and publish results with
167
168
  `setItem` when observers need a mutation event. See the
168
169
  [migration note](migration-ingestion.md#no-internal-scheduling).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genrojs/bag",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "JavaScript port of genro-bag - hierarchical data container",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/resolver.js CHANGED
@@ -330,12 +330,33 @@ export class BagResolver {
330
330
  return JSON.stringify(Object.entries(effectiveKw).sort());
331
331
  }
332
332
 
333
+ /**
334
+ * Merge resolver._kw with node attributes for declared parameters only.
335
+ *
336
+ * A node attribute the resolver does not declare (UI metadata such as
337
+ * '__checked') never reaches load() and never changes the fingerprint.
338
+ *
339
+ * @returns {Object} The effective kwargs.
340
+ * @private
341
+ */
342
+ _buildEffectiveKw() {
343
+ const kwargs = { ...this._kw };
344
+ if (this._node && this._node.attr) {
345
+ for (const key of Object.keys(this._kw)) {
346
+ if (Object.hasOwn(this._node.attr, key)) {
347
+ kwargs[key] = this._node.attr[key];
348
+ }
349
+ }
350
+ }
351
+ return kwargs;
352
+ }
353
+
333
354
  /**
334
355
  * Resolve the value. Main entry point.
335
356
  *
336
357
  * @param {Object} options - Resolution options.
337
358
  * @param {boolean} options.static - If true, return cached value without resolving.
338
- * @param {Object} options.kwargs - Additional kwargs merged with resolver's _kw.
359
+ * @param {Object} options.kwargs - Parameters for this read only (one-off, never cached).
339
360
  * @returns {*} The resolved value (or Promise if load() is async).
340
361
  */
341
362
  resolve(options = {}) {
@@ -346,21 +367,18 @@ export class BagResolver {
346
367
  return this._node ? this._node._value : this.cachedValue;
347
368
  }
348
369
 
349
- // Build kwargs: resolver._kw merged with call kwargs
350
- // Node attributes can also contribute (priority: callKwargs > node.attr > _kw)
351
- const kwargs = { ...this._kw };
352
- if (this._node && this._node.attr) {
353
- const internalParams = this.constructor.internalParams;
354
- for (const [key, value] of Object.entries(this._node.attr)) {
355
- if (!internalParams.has(key)) {
356
- kwargs[key] = value;
357
- }
358
- }
370
+ // Node attributes contribute only for declared parameters
371
+ // (priority: callKwargs > node.attr > _kw)
372
+ const kwargs = this._buildEffectiveKw();
373
+
374
+ // Call kwargs make a one-off read: loaded for this call only,
375
+ // never cached, node untouched
376
+ if (Object.keys(callKwargs).length > 0) {
377
+ return this._load({ ...kwargs, ...callKwargs }, value => this._prepareValue(value));
359
378
  }
360
- Object.assign(kwargs, callKwargs);
361
379
 
362
380
  // Fingerprint-based cache invalidation:
363
- // If params changed, force reload even if TTL hasn't expired
381
+ // If declared params changed on the node, force reload even if TTL hasn't expired
364
382
  const currentFingerprint = this._computeEffectiveFingerprint(kwargs);
365
383
  if (currentFingerprint === this._lastEffectiveFingerprint
366
384
  && !this.expired) {
@@ -368,23 +386,32 @@ export class BagResolver {
368
386
  }
369
387
  this._lastEffectiveFingerprint = currentFingerprint;
370
388
 
371
- // Get retry policy
372
- const policy = getRetryPolicy(this);
389
+ return this._load(kwargs, value => this._finalize(value));
390
+ }
373
391
 
374
- // Call load with retry
392
+ /**
393
+ * Call load() with the retry policy, then apply the given completion.
394
+ *
395
+ * @param {Object} kwargs - Parameters for load().
396
+ * @param {Function} complete - Applied to the loaded value.
397
+ * @returns {*} The completed value (or Promise if load() is async).
398
+ * @private
399
+ */
400
+ _load(kwargs, complete) {
401
+ const policy = getRetryPolicy(this);
375
402
  const doLoad = () => this.load(kwargs);
376
403
 
377
404
  if (policy) {
378
405
  // With retry - always async
379
- return withRetry(doLoad, policy).then(value => this._finalize(value));
406
+ return withRetry(doLoad, policy).then(complete);
380
407
  }
381
408
 
382
409
  // Without retry - can be sync or async
383
410
  const result = doLoad();
384
411
  if (result && typeof result.then === 'function') {
385
- return result.then(value => this._finalize(value));
412
+ return result.then(complete);
386
413
  }
387
- return this._finalize(result);
414
+ return complete(result);
388
415
  }
389
416
 
390
417
  /**
@@ -395,6 +422,26 @@ export class BagResolver {
395
422
  * @private
396
423
  */
397
424
  _finalize(value) {
425
+ value = this._prepareValue(value);
426
+
427
+ this._lastUpdate = Date.now();
428
+
429
+ // Cache duration and node storage policy are independent.
430
+ if (!this.readOnly || this.cacheTime !== 0) {
431
+ this.cachedValue = value;
432
+ }
433
+
434
+ return value;
435
+ }
436
+
437
+ /**
438
+ * Apply asBag conversion to a loaded value without storing it.
439
+ *
440
+ * @param {*} value - The loaded value.
441
+ * @returns {*} The value (possibly converted to Bag).
442
+ * @private
443
+ */
444
+ _prepareValue(value) {
398
445
  // asBag conversion:
399
446
  // true → always convert
400
447
  // false → never convert
@@ -411,14 +458,6 @@ export class BagResolver {
411
458
  if (shouldConvert && value !== null && value !== undefined) {
412
459
  value = this._convertToBag(value);
413
460
  }
414
-
415
- this._lastUpdate = Date.now();
416
-
417
- // Cache duration and node storage policy are independent.
418
- if (!this.readOnly || this.cacheTime !== 0) {
419
- this.cachedValue = value;
420
- }
421
-
422
461
  return value;
423
462
  }
424
463
 
@@ -60,8 +60,7 @@ export class UrlResolver extends BagResolver {
60
60
  static classArgs = ['url'];
61
61
 
62
62
  static internalParams = new Set([
63
- 'cacheTime', 'readOnly', 'retryPolicy', 'asBag',
64
- 'url', 'method', 'qs', 'body', 'timeout', 'transport'
63
+ 'cacheTime', 'readOnly', 'retryPolicy', 'asBag'
65
64
  ]);
66
65
 
67
66
  /**
@@ -87,12 +86,12 @@ export class UrlResolver extends BagResolver {
87
86
  * @returns {Promise<*>} Response data.
88
87
  */
89
88
  async load(kwargs) {
90
- let url = this._kw.url;
91
- const method = this._kw.method;
92
- const qs = this._kw.qs;
93
- const timeout = this._kw.timeout;
94
- const transport = this._kw.transport;
95
- let body = this._kw.body;
89
+ let url = kwargs.url;
90
+ const method = kwargs.method;
91
+ const qs = kwargs.qs;
92
+ const timeout = kwargs.timeout;
93
+ const transport = kwargs.transport;
94
+ let body = kwargs.body;
96
95
 
97
96
  // _body overrides constructor body
98
97
  if (kwargs._body !== undefined) {