memcache 1.0.0 → 1.2.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,7 +1,7 @@
1
- [<img src="./site/logo_medium.png" alt="Memcache Logo" align="center">](https://memcachejs.org)
1
+ [<img src="https://jaredwray.com/images/memcache.svg" alt="Memcache Logo" align="center">](https://memcachejs.org)
2
2
 
3
3
  [![codecov](https://codecov.io/gh/jaredwray/memcache/graph/badge.svg?token=4DUANNWiIE)](https://codecov.io/gh/jaredwray/memcache)
4
- [![tests](https://github.com/jaredwray/memcache/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/memcache/actions/workflows/tests.yml)
4
+ [![tests](https://github.com/jaredwray/memcache/actions/workflows/tests.yaml/badge.svg)](https://github.com/jaredwray/memcache/actions/workflows/tests.yaml)
5
5
  [![npm](https://img.shields.io/npm/v/memcache)](https://www.npmjs.com/package/memcache)
6
6
  [![npm](https://img.shields.io/npm/dm/memcache)](https://www.npmjs.com/package/memcache)
7
7
  [![license](https://img.shields.io/github/license/jaredwray/memcache)](https://github.com/jaredwray/memcache/blob/main/LICENSE)
@@ -83,15 +83,93 @@ await client.delete('mykey');
83
83
  await client.quit();
84
84
  ```
85
85
 
86
+ You can also just pass in the `uri` into the constructor
87
+
88
+ ```javascript
89
+ // Single node as string
90
+ const client = new Memcache('localhost:11211');
91
+
92
+ // Single node with protocol
93
+ const client = new Memcache('memcache://192.168.1.100:11211');
94
+
95
+ // Multiple nodes with options
96
+ const client = new Memcache({
97
+ nodes: ['localhost:11211', 'server2:11211'],
98
+ timeout: 10000
99
+ });
100
+ ```
101
+
102
+ You can specify multiple Memcache nodes by passing an array of connection strings:
103
+
104
+ ```javascript
105
+ import { Memcache } from 'memcache';
106
+
107
+ // Create a client with multiple nodes
108
+ const client = new Memcache({
109
+ nodes: ['localhost:11211', '192.168.1.100:11211', 'memcache://192.168.1.101:11211']
110
+ });
111
+
112
+ // Set and get values (automatically distributed across nodes)
113
+ await client.set('mykey', 'Hello, Memcache!');
114
+ const value = await client.get('mykey');
115
+ console.log(value); // ['Hello, Memcache!']
116
+
117
+ // Close the connection
118
+ await client.quit();
119
+ ```
120
+
121
+ You can also pass an array of MemcacheNode instances for advanced configuration:
122
+
123
+ ```javascript
124
+ import { Memcache, createNode } from 'memcache';
125
+
126
+ // Create nodes with custom settings
127
+ const node1 = createNode('localhost', 11211, { weight: 2 });
128
+ const node2 = createNode('192.168.1.100', 11211, { weight: 1 });
129
+ const node3 = createNode('192.168.1.101', 11211, { weight: 1 });
130
+
131
+ // Create a client with MemcacheNode instances
132
+ const client = new Memcache({
133
+ nodes: [node1, node2, node3],
134
+ timeout: 10000
135
+ });
136
+
137
+ // node1 will receive twice as much traffic due to higher weight
138
+ await client.set('mykey', 'Hello, Memcache!');
139
+ const value = await client.get('mykey');
140
+ console.log(value); // ['Hello, Memcache!']
141
+
142
+ // Close the connection
143
+ await client.quit();
144
+ ```
145
+
86
146
  # API
87
147
 
88
148
  ## Constructor
89
149
 
90
150
  ```typescript
91
- new Memcache(options?: MemcacheOptions)
151
+ new Memcache(options?: string | MemcacheOptions)
92
152
  ```
93
153
 
94
- Creates a new Memcache client instance.
154
+ Creates a new Memcache client instance. You can pass either:
155
+ - A **string** representing a single node URI (uses default settings)
156
+ - A **MemcacheOptions** object for custom configuration
157
+
158
+ **Examples:**
159
+
160
+ ```javascript
161
+ // Single node as string
162
+ const client = new Memcache('localhost:11211');
163
+
164
+ // Single node with protocol
165
+ const client = new Memcache('memcache://192.168.1.100:11211');
166
+
167
+ // Multiple nodes with options
168
+ const client = new Memcache({
169
+ nodes: ['localhost:11211', 'server2:11211'],
170
+ timeout: 10000
171
+ });
172
+ ```
95
173
 
96
174
  ### Options
97
175
 
package/dist/index.cjs CHANGED
@@ -702,12 +702,19 @@ var Memcache = class extends import_hookified2.Hookified {
702
702
  constructor(options) {
703
703
  super();
704
704
  this._hash = new KetamaHash();
705
- this._timeout = options?.timeout || 5e3;
706
- this._keepAlive = options?.keepAlive !== false;
707
- this._keepAliveDelay = options?.keepAliveDelay || 1e3;
708
- const nodeUris = options?.nodes || ["localhost:11211"];
709
- for (const nodeUri of nodeUris) {
710
- this.addNode(nodeUri);
705
+ if (typeof options === "string") {
706
+ this._timeout = 5e3;
707
+ this._keepAlive = true;
708
+ this._keepAliveDelay = 1e3;
709
+ this.addNode(options);
710
+ } else {
711
+ this._timeout = options?.timeout || 5e3;
712
+ this._keepAlive = options?.keepAlive !== false;
713
+ this._keepAliveDelay = options?.keepAliveDelay || 1e3;
714
+ const nodeUris = options?.nodes || ["localhost:11211"];
715
+ for (const nodeUri of nodeUris) {
716
+ this.addNode(nodeUri);
717
+ }
711
718
  }
712
719
  }
713
720
  /**
@@ -1056,16 +1063,8 @@ var Memcache = class extends import_hookified2.Hookified {
1056
1063
  const command = `cas ${key} ${flags} ${exptime} ${bytes} ${casToken}\r
1057
1064
  ${valueStr}`;
1058
1065
  const nodes = await this.getNodesByKey(key);
1059
- const promises = nodes.map(async (node) => {
1060
- try {
1061
- const result = await node.command(command);
1062
- return result === "STORED";
1063
- } catch {
1064
- return false;
1065
- }
1066
- });
1067
- const results = await Promise.all(promises);
1068
- const success = results.every((result) => result === true);
1066
+ const results = await this.execute(command, nodes);
1067
+ const success = results.every((result) => result === "STORED");
1069
1068
  await this.afterHook("cas", {
1070
1069
  key,
1071
1070
  value,
@@ -1094,16 +1093,8 @@ ${valueStr}`;
1094
1093
  const command = `set ${key} ${flags} ${exptime} ${bytes}\r
1095
1094
  ${valueStr}`;
1096
1095
  const nodes = await this.getNodesByKey(key);
1097
- const promises = nodes.map(async (node) => {
1098
- try {
1099
- const result = await node.command(command);
1100
- return result === "STORED";
1101
- } catch {
1102
- return false;
1103
- }
1104
- });
1105
- const results = await Promise.all(promises);
1106
- const success = results.every((result) => result === true);
1096
+ const results = await this.execute(command, nodes);
1097
+ const success = results.every((result) => result === "STORED");
1107
1098
  await this.afterHook("set", { key, value, exptime, flags, success });
1108
1099
  return success;
1109
1100
  }
@@ -1125,16 +1116,8 @@ ${valueStr}`;
1125
1116
  const command = `add ${key} ${flags} ${exptime} ${bytes}\r
1126
1117
  ${valueStr}`;
1127
1118
  const nodes = await this.getNodesByKey(key);
1128
- const promises = nodes.map(async (node) => {
1129
- try {
1130
- const result = await node.command(command);
1131
- return result === "STORED";
1132
- } catch {
1133
- return false;
1134
- }
1135
- });
1136
- const results = await Promise.all(promises);
1137
- const success = results.every((result) => result === true);
1119
+ const results = await this.execute(command, nodes);
1120
+ const success = results.every((result) => result === "STORED");
1138
1121
  await this.afterHook("add", { key, value, exptime, flags, success });
1139
1122
  return success;
1140
1123
  }
@@ -1156,16 +1139,8 @@ ${valueStr}`;
1156
1139
  const command = `replace ${key} ${flags} ${exptime} ${bytes}\r
1157
1140
  ${valueStr}`;
1158
1141
  const nodes = await this.getNodesByKey(key);
1159
- const promises = nodes.map(async (node) => {
1160
- try {
1161
- const result = await node.command(command);
1162
- return result === "STORED";
1163
- } catch {
1164
- return false;
1165
- }
1166
- });
1167
- const results = await Promise.all(promises);
1168
- const success = results.every((result) => result === true);
1142
+ const results = await this.execute(command, nodes);
1143
+ const success = results.every((result) => result === "STORED");
1169
1144
  await this.afterHook("replace", { key, value, exptime, flags, success });
1170
1145
  return success;
1171
1146
  }
@@ -1185,16 +1160,8 @@ ${valueStr}`;
1185
1160
  const command = `append ${key} 0 0 ${bytes}\r
1186
1161
  ${valueStr}`;
1187
1162
  const nodes = await this.getNodesByKey(key);
1188
- const promises = nodes.map(async (node) => {
1189
- try {
1190
- const result = await node.command(command);
1191
- return result === "STORED";
1192
- } catch {
1193
- return false;
1194
- }
1195
- });
1196
- const results = await Promise.all(promises);
1197
- const success = results.every((result) => result === true);
1163
+ const results = await this.execute(command, nodes);
1164
+ const success = results.every((result) => result === "STORED");
1198
1165
  await this.afterHook("append", { key, value, success });
1199
1166
  return success;
1200
1167
  }
@@ -1214,16 +1181,8 @@ ${valueStr}`;
1214
1181
  const command = `prepend ${key} 0 0 ${bytes}\r
1215
1182
  ${valueStr}`;
1216
1183
  const nodes = await this.getNodesByKey(key);
1217
- const promises = nodes.map(async (node) => {
1218
- try {
1219
- const result = await node.command(command);
1220
- return result === "STORED";
1221
- } catch {
1222
- return false;
1223
- }
1224
- });
1225
- const results = await Promise.all(promises);
1226
- const success = results.every((result) => result === true);
1184
+ const results = await this.execute(command, nodes);
1185
+ const success = results.every((result) => result === "STORED");
1227
1186
  await this.afterHook("prepend", { key, value, success });
1228
1187
  return success;
1229
1188
  }
@@ -1238,16 +1197,8 @@ ${valueStr}`;
1238
1197
  await this.beforeHook("delete", { key });
1239
1198
  this.validateKey(key);
1240
1199
  const nodes = await this.getNodesByKey(key);
1241
- const promises = nodes.map(async (node) => {
1242
- try {
1243
- const result = await node.command(`delete ${key}`);
1244
- return result === "DELETED";
1245
- } catch {
1246
- return false;
1247
- }
1248
- });
1249
- const results = await Promise.all(promises);
1250
- const success = results.every((result) => result === true);
1200
+ const results = await this.execute(`delete ${key}`, nodes);
1201
+ const success = results.every((result) => result === "DELETED");
1251
1202
  await this.afterHook("delete", { key, success });
1252
1203
  return success;
1253
1204
  }
@@ -1263,16 +1214,8 @@ ${valueStr}`;
1263
1214
  await this.beforeHook("incr", { key, value });
1264
1215
  this.validateKey(key);
1265
1216
  const nodes = await this.getNodesByKey(key);
1266
- const promises = nodes.map(async (node) => {
1267
- try {
1268
- const result = await node.command(`incr ${key} ${value}`);
1269
- return typeof result === "number" ? result : void 0;
1270
- } catch {
1271
- return void 0;
1272
- }
1273
- });
1274
- const results = await Promise.all(promises);
1275
- const newValue = results.find((v) => v !== void 0);
1217
+ const results = await this.execute(`incr ${key} ${value}`, nodes);
1218
+ const newValue = results.find((v) => typeof v === "number");
1276
1219
  await this.afterHook("incr", { key, value, newValue });
1277
1220
  return newValue;
1278
1221
  }
@@ -1288,16 +1231,8 @@ ${valueStr}`;
1288
1231
  await this.beforeHook("decr", { key, value });
1289
1232
  this.validateKey(key);
1290
1233
  const nodes = await this.getNodesByKey(key);
1291
- const promises = nodes.map(async (node) => {
1292
- try {
1293
- const result = await node.command(`decr ${key} ${value}`);
1294
- return typeof result === "number" ? result : void 0;
1295
- } catch {
1296
- return void 0;
1297
- }
1298
- });
1299
- const results = await Promise.all(promises);
1300
- const newValue = results.find((v) => v !== void 0);
1234
+ const results = await this.execute(`decr ${key} ${value}`, nodes);
1235
+ const newValue = results.find((v) => typeof v === "number");
1301
1236
  await this.afterHook("decr", { key, value, newValue });
1302
1237
  return newValue;
1303
1238
  }
@@ -1313,16 +1248,8 @@ ${valueStr}`;
1313
1248
  await this.beforeHook("touch", { key, exptime });
1314
1249
  this.validateKey(key);
1315
1250
  const nodes = await this.getNodesByKey(key);
1316
- const promises = nodes.map(async (node) => {
1317
- try {
1318
- const result = await node.command(`touch ${key} ${exptime}`);
1319
- return result === "TOUCHED";
1320
- } catch {
1321
- return false;
1322
- }
1323
- });
1324
- const results = await Promise.all(promises);
1325
- const success = results.every((result) => result === true);
1251
+ const results = await this.execute(`touch ${key} ${exptime}`, nodes);
1252
+ const success = results.every((result) => result === "TOUCHED");
1326
1253
  await this.afterHook("touch", { key, exptime, success });
1327
1254
  return success;
1328
1255
  }
@@ -1438,6 +1365,23 @@ ${valueStr}`;
1438
1365
  }
1439
1366
  return nodes;
1440
1367
  }
1368
+ /**
1369
+ * Execute a command on the specified nodes.
1370
+ * @param {string} command - The memcache command string to execute
1371
+ * @param {MemcacheNode[]} nodes - Array of MemcacheNode instances to execute on
1372
+ * @param {ExecuteOptions} options - Optional execution options
1373
+ * @returns {Promise<unknown[]>} Promise resolving to array of results from each node
1374
+ */
1375
+ async execute(command, nodes, options) {
1376
+ const promises = nodes.map(async (node) => {
1377
+ try {
1378
+ return await node.command(command, options?.commandOptions);
1379
+ } catch {
1380
+ return void 0;
1381
+ }
1382
+ });
1383
+ return Promise.all(promises);
1384
+ }
1441
1385
  /**
1442
1386
  * Validates a Memcache key according to protocol requirements.
1443
1387
  * @param {string} key - The key to validate
package/dist/index.d.cts CHANGED
@@ -187,13 +187,17 @@ interface MemcacheOptions {
187
187
  interface MemcacheStats {
188
188
  [key: string]: string;
189
189
  }
190
+ interface ExecuteOptions {
191
+ /** Command options passed to node.command() */
192
+ commandOptions?: CommandOptions;
193
+ }
190
194
  declare class Memcache extends Hookified {
191
195
  private _nodes;
192
196
  private _timeout;
193
197
  private _keepAlive;
194
198
  private _keepAliveDelay;
195
199
  private _hash;
196
- constructor(options?: MemcacheOptions);
200
+ constructor(options?: string | MemcacheOptions);
197
201
  /**
198
202
  * Get the list of nodes
199
203
  * @returns {MemcacheNode[]} Array of MemcacheNode
@@ -474,6 +478,14 @@ declare class Memcache extends Hookified {
474
478
  * @throws {Error} If no nodes are available for the key
475
479
  */
476
480
  getNodesByKey(key: string): Promise<Array<MemcacheNode>>;
481
+ /**
482
+ * Execute a command on the specified nodes.
483
+ * @param {string} command - The memcache command string to execute
484
+ * @param {MemcacheNode[]} nodes - Array of MemcacheNode instances to execute on
485
+ * @param {ExecuteOptions} options - Optional execution options
486
+ * @returns {Promise<unknown[]>} Promise resolving to array of results from each node
487
+ */
488
+ execute(command: string, nodes: MemcacheNode[], options?: ExecuteOptions): Promise<unknown[]>;
477
489
  /**
478
490
  * Validates a Memcache key according to protocol requirements.
479
491
  * @param {string} key - The key to validate
@@ -498,4 +510,4 @@ declare class Memcache extends Hookified {
498
510
  private forwardNodeEvents;
499
511
  }
500
512
 
501
- export { type HashProvider, Memcache, MemcacheEvents, type MemcacheOptions, type MemcacheStats, createNode, Memcache as default };
513
+ export { type ExecuteOptions, type HashProvider, Memcache, MemcacheEvents, type MemcacheOptions, type MemcacheStats, createNode, Memcache as default };
package/dist/index.d.ts CHANGED
@@ -187,13 +187,17 @@ interface MemcacheOptions {
187
187
  interface MemcacheStats {
188
188
  [key: string]: string;
189
189
  }
190
+ interface ExecuteOptions {
191
+ /** Command options passed to node.command() */
192
+ commandOptions?: CommandOptions;
193
+ }
190
194
  declare class Memcache extends Hookified {
191
195
  private _nodes;
192
196
  private _timeout;
193
197
  private _keepAlive;
194
198
  private _keepAliveDelay;
195
199
  private _hash;
196
- constructor(options?: MemcacheOptions);
200
+ constructor(options?: string | MemcacheOptions);
197
201
  /**
198
202
  * Get the list of nodes
199
203
  * @returns {MemcacheNode[]} Array of MemcacheNode
@@ -474,6 +478,14 @@ declare class Memcache extends Hookified {
474
478
  * @throws {Error} If no nodes are available for the key
475
479
  */
476
480
  getNodesByKey(key: string): Promise<Array<MemcacheNode>>;
481
+ /**
482
+ * Execute a command on the specified nodes.
483
+ * @param {string} command - The memcache command string to execute
484
+ * @param {MemcacheNode[]} nodes - Array of MemcacheNode instances to execute on
485
+ * @param {ExecuteOptions} options - Optional execution options
486
+ * @returns {Promise<unknown[]>} Promise resolving to array of results from each node
487
+ */
488
+ execute(command: string, nodes: MemcacheNode[], options?: ExecuteOptions): Promise<unknown[]>;
477
489
  /**
478
490
  * Validates a Memcache key according to protocol requirements.
479
491
  * @param {string} key - The key to validate
@@ -498,4 +510,4 @@ declare class Memcache extends Hookified {
498
510
  private forwardNodeEvents;
499
511
  }
500
512
 
501
- export { type HashProvider, Memcache, MemcacheEvents, type MemcacheOptions, type MemcacheStats, createNode, Memcache as default };
513
+ export { type ExecuteOptions, type HashProvider, Memcache, MemcacheEvents, type MemcacheOptions, type MemcacheStats, createNode, Memcache as default };
package/dist/index.js CHANGED
@@ -675,12 +675,19 @@ var Memcache = class extends Hookified2 {
675
675
  constructor(options) {
676
676
  super();
677
677
  this._hash = new KetamaHash();
678
- this._timeout = options?.timeout || 5e3;
679
- this._keepAlive = options?.keepAlive !== false;
680
- this._keepAliveDelay = options?.keepAliveDelay || 1e3;
681
- const nodeUris = options?.nodes || ["localhost:11211"];
682
- for (const nodeUri of nodeUris) {
683
- this.addNode(nodeUri);
678
+ if (typeof options === "string") {
679
+ this._timeout = 5e3;
680
+ this._keepAlive = true;
681
+ this._keepAliveDelay = 1e3;
682
+ this.addNode(options);
683
+ } else {
684
+ this._timeout = options?.timeout || 5e3;
685
+ this._keepAlive = options?.keepAlive !== false;
686
+ this._keepAliveDelay = options?.keepAliveDelay || 1e3;
687
+ const nodeUris = options?.nodes || ["localhost:11211"];
688
+ for (const nodeUri of nodeUris) {
689
+ this.addNode(nodeUri);
690
+ }
684
691
  }
685
692
  }
686
693
  /**
@@ -1029,16 +1036,8 @@ var Memcache = class extends Hookified2 {
1029
1036
  const command = `cas ${key} ${flags} ${exptime} ${bytes} ${casToken}\r
1030
1037
  ${valueStr}`;
1031
1038
  const nodes = await this.getNodesByKey(key);
1032
- const promises = nodes.map(async (node) => {
1033
- try {
1034
- const result = await node.command(command);
1035
- return result === "STORED";
1036
- } catch {
1037
- return false;
1038
- }
1039
- });
1040
- const results = await Promise.all(promises);
1041
- const success = results.every((result) => result === true);
1039
+ const results = await this.execute(command, nodes);
1040
+ const success = results.every((result) => result === "STORED");
1042
1041
  await this.afterHook("cas", {
1043
1042
  key,
1044
1043
  value,
@@ -1067,16 +1066,8 @@ ${valueStr}`;
1067
1066
  const command = `set ${key} ${flags} ${exptime} ${bytes}\r
1068
1067
  ${valueStr}`;
1069
1068
  const nodes = await this.getNodesByKey(key);
1070
- const promises = nodes.map(async (node) => {
1071
- try {
1072
- const result = await node.command(command);
1073
- return result === "STORED";
1074
- } catch {
1075
- return false;
1076
- }
1077
- });
1078
- const results = await Promise.all(promises);
1079
- const success = results.every((result) => result === true);
1069
+ const results = await this.execute(command, nodes);
1070
+ const success = results.every((result) => result === "STORED");
1080
1071
  await this.afterHook("set", { key, value, exptime, flags, success });
1081
1072
  return success;
1082
1073
  }
@@ -1098,16 +1089,8 @@ ${valueStr}`;
1098
1089
  const command = `add ${key} ${flags} ${exptime} ${bytes}\r
1099
1090
  ${valueStr}`;
1100
1091
  const nodes = await this.getNodesByKey(key);
1101
- const promises = nodes.map(async (node) => {
1102
- try {
1103
- const result = await node.command(command);
1104
- return result === "STORED";
1105
- } catch {
1106
- return false;
1107
- }
1108
- });
1109
- const results = await Promise.all(promises);
1110
- const success = results.every((result) => result === true);
1092
+ const results = await this.execute(command, nodes);
1093
+ const success = results.every((result) => result === "STORED");
1111
1094
  await this.afterHook("add", { key, value, exptime, flags, success });
1112
1095
  return success;
1113
1096
  }
@@ -1129,16 +1112,8 @@ ${valueStr}`;
1129
1112
  const command = `replace ${key} ${flags} ${exptime} ${bytes}\r
1130
1113
  ${valueStr}`;
1131
1114
  const nodes = await this.getNodesByKey(key);
1132
- const promises = nodes.map(async (node) => {
1133
- try {
1134
- const result = await node.command(command);
1135
- return result === "STORED";
1136
- } catch {
1137
- return false;
1138
- }
1139
- });
1140
- const results = await Promise.all(promises);
1141
- const success = results.every((result) => result === true);
1115
+ const results = await this.execute(command, nodes);
1116
+ const success = results.every((result) => result === "STORED");
1142
1117
  await this.afterHook("replace", { key, value, exptime, flags, success });
1143
1118
  return success;
1144
1119
  }
@@ -1158,16 +1133,8 @@ ${valueStr}`;
1158
1133
  const command = `append ${key} 0 0 ${bytes}\r
1159
1134
  ${valueStr}`;
1160
1135
  const nodes = await this.getNodesByKey(key);
1161
- const promises = nodes.map(async (node) => {
1162
- try {
1163
- const result = await node.command(command);
1164
- return result === "STORED";
1165
- } catch {
1166
- return false;
1167
- }
1168
- });
1169
- const results = await Promise.all(promises);
1170
- const success = results.every((result) => result === true);
1136
+ const results = await this.execute(command, nodes);
1137
+ const success = results.every((result) => result === "STORED");
1171
1138
  await this.afterHook("append", { key, value, success });
1172
1139
  return success;
1173
1140
  }
@@ -1187,16 +1154,8 @@ ${valueStr}`;
1187
1154
  const command = `prepend ${key} 0 0 ${bytes}\r
1188
1155
  ${valueStr}`;
1189
1156
  const nodes = await this.getNodesByKey(key);
1190
- const promises = nodes.map(async (node) => {
1191
- try {
1192
- const result = await node.command(command);
1193
- return result === "STORED";
1194
- } catch {
1195
- return false;
1196
- }
1197
- });
1198
- const results = await Promise.all(promises);
1199
- const success = results.every((result) => result === true);
1157
+ const results = await this.execute(command, nodes);
1158
+ const success = results.every((result) => result === "STORED");
1200
1159
  await this.afterHook("prepend", { key, value, success });
1201
1160
  return success;
1202
1161
  }
@@ -1211,16 +1170,8 @@ ${valueStr}`;
1211
1170
  await this.beforeHook("delete", { key });
1212
1171
  this.validateKey(key);
1213
1172
  const nodes = await this.getNodesByKey(key);
1214
- const promises = nodes.map(async (node) => {
1215
- try {
1216
- const result = await node.command(`delete ${key}`);
1217
- return result === "DELETED";
1218
- } catch {
1219
- return false;
1220
- }
1221
- });
1222
- const results = await Promise.all(promises);
1223
- const success = results.every((result) => result === true);
1173
+ const results = await this.execute(`delete ${key}`, nodes);
1174
+ const success = results.every((result) => result === "DELETED");
1224
1175
  await this.afterHook("delete", { key, success });
1225
1176
  return success;
1226
1177
  }
@@ -1236,16 +1187,8 @@ ${valueStr}`;
1236
1187
  await this.beforeHook("incr", { key, value });
1237
1188
  this.validateKey(key);
1238
1189
  const nodes = await this.getNodesByKey(key);
1239
- const promises = nodes.map(async (node) => {
1240
- try {
1241
- const result = await node.command(`incr ${key} ${value}`);
1242
- return typeof result === "number" ? result : void 0;
1243
- } catch {
1244
- return void 0;
1245
- }
1246
- });
1247
- const results = await Promise.all(promises);
1248
- const newValue = results.find((v) => v !== void 0);
1190
+ const results = await this.execute(`incr ${key} ${value}`, nodes);
1191
+ const newValue = results.find((v) => typeof v === "number");
1249
1192
  await this.afterHook("incr", { key, value, newValue });
1250
1193
  return newValue;
1251
1194
  }
@@ -1261,16 +1204,8 @@ ${valueStr}`;
1261
1204
  await this.beforeHook("decr", { key, value });
1262
1205
  this.validateKey(key);
1263
1206
  const nodes = await this.getNodesByKey(key);
1264
- const promises = nodes.map(async (node) => {
1265
- try {
1266
- const result = await node.command(`decr ${key} ${value}`);
1267
- return typeof result === "number" ? result : void 0;
1268
- } catch {
1269
- return void 0;
1270
- }
1271
- });
1272
- const results = await Promise.all(promises);
1273
- const newValue = results.find((v) => v !== void 0);
1207
+ const results = await this.execute(`decr ${key} ${value}`, nodes);
1208
+ const newValue = results.find((v) => typeof v === "number");
1274
1209
  await this.afterHook("decr", { key, value, newValue });
1275
1210
  return newValue;
1276
1211
  }
@@ -1286,16 +1221,8 @@ ${valueStr}`;
1286
1221
  await this.beforeHook("touch", { key, exptime });
1287
1222
  this.validateKey(key);
1288
1223
  const nodes = await this.getNodesByKey(key);
1289
- const promises = nodes.map(async (node) => {
1290
- try {
1291
- const result = await node.command(`touch ${key} ${exptime}`);
1292
- return result === "TOUCHED";
1293
- } catch {
1294
- return false;
1295
- }
1296
- });
1297
- const results = await Promise.all(promises);
1298
- const success = results.every((result) => result === true);
1224
+ const results = await this.execute(`touch ${key} ${exptime}`, nodes);
1225
+ const success = results.every((result) => result === "TOUCHED");
1299
1226
  await this.afterHook("touch", { key, exptime, success });
1300
1227
  return success;
1301
1228
  }
@@ -1411,6 +1338,23 @@ ${valueStr}`;
1411
1338
  }
1412
1339
  return nodes;
1413
1340
  }
1341
+ /**
1342
+ * Execute a command on the specified nodes.
1343
+ * @param {string} command - The memcache command string to execute
1344
+ * @param {MemcacheNode[]} nodes - Array of MemcacheNode instances to execute on
1345
+ * @param {ExecuteOptions} options - Optional execution options
1346
+ * @returns {Promise<unknown[]>} Promise resolving to array of results from each node
1347
+ */
1348
+ async execute(command, nodes, options) {
1349
+ const promises = nodes.map(async (node) => {
1350
+ try {
1351
+ return await node.command(command, options?.commandOptions);
1352
+ } catch {
1353
+ return void 0;
1354
+ }
1355
+ });
1356
+ return Promise.all(promises);
1357
+ }
1414
1358
  /**
1415
1359
  * Validates a Memcache key according to protocol requirements.
1416
1360
  * @param {string} key - The key to validate