memcache 0.3.0 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jared Wray
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,109 +1,482 @@
1
- node.js memcached client
2
- ========================
1
+ [<img src="https://jaredwray.com/images/memcache.svg" alt="Memcache Logo" align="center">](https://memcachejs.org)
2
+
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.yaml/badge.svg)](https://github.com/jaredwray/memcache/actions/workflows/tests.yaml)
5
+ [![npm](https://img.shields.io/npm/v/memcache)](https://www.npmjs.com/package/memcache)
6
+ [![npm](https://img.shields.io/npm/dm/memcache)](https://www.npmjs.com/package/memcache)
7
+ [![license](https://img.shields.io/github/license/jaredwray/memcache)](https://github.com/jaredwray/memcache/blob/main/LICENSE)
8
+
9
+ # Memcache
10
+ Nodejs Memcache Client
11
+
12
+ # Table of Contents
13
+
14
+ - [Getting Started](#getting-started)
15
+ - [Installation](#installation)
16
+ - [Basic Usage](#basic-usage)
17
+ - [Custom Connection](#custom-connection)
18
+ - [API](#api)
19
+ - [Constructor](#constructor)
20
+ - [Properties](#properties)
21
+ - [Connection Management](#connection-management)
22
+ - [Node Management](#node-management)
23
+ - [Data Storage Operations](#data-storage-operations)
24
+ - [String Modification Operations](#string-modification-operations)
25
+ - [Deletion & Expiration](#deletion--expiration)
26
+ - [Numeric Operations](#numeric-operations)
27
+ - [Server Management & Statistics](#server-management--statistics)
28
+ - [Validation](#validation)
29
+ - [Helper Functions](#helper-functions)
30
+ - [Hooks and Events](#hooks-and-events)
31
+ - [Events](#events)
32
+ - [Available Events](#available-events)
33
+ - [Hooks](#hooks)
34
+ - [Available Hooks](#available-hooks)
35
+ - [get(key)](#getkey)
36
+ - [set(key, value, exptime?, flags?)](#setkey-value-exptime-flags)
37
+ - [gets(keys[])](#getskeys)
38
+ - [add(key, value, exptime?, flags?)](#addkey-value-exptime-flags)
39
+ - [replace(key, value, exptime?, flags?)](#replacekey-value-exptime-flags)
40
+ - [append(key, value)](#appendkey-value)
41
+ - [prepend(key, value)](#prependkey-value)
42
+ - [delete(key)](#deletekey)
43
+ - [incr(key, value?)](#incrkey-value)
44
+ - [decr(key, value?)](#decrkey-value)
45
+ - [touch(key, exptime)](#touchkey-exptime)
46
+ - [Hook Examples](#hook-examples)
47
+ - [Contributing](#contributing)
48
+ - [License and Copyright](#license-and-copyright)
49
+
50
+ # Getting Started
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ npm install memcache
56
+ ```
57
+
58
+ or with pnpm:
59
+
60
+ ```bash
61
+ pnpm add memcache
62
+ ```
63
+
64
+ ## Basic Usage
65
+
66
+ ```javascript
67
+ import { Memcache } from 'memcache';
68
+
69
+ // Create a new client
70
+ const client = new Memcache();
71
+
72
+ // Set a value
73
+ await client.set('mykey', 'Hello, Memcache!');
74
+
75
+ // Get a value
76
+ const value = await client.get('mykey');
77
+ console.log(value); // ['Hello, Memcache!']
78
+
79
+ // Delete a value
80
+ await client.delete('mykey');
81
+
82
+ // Close the connection
83
+ await client.quit();
84
+ ```
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
+ });
3
111
 
4
- A pure-JavaScript memcached library for node.
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!']
5
116
 
117
+ // Close the connection
118
+ await client.quit();
119
+ ```
6
120
 
7
- Tests
8
- -----
121
+ You can also pass an array of MemcacheNode instances for advanced configuration:
9
122
 
10
- To run the test suite, first insall <a href="http://github.com/visionmedia/expresso">expresso</a>,
11
- then run <code>make test</code>.
123
+ ```javascript
124
+ import { Memcache, createNode } from 'memcache';
12
125
 
13
- If you have <a href="http://github.com/visionmedia/node-jscoverage">node-jscoverage</a> you can
14
- also <code>make test-cov</code> for coverage, but that's pretty nerdy.
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 });
15
130
 
131
+ // Create a client with MemcacheNode instances
132
+ const client = new Memcache({
133
+ nodes: [node1, node2, node3],
134
+ timeout: 10000
135
+ });
16
136
 
17
- Usage
18
- -----
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!']
19
141
 
20
- Create a Client object to start working.
21
- Host and port can be passed to the constructor or set afterwards.
22
- They have sensible defaults.
142
+ // Close the connection
143
+ await client.quit();
144
+ ```
23
145
 
24
- var memcache = require('./memcache');
146
+ # API
25
147
 
26
- var client = new memcache.Client(port, host);
27
- client.port = 11211;
28
- client.host = 'localhost';
148
+ ## Constructor
29
149
 
30
- The Client object emits 4 important events - connect, close, timeout and error.
150
+ ```typescript
151
+ new Memcache(options?: string | MemcacheOptions)
152
+ ```
31
153
 
32
- client.on('connect', function(){
33
- // no arguments - we've connected
34
- });
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
35
157
 
36
- client.on('close', function(){
37
- // no arguments - connection has been closed
38
- });
158
+ **Examples:**
39
159
 
40
- client.on('timeout', function(){
41
- // no arguments - socket timed out
42
- });
160
+ ```javascript
161
+ // Single node as string
162
+ const client = new Memcache('localhost:11211');
43
163
 
44
- client.on('error', function(e){
45
- // there was an error - exception is 1st argument
46
- });
47
-
48
- // connect to the memcache server after subscribing to some or all of these events
49
- client.connect()
164
+ // Single node with protocol
165
+ const client = new Memcache('memcache://192.168.1.100:11211');
50
166
 
51
- After connecting, you can start to make requests.
167
+ // Multiple nodes with options
168
+ const client = new Memcache({
169
+ nodes: ['localhost:11211', 'server2:11211'],
170
+ timeout: 10000
171
+ });
172
+ ```
52
173
 
53
- client.get('key', function(error, result){
174
+ ### Options
54
175
 
55
- // all of the callbacks have two arguments.
56
- // 'result' may contain things which aren't great, but
57
- // aren't really errors, like 'NOT_STORED'
176
+ - `nodes?: (string | MemcacheNode)[]` - Array of node URIs or MemcacheNode instances
177
+ - Examples: `["localhost:11211", "memcache://192.168.1.100:11212"]`
178
+ - `timeout?: number` - Operation timeout in milliseconds (default: 5000)
179
+ - `keepAlive?: boolean` - Keep connection alive (default: true)
180
+ - `keepAliveDelay?: number` - Keep alive delay in milliseconds (default: 1000)
181
+ - `hash?: HashProvider` - Hash provider for consistent hashing (default: KetamaHash)
58
182
 
59
- });
183
+ ## Properties
60
184
 
61
- client.set('key', 'value', function(error, result){
185
+ ### `nodes: MemcacheNode[]` (readonly)
186
+ Returns the list of all MemcacheNode instances in the cluster.
62
187
 
63
- // lifetime is optional. the default is
64
- // to never expire (0)
188
+ ### `nodeIds: string[]` (readonly)
189
+ Returns the list of node IDs (e.g., `["localhost:11211", "127.0.0.1:11212"]`).
65
190
 
66
- }, lifetime);
191
+ ### `hash: HashProvider`
192
+ Get or set the hash provider used for consistent hashing distribution.
67
193
 
68
- client.delete('key', function(error, result){
194
+ ### `timeout: number`
195
+ Get or set the timeout for operations in milliseconds (default: 5000).
69
196
 
70
- // delete a key from cache.
71
- });
197
+ ### `keepAlive: boolean`
198
+ Get or set the keepAlive setting. Updates all existing nodes. Requires `reconnect()` to apply changes.
72
199
 
73
- client.version(function(error, result)){
200
+ ### `keepAliveDelay: number`
201
+ Get or set the keep alive delay in milliseconds. Updates all existing nodes. Requires `reconnect()` to apply changes.
74
202
 
75
- // grab the server version
76
- });
203
+ ## Connection Management
77
204
 
205
+ ### `connect(nodeId?: string): Promise<void>`
206
+ Connect to all Memcache servers or a specific node.
78
207
 
79
- There are all the commands you would expect.
208
+ ### `disconnect(): Promise<void>`
209
+ Disconnect all connections.
80
210
 
81
- // all of the different "store" operations
82
- // (lifetime & flags are both optional)
83
- client.set(key, value, callback, lifetime, flags);
84
- client.add(key, value, callback, lifetime, flags);
85
- client.replace(key, value, callback, lifetime, flags);
86
- client.append(key, value, callback, lifetime, flags);
87
- client.prepend(key, value, callback, lifetime, flags);
88
- client.cas(key, value, unique, callback, lifetime, flags);
211
+ ### `reconnect(): Promise<void>`
212
+ Reconnect all nodes by disconnecting and connecting them again.
89
213
 
90
- // increment and decrement (named differently to the server commands - for now!)
91
- // (value is optional, defaults to 1)
92
- client.increment('key', value, callback);
93
- client.decrement('key', value, callback);
214
+ ### `quit(): Promise<void>`
215
+ Quit all connections gracefully.
94
216
 
95
- // statistics. the success argument to the callback
96
- // is a key=>value object
97
- client.stats(callback);
98
- client.stats('settings', callback);
99
- client.stats('items', callback);
100
- client.stats('mongeese', callback);
217
+ ### `isConnected(): boolean`
218
+ Check if any node is connected to a Memcache server.
101
219
 
102
- Once you're done, close the connection.
220
+ ## Node Management
103
221
 
104
- client.close();
222
+ ### `getNodes(): MemcacheNode[]`
223
+ Get an array of all MemcacheNode instances.
105
224
 
106
- There might be bugs. I'd like to know about them.
225
+ ### `getNode(id: string): MemcacheNode | undefined`
226
+ Get a specific node by its ID (e.g., `"localhost:11211"`).
107
227
 
108
- I bet you also want to read the <a href="http://github.com/memcached/memcached/blob/master/doc/protocol.txt">memcached
109
- protocol doc</a>. It's exciting! It also explains possible error messages.
228
+ ### `addNode(uri: string | MemcacheNode, weight?: number): Promise<void>`
229
+ Add a new node to the cluster. Throws error if node already exists.
230
+
231
+ ### `removeNode(uri: string): Promise<void>`
232
+ Remove a node from the cluster.
233
+
234
+ ### `getNodesByKey(key: string): Promise<MemcacheNode[]>`
235
+ Get the nodes for a given key using consistent hashing. Automatically connects to nodes if not already connected.
236
+
237
+ ### `parseUri(uri: string): { host: string; port: number }`
238
+ Parse a URI string into host and port. Supports formats:
239
+ - Simple: `"localhost:11211"` or `"localhost"`
240
+ - Protocol: `"memcache://localhost:11211"`, `"tcp://localhost:11211"`
241
+ - IPv6: `"[::1]:11211"` or `"memcache://[2001:db8::1]:11212"`
242
+ - Unix socket: `"/var/run/memcached.sock"` or `"unix:///var/run/memcached.sock"`
243
+
244
+ ## Data Storage Operations
245
+
246
+ ### `get(key: string): Promise<string | undefined>`
247
+ Get a value from the Memcache server. Returns the first successful result from replica nodes.
248
+
249
+ ### `gets(keys: string[]): Promise<Map<string, string>>`
250
+ Get multiple values from the Memcache server. Returns a Map with keys to values.
251
+
252
+ ### `set(key: string, value: string, exptime?: number, flags?: number): Promise<boolean>`
253
+ Set a value in the Memcache server. Returns true only if all replica nodes succeed.
254
+ - `exptime` - Expiration time in seconds (default: 0 = never expire)
255
+ - `flags` - Flags/metadata (default: 0)
256
+
257
+ ### `add(key: string, value: string, exptime?: number, flags?: number): Promise<boolean>`
258
+ Add a value (only if key doesn't exist). Returns true only if all replica nodes succeed.
259
+
260
+ ### `replace(key: string, value: string, exptime?: number, flags?: number): Promise<boolean>`
261
+ Replace a value (only if key exists). Returns true only if all replica nodes succeed.
262
+
263
+ ### `cas(key: string, value: string, casToken: string, exptime?: number, flags?: number): Promise<boolean>`
264
+ Check-And-Set: Store a value only if it hasn't been modified since last fetch. Returns true only if all replica nodes succeed.
265
+
266
+ ## String Modification Operations
267
+
268
+ ### `append(key: string, value: string): Promise<boolean>`
269
+ Append a value to an existing key. Returns true only if all replica nodes succeed.
270
+
271
+ ### `prepend(key: string, value: string): Promise<boolean>`
272
+ Prepend a value to an existing key. Returns true only if all replica nodes succeed.
273
+
274
+ ## Deletion & Expiration
275
+
276
+ ### `delete(key: string): Promise<boolean>`
277
+ Delete a value from the Memcache server. Returns true only if all replica nodes succeed.
278
+
279
+ ### `touch(key: string, exptime: number): Promise<boolean>`
280
+ Update expiration time without retrieving value. Returns true only if all replica nodes succeed.
281
+
282
+ ## Numeric Operations
283
+
284
+ ### `incr(key: string, value?: number): Promise<number | undefined>`
285
+ Increment a value. Returns the new value or undefined on failure.
286
+ - `value` - Amount to increment (default: 1)
287
+
288
+ ### `decr(key: string, value?: number): Promise<number | undefined>`
289
+ Decrement a value. Returns the new value or undefined on failure.
290
+ - `value` - Amount to decrement (default: 1)
291
+
292
+ ## Server Management & Statistics
293
+
294
+ ### `flush(delay?: number): Promise<boolean>`
295
+ Flush all values from all Memcache servers. Returns true if all nodes successfully flushed.
296
+ - `delay` - Optional delay in seconds before flushing
297
+
298
+ ### `stats(type?: string): Promise<Map<string, MemcacheStats>>`
299
+ Get statistics from all Memcache servers. Returns a Map of node IDs to their stats.
300
+
301
+ ### `version(): Promise<Map<string, string>>`
302
+ Get the Memcache server version from all nodes. Returns a Map of node IDs to version strings.
303
+
304
+ ## Validation
305
+
306
+ ### `validateKey(key: string): void`
307
+ Validates a Memcache key according to protocol requirements. Throws error if:
308
+ - Key is empty
309
+ - Key exceeds 250 characters
310
+ - Key contains spaces, newlines, or null characters
311
+
312
+ ## Helper Functions
313
+
314
+ ### `createNode(host: string, port: number, options?: MemcacheNodeOptions): MemcacheNode`
315
+ Factory function to create a new MemcacheNode instance.
316
+
317
+ ```javascript
318
+ import { createNode } from 'memcache';
319
+
320
+ const node = createNode('localhost', 11211, {
321
+ timeout: 5000,
322
+ keepAlive: true,
323
+ weight: 1
324
+ });
325
+ ```
326
+
327
+ # Hooks and Events
328
+
329
+ The Memcache client extends [Hookified](https://github.com/jaredwray/hookified) to provide powerful hooks and events for monitoring and customizing behavior.
330
+
331
+ ## Events
332
+
333
+ The client emits various events during operations that you can listen to:
334
+
335
+ ```javascript
336
+ const client = new Memcache();
337
+
338
+ // Connection events
339
+ client.on('connect', () => {
340
+ console.log('Connected to Memcache server');
341
+ });
342
+
343
+ client.on('close', () => {
344
+ console.log('Connection closed');
345
+ });
346
+
347
+ client.on('error', (error) => {
348
+ console.error('Error:', error);
349
+ });
350
+
351
+ client.on('timeout', () => {
352
+ console.log('Connection timeout');
353
+ });
354
+
355
+ // Cache hit/miss events
356
+ client.on('hit', (key, value) => {
357
+ console.log(`Cache hit for key: ${key}`);
358
+ });
359
+
360
+ client.on('miss', (key) => {
361
+ console.log(`Cache miss for key: ${key}`);
362
+ });
363
+ ```
364
+
365
+ ## Available Events
366
+
367
+ - `connect` - Emitted when connection to Memcache server is established
368
+ - `close` - Emitted when connection is closed
369
+ - `error` - Emitted when an error occurs
370
+ - `timeout` - Emitted when a connection timeout occurs
371
+ - `hit` - Emitted when a key is found in cache (includes key and value)
372
+ - `miss` - Emitted when a key is not found in cache
373
+ - `quit` - Emitted when quit command is sent
374
+ - `warn` - Emitted for warning messages
375
+ - `info` - Emitted for informational messages
376
+
377
+ ## Hooks
378
+
379
+ Hooks allow you to intercept and modify behavior before and after operations. Every operation supports `before` and `after` hooks.
380
+
381
+ ```javascript
382
+ const client = new Memcache();
383
+
384
+ // Add a before hook for get operations
385
+ client.onHook('before:get', async ({ key }) => {
386
+ console.log(`Getting key: ${key}`);
387
+ });
388
+
389
+ // Add an after hook for set operations
390
+ client.onHook('after:set', async ({ key, value, success }) => {
391
+ if (success) {
392
+ console.log(`Successfully set ${key}`);
393
+ }
394
+ });
395
+
396
+ // Hooks can be async and modify behavior
397
+ client.onHook('before:set', async ({ key, value }) => {
398
+ console.log(`About to set ${key} = ${value}`);
399
+ // Perform validation, logging, etc.
400
+ });
401
+ ```
402
+
403
+ ## Available Hooks
404
+
405
+ All operations support before and after hooks with specific parameters:
406
+
407
+ ## get(key)
408
+ - `before:get` - `{ key }`
409
+ - `after:get` - `{ key, value }` (value is array or undefined)
410
+
411
+ ## set(key, value, exptime?, flags?)
412
+ - `before:set` - `{ key, value, exptime, flags }`
413
+ - `after:set` - `{ key, value, exptime, flags, success }`
414
+
415
+ ## gets(keys[])
416
+ - `before:gets` - `{ keys }`
417
+ - `after:gets` - `{ keys, values }` (values is a Map)
418
+
419
+ ## add(key, value, exptime?, flags?)
420
+ - `before:add` - `{ key, value, exptime, flags }`
421
+ - `after:add` - `{ key, value, exptime, flags, success }`
422
+
423
+ ## replace(key, value, exptime?, flags?)
424
+ - `before:replace` - `{ key, value, exptime, flags }`
425
+ - `after:replace` - `{ key, value, exptime, flags, success }`
426
+
427
+ ## append(key, value)
428
+ - `before:append` - `{ key, value }`
429
+ - `after:append` - `{ key, value, success }`
430
+
431
+ ## prepend(key, value)
432
+ - `before:prepend` - `{ key, value }`
433
+ - `after:prepend` - `{ key, value, success }`
434
+
435
+ ## delete(key)
436
+ - `before:delete` - `{ key }`
437
+ - `after:delete` - `{ key, success }`
438
+
439
+ ## incr(key, value?)
440
+ - `before:incr` - `{ key, value }`
441
+ - `after:incr` - `{ key, value, newValue }`
442
+
443
+ ## decr(key, value?)
444
+ - `before:decr` - `{ key, value }`
445
+ - `after:decr` - `{ key, value, newValue }`
446
+
447
+ ## touch(key, exptime)
448
+ - `before:touch` - `{ key, exptime }`
449
+ - `after:touch` - `{ key, exptime, success }`
450
+
451
+ ## Hook Examples
452
+
453
+ ```javascript
454
+ const client = new Memcache();
455
+
456
+ // Log all get operations
457
+ client.onHook('before:get', async ({ key }) => {
458
+ console.log(`[GET] Fetching key: ${key}`);
459
+ });
460
+
461
+ client.onHook('after:get', async ({ key, value }) => {
462
+ console.log(`[GET] Key: ${key}, Found: ${value !== undefined}`);
463
+ });
464
+
465
+ // Log all set operations with timing
466
+ client.onHook('before:set', async (context) => {
467
+ context.startTime = Date.now();
468
+ });
469
+
470
+ client.onHook('after:set', async (context) => {
471
+ const duration = Date.now() - context.startTime;
472
+ console.log(`[SET] Key: ${context.key}, Success: ${context.success}, Time: ${duration}ms`);
473
+ });
474
+ ```
475
+
476
+ # Contributing
477
+
478
+ Please read our [Contributing Guidelines](./CONTRIBUTING.md) and also our [Code of Conduct](./CODE_OF_CONDUCT.md).
479
+
480
+ # License and Copyright
481
+
482
+ [MIT & Copyright (c) Jared Wray](https://github.com/jaredwray/memcache/blob/main/LICENSE)