jsgar 4.13.2 → 4.13.4

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 +61 -20
  2. package/gar.js +61 -20
  3. package/package.json +1 -1
package/dist/gar.umd.js CHANGED
@@ -1179,7 +1179,9 @@
1179
1179
  * @param {Object} opts - Named subscription options (fields below).
1180
1180
  * @param {string} opts.name - Subscription name
1181
1181
  * @param {string} [opts.subscriptionMode='Streaming'] - Subscription mode
1182
- * @param {string|Array<string>|null} [keyName=null] - Key name(s)
1182
+ * @param {string|Array<string>|Array<number>|null} [keyName=null] - Key name(s), matched
1183
+ * exactly via a synthesized key_filter alternation (keys are NOT created by
1184
+ * subscribing); an array of integers passes through as the wire key_id_list
1183
1185
  * @param {string|Array<string>|null} [topicName=null] - Topic name(s)
1184
1186
  * @param {string|Array<string>|null} [className=null] - Class name(s)
1185
1187
  * @param {string|null} [keyFilter=null] - Key filter regex (cannot use with keyName)
@@ -1258,16 +1260,24 @@
1258
1260
  referencingClasses = referencingClassList;
1259
1261
  }
1260
1262
 
1261
- let singleClass = Array.isArray(classList) && classList.length === 1 ? classList[0] : null;
1262
-
1263
- // Convert keyName to array
1263
+ // Convert keyName to array. Subscribing must NOT create keys: names are no longer
1264
+ // introduced to build a key_id_list (the old behavior materialized every named key on
1265
+ // the server). Names become an exact-match alternation key_filter instead — the server
1266
+ // recognizes the literal-alternation shape and builds a trie, skipping the full
1267
+ // regex->DFA compilation. An array of integers passes through as key_id_list verbatim
1268
+ // (the caller already holds real key ids).
1264
1269
  let keyNames = [];
1265
1270
  if (typeof keyName === 'string') {
1266
1271
  keyNames = [keyName];
1267
1272
  } else if (Array.isArray(keyName)) {
1268
1273
  keyNames = keyName;
1269
1274
  }
1270
- const keyIdList = keyNames.map(x => this.getAndPossiblyIntroduceKeyId(x, singleClass));
1275
+ let keyIdList = [];
1276
+ if (keyNames.length > 0 && keyNames.every(Number.isInteger)) {
1277
+ keyIdList = keyNames;
1278
+ } else if (keyNames.length > 0) {
1279
+ keyFilter = keyNames.map(GARClient.escapeRegex).join('|');
1280
+ }
1271
1281
 
1272
1282
  // Convert topicName to array
1273
1283
  let topicNames = [];
@@ -1541,27 +1551,48 @@
1541
1551
  }
1542
1552
 
1543
1553
  /**
1544
- * Delete a key by name if it exists in the localKeyMap. Safe to call multiple times.
1554
+ * Remove a single class from a key by sending a KeyIntroduction with deleted_class set, if the
1555
+ * key exists in the localKeyMap. Safe to call multiple times. The key may still exist on the
1556
+ * server in other classes, so the localKeyMap entry is preserved. Careful: every record that
1557
+ * applied to className but to no remaining class is deleted.
1545
1558
  * @param {string} keyName - Key name
1546
- * @param {string|null} [className=null] - If set, removes the key from this class only via
1547
- * a KeyIntroduction with deleted_class set; the key may still exist on the server in
1548
- * other classes and the localKeyMap entry is preserved. If unset, sends DeleteKey to
1549
- * remove the key entirely and forgets the local id so a future
1550
- * getAndPossiblyIntroduceKeyId(keyName) re-introduces with a fresh id.
1559
+ * @param {string} className - Class to remove from the key
1560
+ * @param {string|Array<string>|null} [downgradeToClassList=null] - If set, populates
1561
+ * key_introduction::class_list in the same message the classes the key should apply to
1562
+ * alongside the removal. A single key_introduction may both add classes (class_list) and
1563
+ * remove one (deleted_class).
1551
1564
  */
1552
- deleteKey(keyName, className = null) {
1565
+ deleteClass(keyName, className, downgradeToClassList = null) {
1553
1566
  const keyId = this.localKeyMap.get(keyName);
1554
1567
  if (keyId === undefined) return;
1568
+ const value = { key_id: keyId, name: keyName, deleted_class: className };
1569
+ if (downgradeToClassList) {
1570
+ value.class_list = typeof downgradeToClassList === 'string'
1571
+ ? downgradeToClassList.split(/\s+/).filter(Boolean)
1572
+ : downgradeToClassList;
1573
+ }
1574
+ this.sendMessage({ message_type: 'KeyIntroduction', value });
1575
+ }
1576
+
1577
+ /**
1578
+ * Delete a key by name if it exists in the localKeyMap. Safe to call multiple times.
1579
+ * @param {string} keyName - Key name
1580
+ * @param {string|null} [className=null] - If set, delegates to deleteClass (KeyIntroduction
1581
+ * with deleted_class set); the key may still exist on the server in other classes and the
1582
+ * localKeyMap entry is preserved. If unset, sends DeleteKey to remove the key entirely and
1583
+ * forgets the local id so a future getAndPossiblyIntroduceKeyId(keyName) re-introduces with
1584
+ * a fresh id.
1585
+ */
1586
+ deleteKey(keyName, className = null) {
1555
1587
  if (className) {
1556
- this.sendMessage({
1557
- message_type: 'KeyIntroduction',
1558
- value: { key_id: keyId, name: keyName, deleted_class: className },
1559
- });
1560
- } else {
1561
- this.publishDeleteKey(keyId);
1562
- // Forget the local id so future re-introduction allocates a new one.
1563
- this.localKeyMap.delete(keyName);
1588
+ this.deleteClass(keyName, className);
1589
+ return;
1564
1590
  }
1591
+ const keyId = this.localKeyMap.get(keyName);
1592
+ if (keyId === undefined) return;
1593
+ this.publishDeleteKey(keyId);
1594
+ // Forget the local id so future re-introduction allocates a new one.
1595
+ this.localKeyMap.delete(keyName);
1565
1596
  }
1566
1597
 
1567
1598
  /**
@@ -1849,6 +1880,16 @@
1849
1880
  });
1850
1881
  }
1851
1882
 
1883
+ /**
1884
+ * Escape TRS-regex metacharacters so `name` matches literally (TRS regexes match the
1885
+ * whole string, so an escaped name is an exact-match alternative in a key_filter).
1886
+ * @param {string} name
1887
+ * @returns {string}
1888
+ */
1889
+ static escapeRegex(name) {
1890
+ return String(name).replace(/[\\.^$|?*+()[\]{}]/g, '\\$&');
1891
+ }
1892
+
1852
1893
  static _generateUUID() {
1853
1894
  const bytes = new Uint8Array(16);
1854
1895
  if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues)
package/gar.js CHANGED
@@ -1172,7 +1172,9 @@ class GARClient {
1172
1172
  * @param {Object} opts - Named subscription options (fields below).
1173
1173
  * @param {string} opts.name - Subscription name
1174
1174
  * @param {string} [opts.subscriptionMode='Streaming'] - Subscription mode
1175
- * @param {string|Array<string>|null} [keyName=null] - Key name(s)
1175
+ * @param {string|Array<string>|Array<number>|null} [keyName=null] - Key name(s), matched
1176
+ * exactly via a synthesized key_filter alternation (keys are NOT created by
1177
+ * subscribing); an array of integers passes through as the wire key_id_list
1176
1178
  * @param {string|Array<string>|null} [topicName=null] - Topic name(s)
1177
1179
  * @param {string|Array<string>|null} [className=null] - Class name(s)
1178
1180
  * @param {string|null} [keyFilter=null] - Key filter regex (cannot use with keyName)
@@ -1251,16 +1253,24 @@ class GARClient {
1251
1253
  referencingClasses = referencingClassList;
1252
1254
  }
1253
1255
 
1254
- let singleClass = Array.isArray(classList) && classList.length === 1 ? classList[0] : null;
1255
-
1256
- // Convert keyName to array
1256
+ // Convert keyName to array. Subscribing must NOT create keys: names are no longer
1257
+ // introduced to build a key_id_list (the old behavior materialized every named key on
1258
+ // the server). Names become an exact-match alternation key_filter instead — the server
1259
+ // recognizes the literal-alternation shape and builds a trie, skipping the full
1260
+ // regex->DFA compilation. An array of integers passes through as key_id_list verbatim
1261
+ // (the caller already holds real key ids).
1257
1262
  let keyNames = [];
1258
1263
  if (typeof keyName === 'string') {
1259
1264
  keyNames = [keyName];
1260
1265
  } else if (Array.isArray(keyName)) {
1261
1266
  keyNames = keyName;
1262
1267
  }
1263
- const keyIdList = keyNames.map(x => this.getAndPossiblyIntroduceKeyId(x, singleClass));
1268
+ let keyIdList = [];
1269
+ if (keyNames.length > 0 && keyNames.every(Number.isInteger)) {
1270
+ keyIdList = keyNames;
1271
+ } else if (keyNames.length > 0) {
1272
+ keyFilter = keyNames.map(GARClient.escapeRegex).join('|');
1273
+ }
1264
1274
 
1265
1275
  // Convert topicName to array
1266
1276
  let topicNames = [];
@@ -1534,27 +1544,48 @@ class GARClient {
1534
1544
  }
1535
1545
 
1536
1546
  /**
1537
- * Delete a key by name if it exists in the localKeyMap. Safe to call multiple times.
1547
+ * Remove a single class from a key by sending a KeyIntroduction with deleted_class set, if the
1548
+ * key exists in the localKeyMap. Safe to call multiple times. The key may still exist on the
1549
+ * server in other classes, so the localKeyMap entry is preserved. Careful: every record that
1550
+ * applied to className but to no remaining class is deleted.
1538
1551
  * @param {string} keyName - Key name
1539
- * @param {string|null} [className=null] - If set, removes the key from this class only via
1540
- * a KeyIntroduction with deleted_class set; the key may still exist on the server in
1541
- * other classes and the localKeyMap entry is preserved. If unset, sends DeleteKey to
1542
- * remove the key entirely and forgets the local id so a future
1543
- * getAndPossiblyIntroduceKeyId(keyName) re-introduces with a fresh id.
1552
+ * @param {string} className - Class to remove from the key
1553
+ * @param {string|Array<string>|null} [downgradeToClassList=null] - If set, populates
1554
+ * key_introduction::class_list in the same message the classes the key should apply to
1555
+ * alongside the removal. A single key_introduction may both add classes (class_list) and
1556
+ * remove one (deleted_class).
1544
1557
  */
1545
- deleteKey(keyName, className = null) {
1558
+ deleteClass(keyName, className, downgradeToClassList = null) {
1546
1559
  const keyId = this.localKeyMap.get(keyName);
1547
1560
  if (keyId === undefined) return;
1561
+ const value = { key_id: keyId, name: keyName, deleted_class: className };
1562
+ if (downgradeToClassList) {
1563
+ value.class_list = typeof downgradeToClassList === 'string'
1564
+ ? downgradeToClassList.split(/\s+/).filter(Boolean)
1565
+ : downgradeToClassList;
1566
+ }
1567
+ this.sendMessage({ message_type: 'KeyIntroduction', value });
1568
+ }
1569
+
1570
+ /**
1571
+ * Delete a key by name if it exists in the localKeyMap. Safe to call multiple times.
1572
+ * @param {string} keyName - Key name
1573
+ * @param {string|null} [className=null] - If set, delegates to deleteClass (KeyIntroduction
1574
+ * with deleted_class set); the key may still exist on the server in other classes and the
1575
+ * localKeyMap entry is preserved. If unset, sends DeleteKey to remove the key entirely and
1576
+ * forgets the local id so a future getAndPossiblyIntroduceKeyId(keyName) re-introduces with
1577
+ * a fresh id.
1578
+ */
1579
+ deleteKey(keyName, className = null) {
1548
1580
  if (className) {
1549
- this.sendMessage({
1550
- message_type: 'KeyIntroduction',
1551
- value: { key_id: keyId, name: keyName, deleted_class: className },
1552
- });
1553
- } else {
1554
- this.publishDeleteKey(keyId);
1555
- // Forget the local id so future re-introduction allocates a new one.
1556
- this.localKeyMap.delete(keyName);
1581
+ this.deleteClass(keyName, className);
1582
+ return;
1557
1583
  }
1584
+ const keyId = this.localKeyMap.get(keyName);
1585
+ if (keyId === undefined) return;
1586
+ this.publishDeleteKey(keyId);
1587
+ // Forget the local id so future re-introduction allocates a new one.
1588
+ this.localKeyMap.delete(keyName);
1558
1589
  }
1559
1590
 
1560
1591
  /**
@@ -1842,6 +1873,16 @@ class GARClient {
1842
1873
  });
1843
1874
  }
1844
1875
 
1876
+ /**
1877
+ * Escape TRS-regex metacharacters so `name` matches literally (TRS regexes match the
1878
+ * whole string, so an escaped name is an exact-match alternative in a key_filter).
1879
+ * @param {string} name
1880
+ * @returns {string}
1881
+ */
1882
+ static escapeRegex(name) {
1883
+ return String(name).replace(/[\\.^$|?*+()[\]{}]/g, '\\$&');
1884
+ }
1885
+
1845
1886
  static _generateUUID() {
1846
1887
  const bytes = new Uint8Array(16);
1847
1888
  if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgar",
3
- "version": "4.13.2",
3
+ "version": "4.13.4",
4
4
  "description": "A Javascript client for the GAR protocol",
5
5
  "type": "module",
6
6
  "main": "dist/gar.umd.js",