jsgar 4.13.8 → 4.14.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.
Files changed (3) hide show
  1. package/dist/gar.umd.js +39 -10
  2. package/gar.js +39 -10
  3. package/package.json +1 -1
package/dist/gar.umd.js CHANGED
@@ -212,7 +212,10 @@
212
212
  }
213
213
  this.unique_key_and_record_updates = uniqueKeyAndRecordUpdates;
214
214
  this.logEveryMessage = logEveryMessage;
215
- this.version = 650710;
215
+ this.version = 650711;
216
+ // Monotonic correlation id stamped inline on each compare-exchange (required protocol field); the
217
+ // result echoes it. Correlation no longer rides the active_ownership marker — see compare_exchange.md.
218
+ this._casMessageReference = 0;
216
219
  this.uuid = GARClient._generateUUID();
217
220
 
218
221
  if (typeof window !== 'undefined' && window.location) {
@@ -229,6 +232,10 @@
229
232
  this.serverKeyNameToId = new Map();
230
233
  this.localTopicMap = new Map();
231
234
  this.localKeyMap = new Map();
235
+ // Classes each locally-introduced key has been introduced under, so a later
236
+ // getAndPossiblyIntroduceKeyId with a NEW class sends an additive KeyIntroduction (the server's
237
+ // class_list is additive) rather than silently no-op'ing. Keyed by key name -> Set<className>.
238
+ this.localKeyClasses = new Map();
232
239
  this.invalidatedKeyIds = new Set();
233
240
  this.recordMap = new Map();
234
241
  this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, require_existing: false, client_key_id: 0 };
@@ -296,6 +303,7 @@
296
303
  this.localKeyCounter = 1;
297
304
  this.localTopicMap.clear();
298
305
  this.localKeyMap.clear();
306
+ this.localKeyClasses.clear();
299
307
  this.invalidatedKeyIds.clear();
300
308
 
301
309
  // Active ownership state
@@ -1504,15 +1512,30 @@
1504
1512
  if (existingId !== undefined && this.invalidatedKeyIds.has(existingId)) {
1505
1513
  this.invalidatedKeyIds.delete(existingId);
1506
1514
  this.localKeyMap.delete(name);
1515
+ this.localKeyClasses.delete(name);
1507
1516
  }
1517
+ const newClasses = typeof className === 'string'
1518
+ ? className.split(/\s+/).filter(Boolean)
1519
+ : (Array.isArray(className) ? className.filter(Boolean) : []);
1508
1520
  if (!this.localKeyMap.has(name)) {
1509
- if (!className) throw new Error(`getAndPossiblyIntroduceKeyId: className required to introduce key "${name}"`);
1521
+ if (!newClasses.length) throw new Error(`getAndPossiblyIntroduceKeyId: className required to introduce key "${name}"`);
1510
1522
  const keyId = this.localKeyCounter++;
1511
1523
  this.localKeyMap.set(name, keyId);
1512
- const value = { key_id: keyId, name };
1513
- if (typeof className === 'string') value.class_list = className.split(/\s+/).filter(Boolean);
1514
- else if (Array.isArray(className)) value.class_list = className;
1515
- this.sendMessage({ message_type: 'KeyIntroduction', value });
1524
+ this.localKeyClasses.set(name, new Set(newClasses));
1525
+ this.sendMessage({ message_type: 'KeyIntroduction', value: { key_id: keyId, name, class_list: newClasses } });
1526
+ } else {
1527
+ // Already introduced: add any classes the key isn't yet a member of via an additive
1528
+ // KeyIntroduction (same key_id, only the new classes — the server's class_list adds to the
1529
+ // existing membership). Without this, a key first introduced under one class never gains a
1530
+ // second class on a later call, and a write/CAS on a topic of the new class is rejected
1531
+ // server-side ("topic X does not apply to key of class ...").
1532
+ let known = this.localKeyClasses.get(name);
1533
+ if (!known) { known = new Set(); this.localKeyClasses.set(name, known); }
1534
+ const added = newClasses.filter(c => !known.has(c));
1535
+ if (added.length) {
1536
+ added.forEach(c => known.add(c));
1537
+ this.sendMessage({ message_type: 'KeyIntroduction', value: { key_id: this.localKeyMap.get(name), name, class_list: added } });
1538
+ }
1516
1539
  }
1517
1540
  return this.localKeyMap.get(name);
1518
1541
  }
@@ -1566,10 +1589,14 @@
1566
1589
  const keyId = this.localKeyMap.get(keyName);
1567
1590
  if (keyId === undefined) return;
1568
1591
  const value = { key_id: keyId, name: keyName, deleted_class: className };
1592
+ const known = this.localKeyClasses.get(keyName);
1593
+ if (known) known.delete(className);
1569
1594
  if (downgradeToClassList) {
1570
- value.class_list = typeof downgradeToClassList === 'string'
1595
+ const downgrade = typeof downgradeToClassList === 'string'
1571
1596
  ? downgradeToClassList.split(/\s+/).filter(Boolean)
1572
- : downgradeToClassList;
1597
+ : downgradeToClassList.filter(Boolean);
1598
+ value.class_list = downgrade;
1599
+ if (known) downgrade.forEach(c => known.add(c));
1573
1600
  }
1574
1601
  this.sendMessage({ message_type: 'KeyIntroduction', value });
1575
1602
  }
@@ -1591,8 +1618,9 @@
1591
1618
  const keyId = this.localKeyMap.get(keyName);
1592
1619
  if (keyId === undefined) return;
1593
1620
  this.publishDeleteKey(keyId);
1594
- // Forget the local id so future re-introduction allocates a new one.
1621
+ // Forget the local id (and its class set) so future re-introduction allocates a new one.
1595
1622
  this.localKeyMap.delete(keyName);
1623
+ this.localKeyClasses.delete(keyName);
1596
1624
  }
1597
1625
 
1598
1626
  /**
@@ -1674,9 +1702,10 @@
1674
1702
  * @param {any} value - New value to set on match
1675
1703
  */
1676
1704
  compareExchangeRecordWithIds(keyId, topicId, test, value) {
1705
+ const msgRef = ++this._casMessageReference;
1677
1706
  this.sendMessage({
1678
1707
  message_type: 'JSONCompareExchange',
1679
- value: { record_id: { key_id: keyId, topic_id: topicId }, test, value }
1708
+ value: { record_id: { key_id: keyId, topic_id: topicId }, msg_ref: msgRef, test, value }
1680
1709
  });
1681
1710
  }
1682
1711
 
package/gar.js CHANGED
@@ -205,7 +205,10 @@ class GARClient {
205
205
  }
206
206
  this.unique_key_and_record_updates = uniqueKeyAndRecordUpdates;
207
207
  this.logEveryMessage = logEveryMessage;
208
- this.version = 650710;
208
+ this.version = 650711;
209
+ // Monotonic correlation id stamped inline on each compare-exchange (required protocol field); the
210
+ // result echoes it. Correlation no longer rides the active_ownership marker — see compare_exchange.md.
211
+ this._casMessageReference = 0;
209
212
  this.uuid = GARClient._generateUUID();
210
213
 
211
214
  if (typeof window !== 'undefined' && window.location) {
@@ -222,6 +225,10 @@ class GARClient {
222
225
  this.serverKeyNameToId = new Map();
223
226
  this.localTopicMap = new Map();
224
227
  this.localKeyMap = new Map();
228
+ // Classes each locally-introduced key has been introduced under, so a later
229
+ // getAndPossiblyIntroduceKeyId with a NEW class sends an additive KeyIntroduction (the server's
230
+ // class_list is additive) rather than silently no-op'ing. Keyed by key name -> Set<className>.
231
+ this.localKeyClasses = new Map();
225
232
  this.invalidatedKeyIds = new Set();
226
233
  this.recordMap = new Map();
227
234
  this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, require_existing: false, client_key_id: 0 };
@@ -289,6 +296,7 @@ class GARClient {
289
296
  this.localKeyCounter = 1;
290
297
  this.localTopicMap.clear();
291
298
  this.localKeyMap.clear();
299
+ this.localKeyClasses.clear();
292
300
  this.invalidatedKeyIds.clear();
293
301
 
294
302
  // Active ownership state
@@ -1497,15 +1505,30 @@ class GARClient {
1497
1505
  if (existingId !== undefined && this.invalidatedKeyIds.has(existingId)) {
1498
1506
  this.invalidatedKeyIds.delete(existingId);
1499
1507
  this.localKeyMap.delete(name);
1508
+ this.localKeyClasses.delete(name);
1500
1509
  }
1510
+ const newClasses = typeof className === 'string'
1511
+ ? className.split(/\s+/).filter(Boolean)
1512
+ : (Array.isArray(className) ? className.filter(Boolean) : []);
1501
1513
  if (!this.localKeyMap.has(name)) {
1502
- if (!className) throw new Error(`getAndPossiblyIntroduceKeyId: className required to introduce key "${name}"`);
1514
+ if (!newClasses.length) throw new Error(`getAndPossiblyIntroduceKeyId: className required to introduce key "${name}"`);
1503
1515
  const keyId = this.localKeyCounter++;
1504
1516
  this.localKeyMap.set(name, keyId);
1505
- const value = { key_id: keyId, name };
1506
- if (typeof className === 'string') value.class_list = className.split(/\s+/).filter(Boolean);
1507
- else if (Array.isArray(className)) value.class_list = className;
1508
- this.sendMessage({ message_type: 'KeyIntroduction', value });
1517
+ this.localKeyClasses.set(name, new Set(newClasses));
1518
+ this.sendMessage({ message_type: 'KeyIntroduction', value: { key_id: keyId, name, class_list: newClasses } });
1519
+ } else {
1520
+ // Already introduced: add any classes the key isn't yet a member of via an additive
1521
+ // KeyIntroduction (same key_id, only the new classes — the server's class_list adds to the
1522
+ // existing membership). Without this, a key first introduced under one class never gains a
1523
+ // second class on a later call, and a write/CAS on a topic of the new class is rejected
1524
+ // server-side ("topic X does not apply to key of class ...").
1525
+ let known = this.localKeyClasses.get(name);
1526
+ if (!known) { known = new Set(); this.localKeyClasses.set(name, known); }
1527
+ const added = newClasses.filter(c => !known.has(c));
1528
+ if (added.length) {
1529
+ added.forEach(c => known.add(c));
1530
+ this.sendMessage({ message_type: 'KeyIntroduction', value: { key_id: this.localKeyMap.get(name), name, class_list: added } });
1531
+ }
1509
1532
  }
1510
1533
  return this.localKeyMap.get(name);
1511
1534
  }
@@ -1559,10 +1582,14 @@ class GARClient {
1559
1582
  const keyId = this.localKeyMap.get(keyName);
1560
1583
  if (keyId === undefined) return;
1561
1584
  const value = { key_id: keyId, name: keyName, deleted_class: className };
1585
+ const known = this.localKeyClasses.get(keyName);
1586
+ if (known) known.delete(className);
1562
1587
  if (downgradeToClassList) {
1563
- value.class_list = typeof downgradeToClassList === 'string'
1588
+ const downgrade = typeof downgradeToClassList === 'string'
1564
1589
  ? downgradeToClassList.split(/\s+/).filter(Boolean)
1565
- : downgradeToClassList;
1590
+ : downgradeToClassList.filter(Boolean);
1591
+ value.class_list = downgrade;
1592
+ if (known) downgrade.forEach(c => known.add(c));
1566
1593
  }
1567
1594
  this.sendMessage({ message_type: 'KeyIntroduction', value });
1568
1595
  }
@@ -1584,8 +1611,9 @@ class GARClient {
1584
1611
  const keyId = this.localKeyMap.get(keyName);
1585
1612
  if (keyId === undefined) return;
1586
1613
  this.publishDeleteKey(keyId);
1587
- // Forget the local id so future re-introduction allocates a new one.
1614
+ // Forget the local id (and its class set) so future re-introduction allocates a new one.
1588
1615
  this.localKeyMap.delete(keyName);
1616
+ this.localKeyClasses.delete(keyName);
1589
1617
  }
1590
1618
 
1591
1619
  /**
@@ -1667,9 +1695,10 @@ class GARClient {
1667
1695
  * @param {any} value - New value to set on match
1668
1696
  */
1669
1697
  compareExchangeRecordWithIds(keyId, topicId, test, value) {
1698
+ const msgRef = ++this._casMessageReference;
1670
1699
  this.sendMessage({
1671
1700
  message_type: 'JSONCompareExchange',
1672
- value: { record_id: { key_id: keyId, topic_id: topicId }, test, value }
1701
+ value: { record_id: { key_id: keyId, topic_id: topicId }, msg_ref: msgRef, test, value }
1673
1702
  });
1674
1703
  }
1675
1704
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgar",
3
- "version": "4.13.8",
3
+ "version": "4.14.0",
4
4
  "description": "A Javascript client for the GAR protocol",
5
5
  "type": "module",
6
6
  "main": "dist/gar.umd.js",