memcache 1.4.0 → 1.5.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
@@ -14,7 +14,6 @@ Nodejs Memcache Client
14
14
  - [Getting Started](#getting-started)
15
15
  - [Installation](#installation)
16
16
  - [Basic Usage](#basic-usage)
17
- - [Custom Connection](#custom-connection)
18
17
  - [API](#api)
19
18
  - [Constructor](#constructor)
20
19
  - [Properties](#properties)
@@ -47,6 +46,7 @@ Nodejs Memcache Client
47
46
  - [Distribution Algorithms](#distribution-algorithms)
48
47
  - [KetamaHash (Default)](#ketamahash-default)
49
48
  - [ModulaHash](#modulahash)
49
+ - [BroadcastHash](#broadcasthash)
50
50
  - [Choosing an Algorithm](#choosing-an-algorithm)
51
51
  - [Retry Configuration](#retry-configuration)
52
52
  - [Basic Retry Setup](#basic-retry-setup)
@@ -65,6 +65,7 @@ Nodejs Memcache Client
65
65
  - [Auto Discovery Events](#auto-discovery-events)
66
66
  - [Legacy Command Support](#legacy-command-support)
67
67
  - [IPv6 Support](#ipv6-support)
68
+ - [Benchmarks](#benchmarks)
68
69
  - [Contributing](#contributing)
69
70
  - [License and Copyright](#license-and-copyright)
70
71
 
@@ -583,15 +584,39 @@ const client = new Memcache({
583
584
  // server2 will receive approximately 25% of keys
584
585
  ```
585
586
 
587
+ ## BroadcastHash
588
+
589
+ BroadcastHash sends every operation to all nodes in the cluster. Instead of partitioning keys across nodes, every `getNodesByKey()` call returns all nodes, so reads and writes are broadcast to every server.
590
+
591
+ ```javascript
592
+ import { Memcache, BroadcastHash } from 'memcache';
593
+
594
+ // Use BroadcastHash for full replication
595
+ const client = new Memcache({
596
+ nodes: ['server1:11211', 'server2:11211', 'server3:11211'],
597
+ hash: new BroadcastHash()
598
+ });
599
+
600
+ // Every set/get/delete hits all three nodes
601
+ await client.set('mykey', 'Hello!');
602
+ ```
603
+
604
+ **Characteristics:**
605
+ - Every operation targets all nodes
606
+ - No key partitioning — all nodes hold the same data
607
+ - Reads return the first successful result from any node
608
+ - Writes succeed only if all nodes succeed
609
+ - Best for replication, broadcast invalidation, or small clusters where all nodes should be in sync
610
+
586
611
  ## Choosing an Algorithm
587
612
 
588
- | Feature | KetamaHash | ModulaHash |
589
- |---------|------------|------------|
590
- | Key redistribution on node change | Minimal (~1/n keys) | All keys may move |
591
- | Complexity | Higher (virtual nodes) | Lower (simple modulo) |
592
- | Performance | Slightly slower | Faster |
593
- | Best for | Dynamic scaling | Fixed clusters |
594
- | Weighted nodes | Yes | Yes |
613
+ | Feature | KetamaHash | ModulaHash | BroadcastHash |
614
+ |---------|------------|------------|---------------|
615
+ | Key redistribution on node change | Minimal (~1/n keys) | All keys may move | N/A (all nodes always) |
616
+ | Complexity | Higher (virtual nodes) | Lower (simple modulo) | Simplest |
617
+ | Performance | Slightly slower | Faster | Depends on node count |
618
+ | Best for | Dynamic scaling | Fixed clusters | Replication |
619
+ | Weighted nodes | Yes | Yes | No |
595
620
 
596
621
  **Use KetamaHash (default) when:**
597
622
  - Your cluster size may change dynamically
@@ -603,6 +628,11 @@ const client = new Memcache({
603
628
  - You prefer simplicity over minimal redistribution
604
629
  - You're in a testing or development environment
605
630
 
631
+ **Use BroadcastHash when:**
632
+ - You want all nodes to hold the same data
633
+ - You need broadcast cache invalidation across all nodes
634
+ - You're running a small cluster where replication is more important than partitioning
635
+
606
636
  # Retry Configuration
607
637
 
608
638
  The Memcache client supports automatic retry of failed commands with configurable backoff strategies.
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AutoDiscovery: () => AutoDiscovery,
24
+ BroadcastHash: () => BroadcastHash,
24
25
  Memcache: () => Memcache,
25
26
  MemcacheEvents: () => MemcacheEvents,
26
27
  MemcacheNode: () => MemcacheNode,
@@ -317,7 +318,7 @@ var MemcacheNode = class extends import_hookified.Hookified {
317
318
  _authenticated = false;
318
319
  _binaryBuffer = Buffer.alloc(0);
319
320
  constructor(host, port, options) {
320
- super();
321
+ super({ throwOnEmptyListeners: false });
321
322
  this._host = host;
322
323
  this._port = port;
323
324
  this._timeout = options?.timeout || 5e3;
@@ -946,7 +947,7 @@ var AutoDiscovery = class _AutoDiscovery extends import_hookified2.Hookified {
946
947
  _isRunning = false;
947
948
  _isPolling = false;
948
949
  constructor(options) {
949
- super();
950
+ super({ throwOnEmptyListeners: false });
950
951
  this._configEndpoint = options.configEndpoint;
951
952
  this._pollingInterval = options.pollingInterval;
952
953
  this._useLegacyCommand = options.useLegacyCommand;
@@ -1160,6 +1161,67 @@ var AutoDiscovery = class _AutoDiscovery extends import_hookified2.Hookified {
1160
1161
  }
1161
1162
  };
1162
1163
 
1164
+ // src/broadcast.ts
1165
+ var BroadcastHash = class {
1166
+ /** The name of this distribution strategy */
1167
+ name = "broadcast";
1168
+ /** Map of node IDs to MemcacheNode instances */
1169
+ nodeMap;
1170
+ /** Cached array of nodes, rebuilt only on add/remove */
1171
+ nodeCache;
1172
+ constructor() {
1173
+ this.nodeMap = /* @__PURE__ */ new Map();
1174
+ this.nodeCache = [];
1175
+ }
1176
+ /**
1177
+ * Gets all nodes in the distribution.
1178
+ * @returns Array of all MemcacheNode instances
1179
+ */
1180
+ get nodes() {
1181
+ return [...this.nodeCache];
1182
+ }
1183
+ /**
1184
+ * Adds a node to the distribution.
1185
+ * @param node - The MemcacheNode to add
1186
+ */
1187
+ addNode(node) {
1188
+ this.nodeMap.set(node.id, node);
1189
+ this.rebuildCache();
1190
+ }
1191
+ /**
1192
+ * Removes a node from the distribution by its ID.
1193
+ * @param id - The node ID (e.g., "localhost:11211")
1194
+ */
1195
+ removeNode(id) {
1196
+ if (this.nodeMap.delete(id)) {
1197
+ this.rebuildCache();
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Gets a specific node by its ID.
1202
+ * @param id - The node ID (e.g., "localhost:11211")
1203
+ * @returns The MemcacheNode if found, undefined otherwise
1204
+ */
1205
+ getNode(id) {
1206
+ return this.nodeMap.get(id);
1207
+ }
1208
+ /**
1209
+ * Returns all nodes regardless of key. Every operation is broadcast
1210
+ * to every node in the cluster.
1211
+ * @param _key - The cache key (ignored — all nodes are always returned)
1212
+ * @returns Array of all MemcacheNode instances
1213
+ */
1214
+ getNodesByKey(_key) {
1215
+ return [...this.nodeCache];
1216
+ }
1217
+ /**
1218
+ * Rebuilds the cached node array from the map.
1219
+ */
1220
+ rebuildCache() {
1221
+ this.nodeCache = [...this.nodeMap.values()];
1222
+ }
1223
+ };
1224
+
1163
1225
  // src/ketama.ts
1164
1226
  var import_node_crypto = require("crypto");
1165
1227
  var hashFunctionForBuiltin = (algorithm) => (value) => (0, import_node_crypto.createHash)(algorithm).update(value).digest().readInt32BE();
@@ -1636,7 +1698,7 @@ var Memcache = class extends import_hookified3.Hookified {
1636
1698
  _autoDiscovery;
1637
1699
  _autoDiscoverOptions;
1638
1700
  constructor(options) {
1639
- super();
1701
+ super({ throwOnEmptyListeners: false });
1640
1702
  if (typeof options === "string") {
1641
1703
  this._hash = new KetamaHash();
1642
1704
  this._timeout = 5e3;
@@ -2590,6 +2652,7 @@ var index_default = Memcache;
2590
2652
  // Annotate the CommonJS export names for ESM import in node:
2591
2653
  0 && (module.exports = {
2592
2654
  AutoDiscovery,
2655
+ BroadcastHash,
2593
2656
  Memcache,
2594
2657
  MemcacheEvents,
2595
2658
  MemcacheNode,
package/dist/index.d.cts CHANGED
@@ -476,6 +476,65 @@ declare class AutoDiscovery extends Hookified {
476
476
  private parseEndpoint;
477
477
  }
478
478
 
479
+ /**
480
+ * A distribution hash implementation that sends every key to all nodes.
481
+ * Unlike KetamaHash or ModulaHash, this does not partition keys — every
482
+ * operation targets every node in the cluster.
483
+ *
484
+ * This is useful for replication scenarios where all nodes should hold
485
+ * the same data, or for broadcast operations like flush/delete.
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * const client = new Memcache({
490
+ * nodes: ['server1:11211', 'server2:11211'],
491
+ * hash: new BroadcastHash(),
492
+ * });
493
+ * // Every set/get/delete will hit all nodes
494
+ * ```
495
+ */
496
+ declare class BroadcastHash implements HashProvider {
497
+ /** The name of this distribution strategy */
498
+ readonly name = "broadcast";
499
+ /** Map of node IDs to MemcacheNode instances */
500
+ private nodeMap;
501
+ /** Cached array of nodes, rebuilt only on add/remove */
502
+ private nodeCache;
503
+ constructor();
504
+ /**
505
+ * Gets all nodes in the distribution.
506
+ * @returns Array of all MemcacheNode instances
507
+ */
508
+ get nodes(): Array<MemcacheNode>;
509
+ /**
510
+ * Adds a node to the distribution.
511
+ * @param node - The MemcacheNode to add
512
+ */
513
+ addNode(node: MemcacheNode): void;
514
+ /**
515
+ * Removes a node from the distribution by its ID.
516
+ * @param id - The node ID (e.g., "localhost:11211")
517
+ */
518
+ removeNode(id: string): void;
519
+ /**
520
+ * Gets a specific node by its ID.
521
+ * @param id - The node ID (e.g., "localhost:11211")
522
+ * @returns The MemcacheNode if found, undefined otherwise
523
+ */
524
+ getNode(id: string): MemcacheNode | undefined;
525
+ /**
526
+ * Returns all nodes regardless of key. Every operation is broadcast
527
+ * to every node in the cluster.
528
+ * @param _key - The cache key (ignored — all nodes are always returned)
529
+ * @returns Array of all MemcacheNode instances
530
+ */
531
+ getNodesByKey(_key: string): Array<MemcacheNode>;
532
+ /**
533
+ * Rebuilds the cached node array from the map.
534
+ */
535
+ private rebuildCache;
536
+ }
537
+
479
538
  /**
480
539
  * Function that returns an unsigned 32-bit hash of the input.
481
540
  */
@@ -996,4 +1055,4 @@ declare class Memcache extends Hookified {
996
1055
  private applyClusterConfig;
997
1056
  }
998
1057
 
999
- export { type AutoDiscoverOptions, AutoDiscovery, type ClusterConfig, type DiscoveredNode, type ExecuteOptions, type HashProvider, Memcache, MemcacheEvents, MemcacheNode, type MemcacheOptions, type MemcacheStats, ModulaHash, type RetryBackoffFunction, type SASLCredentials, createNode, Memcache as default, defaultRetryBackoff, exponentialRetryBackoff };
1058
+ export { type AutoDiscoverOptions, AutoDiscovery, BroadcastHash, type ClusterConfig, type DiscoveredNode, type ExecuteOptions, type HashProvider, Memcache, MemcacheEvents, MemcacheNode, type MemcacheOptions, type MemcacheStats, ModulaHash, type RetryBackoffFunction, type SASLCredentials, createNode, Memcache as default, defaultRetryBackoff, exponentialRetryBackoff };
package/dist/index.d.ts CHANGED
@@ -476,6 +476,65 @@ declare class AutoDiscovery extends Hookified {
476
476
  private parseEndpoint;
477
477
  }
478
478
 
479
+ /**
480
+ * A distribution hash implementation that sends every key to all nodes.
481
+ * Unlike KetamaHash or ModulaHash, this does not partition keys — every
482
+ * operation targets every node in the cluster.
483
+ *
484
+ * This is useful for replication scenarios where all nodes should hold
485
+ * the same data, or for broadcast operations like flush/delete.
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * const client = new Memcache({
490
+ * nodes: ['server1:11211', 'server2:11211'],
491
+ * hash: new BroadcastHash(),
492
+ * });
493
+ * // Every set/get/delete will hit all nodes
494
+ * ```
495
+ */
496
+ declare class BroadcastHash implements HashProvider {
497
+ /** The name of this distribution strategy */
498
+ readonly name = "broadcast";
499
+ /** Map of node IDs to MemcacheNode instances */
500
+ private nodeMap;
501
+ /** Cached array of nodes, rebuilt only on add/remove */
502
+ private nodeCache;
503
+ constructor();
504
+ /**
505
+ * Gets all nodes in the distribution.
506
+ * @returns Array of all MemcacheNode instances
507
+ */
508
+ get nodes(): Array<MemcacheNode>;
509
+ /**
510
+ * Adds a node to the distribution.
511
+ * @param node - The MemcacheNode to add
512
+ */
513
+ addNode(node: MemcacheNode): void;
514
+ /**
515
+ * Removes a node from the distribution by its ID.
516
+ * @param id - The node ID (e.g., "localhost:11211")
517
+ */
518
+ removeNode(id: string): void;
519
+ /**
520
+ * Gets a specific node by its ID.
521
+ * @param id - The node ID (e.g., "localhost:11211")
522
+ * @returns The MemcacheNode if found, undefined otherwise
523
+ */
524
+ getNode(id: string): MemcacheNode | undefined;
525
+ /**
526
+ * Returns all nodes regardless of key. Every operation is broadcast
527
+ * to every node in the cluster.
528
+ * @param _key - The cache key (ignored — all nodes are always returned)
529
+ * @returns Array of all MemcacheNode instances
530
+ */
531
+ getNodesByKey(_key: string): Array<MemcacheNode>;
532
+ /**
533
+ * Rebuilds the cached node array from the map.
534
+ */
535
+ private rebuildCache;
536
+ }
537
+
479
538
  /**
480
539
  * Function that returns an unsigned 32-bit hash of the input.
481
540
  */
@@ -996,4 +1055,4 @@ declare class Memcache extends Hookified {
996
1055
  private applyClusterConfig;
997
1056
  }
998
1057
 
999
- export { type AutoDiscoverOptions, AutoDiscovery, type ClusterConfig, type DiscoveredNode, type ExecuteOptions, type HashProvider, Memcache, MemcacheEvents, MemcacheNode, type MemcacheOptions, type MemcacheStats, ModulaHash, type RetryBackoffFunction, type SASLCredentials, createNode, Memcache as default, defaultRetryBackoff, exponentialRetryBackoff };
1058
+ export { type AutoDiscoverOptions, AutoDiscovery, BroadcastHash, type ClusterConfig, type DiscoveredNode, type ExecuteOptions, type HashProvider, Memcache, MemcacheEvents, MemcacheNode, type MemcacheOptions, type MemcacheStats, ModulaHash, type RetryBackoffFunction, type SASLCredentials, createNode, Memcache as default, defaultRetryBackoff, exponentialRetryBackoff };
package/dist/index.js CHANGED
@@ -285,7 +285,7 @@ var MemcacheNode = class extends Hookified {
285
285
  _authenticated = false;
286
286
  _binaryBuffer = Buffer.alloc(0);
287
287
  constructor(host, port, options) {
288
- super();
288
+ super({ throwOnEmptyListeners: false });
289
289
  this._host = host;
290
290
  this._port = port;
291
291
  this._timeout = options?.timeout || 5e3;
@@ -914,7 +914,7 @@ var AutoDiscovery = class _AutoDiscovery extends Hookified2 {
914
914
  _isRunning = false;
915
915
  _isPolling = false;
916
916
  constructor(options) {
917
- super();
917
+ super({ throwOnEmptyListeners: false });
918
918
  this._configEndpoint = options.configEndpoint;
919
919
  this._pollingInterval = options.pollingInterval;
920
920
  this._useLegacyCommand = options.useLegacyCommand;
@@ -1128,6 +1128,67 @@ var AutoDiscovery = class _AutoDiscovery extends Hookified2 {
1128
1128
  }
1129
1129
  };
1130
1130
 
1131
+ // src/broadcast.ts
1132
+ var BroadcastHash = class {
1133
+ /** The name of this distribution strategy */
1134
+ name = "broadcast";
1135
+ /** Map of node IDs to MemcacheNode instances */
1136
+ nodeMap;
1137
+ /** Cached array of nodes, rebuilt only on add/remove */
1138
+ nodeCache;
1139
+ constructor() {
1140
+ this.nodeMap = /* @__PURE__ */ new Map();
1141
+ this.nodeCache = [];
1142
+ }
1143
+ /**
1144
+ * Gets all nodes in the distribution.
1145
+ * @returns Array of all MemcacheNode instances
1146
+ */
1147
+ get nodes() {
1148
+ return [...this.nodeCache];
1149
+ }
1150
+ /**
1151
+ * Adds a node to the distribution.
1152
+ * @param node - The MemcacheNode to add
1153
+ */
1154
+ addNode(node) {
1155
+ this.nodeMap.set(node.id, node);
1156
+ this.rebuildCache();
1157
+ }
1158
+ /**
1159
+ * Removes a node from the distribution by its ID.
1160
+ * @param id - The node ID (e.g., "localhost:11211")
1161
+ */
1162
+ removeNode(id) {
1163
+ if (this.nodeMap.delete(id)) {
1164
+ this.rebuildCache();
1165
+ }
1166
+ }
1167
+ /**
1168
+ * Gets a specific node by its ID.
1169
+ * @param id - The node ID (e.g., "localhost:11211")
1170
+ * @returns The MemcacheNode if found, undefined otherwise
1171
+ */
1172
+ getNode(id) {
1173
+ return this.nodeMap.get(id);
1174
+ }
1175
+ /**
1176
+ * Returns all nodes regardless of key. Every operation is broadcast
1177
+ * to every node in the cluster.
1178
+ * @param _key - The cache key (ignored — all nodes are always returned)
1179
+ * @returns Array of all MemcacheNode instances
1180
+ */
1181
+ getNodesByKey(_key) {
1182
+ return [...this.nodeCache];
1183
+ }
1184
+ /**
1185
+ * Rebuilds the cached node array from the map.
1186
+ */
1187
+ rebuildCache() {
1188
+ this.nodeCache = [...this.nodeMap.values()];
1189
+ }
1190
+ };
1191
+
1131
1192
  // src/ketama.ts
1132
1193
  import { createHash } from "crypto";
1133
1194
  var hashFunctionForBuiltin = (algorithm) => (value) => createHash(algorithm).update(value).digest().readInt32BE();
@@ -1604,7 +1665,7 @@ var Memcache = class extends Hookified3 {
1604
1665
  _autoDiscovery;
1605
1666
  _autoDiscoverOptions;
1606
1667
  constructor(options) {
1607
- super();
1668
+ super({ throwOnEmptyListeners: false });
1608
1669
  if (typeof options === "string") {
1609
1670
  this._hash = new KetamaHash();
1610
1671
  this._timeout = 5e3;
@@ -2557,6 +2618,7 @@ ${valueStr}`;
2557
2618
  var index_default = Memcache;
2558
2619
  export {
2559
2620
  AutoDiscovery,
2621
+ BroadcastHash,
2560
2622
  Memcache,
2561
2623
  MemcacheEvents,
2562
2624
  MemcacheNode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memcache",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Nodejs Memcache Client",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -46,24 +46,24 @@
46
46
  },
47
47
  "homepage": "https://github.com/jaredwray/memcache#readme",
48
48
  "devDependencies": {
49
- "@biomejs/biome": "^2.3.15",
49
+ "@biomejs/biome": "^2.4.7",
50
50
  "@faker-js/faker": "^10.3.0",
51
51
  "@monstermann/tinybench-pretty-printer": "^0.3.0",
52
52
  "@types/memcached": "^2.2.10",
53
53
  "@types/memjs": "^1.3.3",
54
- "@types/node": "^25.2.3",
55
- "@vitest/coverage-v8": "^4.0.18",
54
+ "@types/node": "^25.5.0",
55
+ "@vitest/coverage-v8": "^4.1.0",
56
56
  "memcached": "^2.2.2",
57
57
  "memjs": "^1.3.2",
58
- "rimraf": "^6.1.2",
58
+ "rimraf": "^6.1.3",
59
59
  "tinybench": "^6.0.0",
60
60
  "tsup": "^8.5.1",
61
61
  "tsx": "^4.21.0",
62
62
  "typescript": "^5.9.3",
63
- "vitest": "^4.0.18"
63
+ "vitest": "^4.1.0"
64
64
  },
65
65
  "dependencies": {
66
- "hookified": "^1.15.1"
66
+ "hookified": "^2.1.0"
67
67
  },
68
68
  "files": [
69
69
  "dist",