logo

codecov tests npm npm license

Memcache

Nodejs Memcache Client

Table of Contents

Getting Started

Installation

npm install memcache

or with pnpm:

pnpm add memcache

Basic Usage

import { Memcache } from 'memcache';

// Create a new client
const client = new Memcache();

// Set a value
await client.set('mykey', 'Hello, Memcache!');

// Get a value
const value = await client.get('mykey');
console.log(value); // ['Hello, Memcache!']

// Delete a value
await client.delete('mykey');

// Close the connection
await client.quit();

You can also just pass in the uri into the constructor

// Single node as string
const client = new Memcache('localhost:11211');

// Single node with protocol
const client = new Memcache('memcache://192.168.1.100:11211');

// Multiple nodes with options
const client = new Memcache({
  nodes: ['localhost:11211', 'server2:11211'],
  timeout: 10000
});

You can specify multiple Memcache nodes by passing an array of connection strings:

import { Memcache } from 'memcache';

// Create a client with multiple nodes
const client = new Memcache({
  nodes: ['localhost:11211', '192.168.1.100:11211', 'memcache://192.168.1.101:11211']
});

// Set and get values (automatically distributed across nodes)
await client.set('mykey', 'Hello, Memcache!');
const value = await client.get('mykey');
console.log(value); // ['Hello, Memcache!']

// Close the connection
await client.quit();

You can also pass an array of MemcacheNode instances for advanced configuration:

import { Memcache, createNode } from 'memcache';

// Create nodes with custom settings
const node1 = createNode('localhost', 11211, { weight: 2 });
const node2 = createNode('192.168.1.100', 11211, { weight: 1 });
const node3 = createNode('192.168.1.101', 11211, { weight: 1 });

// Create a client with MemcacheNode instances
const client = new Memcache({
  nodes: [node1, node2, node3],
  timeout: 10000
});

// node1 will receive twice as much traffic due to higher weight
await client.set('mykey', 'Hello, Memcache!');
const value = await client.get('mykey');
console.log(value); // ['Hello, Memcache!']

// Close the connection
await client.quit();

API

Constructor

new Memcache(options?: string | MemcacheOptions)

Creates a new Memcache client instance. You can pass either:

Examples:

// Single node as string
const client = new Memcache('localhost:11211');

// Single node with protocol
const client = new Memcache('memcache://192.168.1.100:11211');

// Multiple nodes with options
const client = new Memcache({
  nodes: ['localhost:11211', 'server2:11211'],
  timeout: 10000
});

Options

Properties

nodes: MemcacheNode[] (readonly)

Returns the list of all MemcacheNode instances in the cluster.

nodeIds: string[] (readonly)

Returns the list of node IDs (e.g., ["localhost:11211", "127.0.0.1:11212"]).

hash: HashProvider

Get or set the hash provider used for consistent hashing distribution.

timeout: number

Get or set the timeout in milliseconds (default: 5000). Setting it updates existing nodes and the Auto Discovery connection, and applies to commands that are already waiting.

maxPendingCommands: number

Get or set the most requests each node keeps waiting for a response or for its connection to open (default: 0, no limit). The value is rounded down, and anything below 1, or not a finite number, means no limit. Setting it updates existing nodes and applies to the next request; requests already waiting are not affected.

keepAlive: boolean

Get or set the keepAlive setting. Updates all existing nodes. Requires reconnect() to apply changes.

keepAliveDelay: number

Get or set the keep alive delay in milliseconds. Updates all existing nodes. Requires reconnect() to apply changes.

retries: number

Get or set the number of retry attempts for failed commands (default: 0).

retryDelay: number

Get or set the base delay in milliseconds between retry attempts (default: 100).

retryBackoff: RetryBackoffFunction

Get or set the backoff function for calculating retry delays.

retryOnlyIdempotent: boolean

Get or set whether retries are restricted to idempotent commands only (default: true).

maxKeySize: number

Get or set the maximum allowed key size in characters (default: 250). Memcache protocol max is 250.

maxValueSize: number

Get or set the maximum allowed value size in bytes (default: 1048576). Writes (set, add, replace, append, prepend, cas) throw when the encoded value exceeds this limit. Raise it if your memcached server is started with a larger -I item size.

maxExpiration: number

Get or set the maximum allowed expiration in seconds (default: 2592000). Writes that accept an expiration (set, add, replace, cas, touch) throw when exptime exceeds this limit. 0 (no expiration) is always allowed. Memcached treats any exptime greater than 2592000 as an absolute Unix timestamp, so the default guards against accidentally setting a TTL that memcached interprets as "already expired." Raise this if you need to pass Unix timestamps.

hashLargeKey: boolean

Get or set whether keys exceeding maxKeySize are hashed instead of throwing (default: false). When enabled, oversized keys are replaced with a short, deterministic hex digest (via the hashery library, djb2 by default) before being sent to memcache, so any string length is accepted. The same input always produces the same hashed key, but distinct long keys can collide. To change algorithm or providers, configure the hashery property.

const client = new Memcache({ hashLargeKey: true });
const longKey = 'user:profile:' + 'x'.repeat(500);
await client.set(longKey, 'value');     // hashed automatically
await client.get(longKey);              // same hash, returns 'value'

hashery: Hashery

Get or set the Hashery instance used to hash oversized keys when hashLargeKey is enabled. Always returns an instance, even when hashing is disabled, so you can pre-configure it (algorithm, custom providers, caching) before flipping hashLargeKey on. Hashery is re-exported from this package for convenience.

import Memcache, { Hashery } from 'memcache';

// Simple — defaults to djb2 sync hashing
const simple = new Memcache({ hashLargeKey: true });

// Advanced — supply a Hashery preconfigured for fnv1
const advanced = new Memcache({
  hashLargeKey: new Hashery({ defaultAlgorithmSync: 'fnv1' }),
});

// Or mutate the instance after construction
simple.hashery.defaultAlgorithmSync = 'murmur';

lazyConnect: boolean (readonly)

Whether nodes defer connecting until the first command is executed (default: true).

Connection Management

connect(nodeId?: string): Promise<void>

Connect to all Memcache servers or a specific node.

disconnect(): Promise<void>

Disconnect all connections.

reconnect(): Promise<void>

Reconnect all nodes by disconnecting and connecting them again.

quit(): Promise<void>

Quit all connections gracefully.

isConnected(): boolean

Check if any node is connected to a Memcache server.

Node Management

getNodes(): MemcacheNode[]

Get an array of all MemcacheNode instances.

getNode(id: string): MemcacheNode | undefined

Get a specific node by its ID (e.g., "localhost:11211").

addNode(uri: string | MemcacheNode, weight?: number): Promise<void>

Add a new node to the cluster. Throws error if node already exists.

removeNode(uri: string): Promise<void>

Remove a node from the cluster.

getNodesByKey(key: string): Promise<MemcacheNode[]>

Get the nodes for a given key using consistent hashing. Automatically connects to nodes if not already connected. With maxPendingCommands set, a call waiting for a node's connection counts toward that node's limit and, past it, rejects at once.

parseUri(uri: string): { host: string; port: number; secure?: boolean }

Parse a URI string into host and port. Supports formats:

Data Storage Operations

get(key: string): Promise<string | undefined>

Get a value from the Memcache server. Returns the first successful result from replica nodes.

gets(keys: string[]): Promise<Map<string, string>>

Get multiple values from the Memcache server. Returns a Map with keys to values.

set(key: string, value: string, exptime?: number, flags?: number): Promise<boolean>

Set a value in the Memcache server. Returns true only if all replica nodes succeed.

add(key: string, value: string, exptime?: number, flags?: number): Promise<boolean>

Add a value (only if key doesn't exist). Returns true only if all replica nodes succeed.

replace(key: string, value: string, exptime?: number, flags?: number): Promise<boolean>

Replace a value (only if key exists). Returns true only if all replica nodes succeed.

cas(key: string, value: string, casToken: string, exptime?: number, flags?: number): Promise<boolean>

Check-And-Set: Store a value only if it hasn't been modified since last fetch. Returns true only if all replica nodes succeed.

String Modification Operations

append(key: string, value: string): Promise<boolean>

Append a value to an existing key. Returns true only if all replica nodes succeed.

prepend(key: string, value: string): Promise<boolean>

Prepend a value to an existing key. Returns true only if all replica nodes succeed.

Deletion & Expiration

delete(key: string): Promise<boolean>

Delete a value from the Memcache server. Returns true only if all replica nodes succeed.

touch(key: string, exptime: number): Promise<boolean>

Update expiration time without retrieving value. Returns true only if all replica nodes succeed.

Numeric Operations

incr(key: string, value?: number): Promise<number | undefined>

Increment a value. Returns the new value or undefined on failure.

decr(key: string, value?: number): Promise<number | undefined>

Decrement a value. Returns the new value or undefined on failure.

Server Management & Statistics

flush(delay?: number): Promise<boolean>

Flush all values from all Memcache servers. Returns true if all nodes successfully flushed.

stats(type?: string): Promise<Map<string, MemcacheStats>>

Get statistics from all Memcache servers. Returns a Map of node IDs to their stats.

version(): Promise<Map<string, string>>

Get the Memcache server version from all nodes. Returns a Map of node IDs to version strings.

Validation

validateKey(key: string): void

Validates a Memcache key according to protocol requirements. Throws error if:

resolveKey(key: string): string

Returns the key that will actually be sent to memcache. When hashLargeKey is true and the key length exceeds maxKeySize, returns a short hex digest produced by the configured hashery instance (djb2 by default — 8 hex chars). Otherwise returns the original key unchanged. Called automatically before validateKey in every command, so calling it manually is only needed when you want to inspect the on-wire key.

Helper Functions

createNode(host: string, port: number, options?: MemcacheNodeOptions): MemcacheNode

Factory function to create a new MemcacheNode instance.

import { createNode } from 'memcache';

const node = createNode('localhost', 11211, {
  timeout: 5000,
  keepAlive: true,
  weight: 1
});

Hooks and Events

The Memcache client extends Hookified to provide powerful hooks and events for monitoring and customizing behavior.

Events

The client emits various events during operations that you can listen to:

const client = new Memcache();

// Connection events
client.on('connect', () => {
  console.log('Connected to Memcache server');
});

client.on('close', () => {
  console.log('Connection closed');
});

client.on('error', (error) => {
  console.error('Error:', error);
});

client.on('timeout', (nodeId) => {
  console.log(`No response from ${nodeId} in time`);
});

// Cache hit/miss events
client.on('hit', (key, value) => {
  console.log(`Cache hit for key: ${key}`);
});

client.on('miss', (key) => {
  console.log(`Cache miss for key: ${key}`);
});

Available Events

Hooks

Hooks allow you to intercept and modify behavior before and after operations. Every operation supports before and after hooks.

const client = new Memcache();

// Add a before hook for get operations
client.onHook('before:get', async ({ key }) => {
  console.log(`Getting key: ${key}`);
});

// Add an after hook for set operations
client.onHook('after:set', async ({ key, value, success }) => {
  if (success) {
    console.log(`Successfully set ${key}`);
  }
});

// Hooks can be async and modify behavior
client.onHook('before:set', async ({ key, value }) => {
  console.log(`About to set ${key} = ${value}`);
  // Perform validation, logging, etc.
});

Available Hooks

All operations support before and after hooks with specific parameters:

get(key)

set(key, value, exptime?, flags?)

gets(keys[])

add(key, value, exptime?, flags?)

replace(key, value, exptime?, flags?)

append(key, value)

prepend(key, value)

delete(key)

incr(key, value?)

decr(key, value?)

touch(key, exptime)

Hook Examples

const client = new Memcache();

// Log all get operations
client.onHook('before:get', async ({ key }) => {
  console.log(`[GET] Fetching key: ${key}`);
});

client.onHook('after:get', async ({ key, value }) => {
  console.log(`[GET] Key: ${key}, Found: ${value !== undefined}`);
});

// Log all set operations with timing
client.onHook('before:set', async (context) => {
  context.startTime = Date.now();
});

client.onHook('after:set', async (context) => {
  const duration = Date.now() - context.startTime;
  console.log(`[SET] Key: ${context.key}, Success: ${context.success}, Time: ${duration}ms`);
});

Distribution Algorithms

Memcache supports pluggable distribution algorithms to determine how keys are distributed across nodes. You can configure the algorithm using the hash option.

KetamaHash (Default)

KetamaHash uses the Ketama consistent hashing algorithm, which minimizes key redistribution when nodes are added or removed. This is the default and recommended algorithm for production environments with dynamic scaling.

import { Memcache } from 'memcache';

// KetamaHash is used by default
const client = new Memcache({
  nodes: ['server1:11211', 'server2:11211', 'server3:11211']
});

Characteristics:

ModulaHash

ModulaHash uses a simple modulo-based hashing algorithm (hash(key) % nodeCount). This is a simpler algorithm that may redistribute all keys when nodes change.

import { Memcache, ModulaHash } from 'memcache';

// Use ModulaHash for distribution
const client = new Memcache({
  nodes: ['server1:11211', 'server2:11211', 'server3:11211'],
  hash: new ModulaHash()
});

// With a custom hash algorithm (default is sha1)
const client2 = new Memcache({
  nodes: ['server1:11211', 'server2:11211'],
  hash: new ModulaHash('md5')
});

Characteristics:

Weighted Nodes with ModulaHash

ModulaHash supports weighted nodes, where nodes with higher weights receive proportionally more keys:

import { Memcache, ModulaHash, createNode } from 'memcache';

// Create nodes with different weights
const node1 = createNode('server1', 11211, { weight: 3 }); // 3x traffic
const node2 = createNode('server2', 11211, { weight: 1 }); // 1x traffic

const client = new Memcache({
  nodes: [node1, node2],
  hash: new ModulaHash()
});

// server1 will receive approximately 75% of keys
// server2 will receive approximately 25% of keys

BroadcastHash

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.

import { Memcache, BroadcastHash } from 'memcache';

// Use BroadcastHash for full replication
const client = new Memcache({
  nodes: ['server1:11211', 'server2:11211', 'server3:11211'],
  hash: new BroadcastHash()
});

// Every set/get/delete hits all three nodes
await client.set('mykey', 'Hello!');

Characteristics:

Choosing an Algorithm

Feature KetamaHash ModulaHash BroadcastHash
Key redistribution on node change Minimal (~1/n keys) All keys may move N/A (all nodes always)
Complexity Higher (virtual nodes) Lower (simple modulo) Simplest
Performance Slightly slower Faster Depends on node count
Best for Dynamic scaling Fixed clusters Replication
Weighted nodes Yes Yes No

Use KetamaHash (default) when:

Use ModulaHash when:

Use BroadcastHash when:

Retry Configuration

The Memcache client supports automatic retry of failed commands with configurable backoff strategies.

Basic Retry Setup

Enable retries by setting the retries option:

import { Memcache } from 'memcache';

const client = new Memcache({
  nodes: ['localhost:11211'],
  retries: 3,        // Retry up to 3 times
  retryDelay: 100    // 100ms between retries
});

You can also modify retry settings at runtime:

client.retries = 5;
client.retryDelay = 200;

Backoff Strategies

The client includes two built-in backoff functions:

Fixed Delay (Default)

import { Memcache, defaultRetryBackoff } from 'memcache';

const client = new Memcache({
  retries: 3,
  retryDelay: 100,
  retryBackoff: defaultRetryBackoff  // 100ms, 100ms, 100ms
});

Exponential Backoff

import { Memcache, exponentialRetryBackoff } from 'memcache';

const client = new Memcache({
  retries: 3,
  retryDelay: 100,
  retryBackoff: exponentialRetryBackoff  // 100ms, 200ms, 400ms
});

Custom Backoff Function

You can provide your own backoff function:

const client = new Memcache({
  retries: 3,
  retryDelay: 100,
  retryBackoff: (attempt, baseDelay) => {
    // Exponential backoff with jitter
    const delay = baseDelay * Math.pow(2, attempt);
    return delay + Math.random() * delay * 0.1;
  }
});

The backoff function receives:

Idempotent Safety

Important: By default, retries are only performed for commands explicitly marked as idempotent. This prevents accidental double-execution of non-idempotent operations like incr, decr, append, and prepend.

Why This Matters

If a network timeout occurs after the server applies a mutation but before the client receives the response, retrying would apply the mutation twice:

Safe Usage Patterns

For read operations (always safe to retry):

// Mark read operations as idempotent
await client.execute('get mykey', nodes, { idempotent: true });

For idempotent writes (safe to retry):

// SET with the same value is idempotent
await client.execute('set mykey 0 0 5\r\nhello', nodes, { idempotent: true });

Disable safety for all commands (use with caution):

const client = new Memcache({
  retries: 3,
  retryOnlyIdempotent: false  // Allow retries for ALL commands
});

Behavior Summary

retryOnlyIdempotent idempotent flag Retries enabled?
true (default) false (default) No
true (default) true Yes
false (any) Yes

Methods Without Retry Support

The following methods do not use the retry mechanism and have their own error handling:

To use retries with read operations, use the execute() method directly:

const nodes = await client.getNodesByKey('mykey');
const results = await client.execute('get mykey', nodes, { idempotent: true });

SASL Authentication

The Memcache client supports SASL (Simple Authentication and Security Layer) authentication using the PLAIN mechanism. This allows you to connect to memcached servers that require authentication.

Enabling SASL Authentication

import { Memcache } from 'memcache';

const client = new Memcache({
  nodes: ['localhost:11211'],
  sasl: {
    username: 'myuser',
    password: 'mypassword',
  },
});

await client.connect();
// Client is now authenticated and ready to use

SASL Options

The sasl option accepts an object with the following properties:

Currently, only the PLAIN mechanism is supported.

Binary Protocol Methods

Important: Memcached servers with SASL enabled (-S flag) require the binary protocol for all operations after authentication. The standard text-based methods (client.get(), client.set(), etc.) will not work on SASL-enabled servers.

Use the binary* methods on nodes for SASL-enabled servers:

import { Memcache } from 'memcache';

const client = new Memcache({
  nodes: ['localhost:11211'],
  sasl: { username: 'user', password: 'pass' },
});

await client.connect();

// Access the node directly for binary operations
const node = client.nodes[0];

// Binary protocol operations
await node.binarySet('mykey', 'myvalue', 3600);     // Set with 1 hour expiry
const value = await node.binaryGet('mykey');         // Get value
await node.binaryDelete('mykey');                    // Delete key

// Other binary operations
await node.binaryAdd('newkey', 'value');             // Add (only if not exists)
await node.binaryReplace('existingkey', 'newvalue'); // Replace (only if exists)
await node.binaryIncr('counter', 1);                 // Increment
await node.binaryDecr('counter', 1);                 // Decrement
await node.binaryAppend('mykey', '-suffix');         // Append to value
await node.binaryPrepend('mykey', 'prefix-');        // Prepend to value
await node.binaryTouch('mykey', 7200);               // Update expiration
await node.binaryFlush();                            // Flush all
const version = await node.binaryVersion();          // Get server version
const stats = await node.binaryStats();              // Get server stats

The binary* methods can run concurrently on the same node. Requests are pipelined on the node's connection, and each caller gets the response to its own request:

const [a, b] = await Promise.all([node.binaryGet('key-a'), node.binaryGet('key-b')]);

If a response doesn't match the request it should answer, every pending binary request is rejected and the connection is closed instead of returning another request's data.

Per-Node SASL Configuration

You can also configure SASL credentials when creating individual nodes:

import { createNode } from 'memcache';

// Create a node with SASL credentials
const node = createNode('localhost', 11211, {
  sasl: { username: 'user', password: 'pass' },
});

// Connect and use binary methods
await node.connect();
await node.binarySet('mykey', 'hello');
const value = await node.binaryGet('mykey');

Authentication Events

You can listen for authentication events on both nodes and the client:

import { Memcache, MemcacheNode } from 'memcache';

// Node-level events
const node = new MemcacheNode('localhost', 11211, {
  sasl: { username: 'user', password: 'pass' },
});

node.on('authenticated', () => {
  console.log('Node authenticated successfully');
});

node.on('error', (error) => {
  if (error.message.includes('SASL authentication failed')) {
    console.error('Authentication failed:', error.message);
  }
});

await node.connect();

// Client-level events (forwarded from nodes)
const client = new Memcache({
  nodes: ['localhost:11211'],
  sasl: { username: 'user', password: 'pass' },
});

client.on('authenticated', () => {
  console.log('Client authenticated');
});

await client.connect();

Node Properties

Server Configuration

To use SASL authentication, your memcached server must be configured with SASL support:

  1. Build memcached with SASL support - Ensure memcached was compiled with --enable-sasl

  2. Create SASL users - Use saslpasswd2 to create users:

    saslpasswd2 -a memcached -c username
    
  3. Configure SASL mechanism - Create /etc/sasl2/memcached.conf:

    mech_list: plain
    
  4. Start memcached with SASL - Use the -S flag:

    memcached -S -m 64 -p 11211
    

For more details, see the memcached SASL documentation.

Auto Discovery

The Memcache client supports AWS ElastiCache Auto Discovery, which automatically detects cluster topology changes and adds or removes nodes as needed. When enabled, the client connects to a configuration endpoint, retrieves the current list of cache nodes, and periodically polls for changes.

Enabling Auto Discovery

import { Memcache } from 'memcache';

const client = new Memcache({
  nodes: [],
  autoDiscover: {
    enabled: true,
    configEndpoint: 'my-cluster.cfg.use1.cache.amazonaws.com:11211',
  },
});

await client.connect();
// The client automatically discovers and connects to all cluster nodes

If you omit configEndpoint, the first node in the nodes array is used as the configuration endpoint:

const client = new Memcache({
  nodes: ['my-cluster.cfg.use1.cache.amazonaws.com:11211'],
  autoDiscover: {
    enabled: true,
  },
});

Auto Discovery Options

The autoDiscover option accepts an object with the following properties:

Auto Discovery Events

The client emits events during the auto discovery lifecycle:

const client = new Memcache({
  nodes: [],
  autoDiscover: {
    enabled: true,
    configEndpoint: 'my-cluster.cfg.use1.cache.amazonaws.com:11211',
  },
});

// Emitted on initial discovery with the full cluster config
client.on('autoDiscover', (config) => {
  console.log('Discovered nodes:', config.nodes);
  console.log('Config version:', config.version);
});

// Emitted when polling detects a topology change
client.on('autoDiscoverUpdate', (config) => {
  console.log('Cluster topology changed:', config.nodes);
});

// Emitted when discovery encounters an error (non-fatal, retries on next poll)
client.on('autoDiscoverError', (error) => {
  console.error('Discovery error:', error.message);
});

await client.connect();

Legacy Command Support

For ElastiCache engine versions older than 1.4.14, use the legacy discovery command:

const client = new Memcache({
  nodes: [],
  autoDiscover: {
    enabled: true,
    configEndpoint: 'my-cluster.cfg.use1.cache.amazonaws.com:11211',
    useLegacyCommand: true, // Uses 'get AmazonElastiCache:cluster' instead of 'config get cluster'
  },
});

IPv6 Support

The Memcache client fully supports IPv6 addresses using standard bracket notation in URIs.

Connecting to IPv6 Nodes

import { Memcache } from 'memcache';

// IPv6 loopback
const client = new Memcache('[::1]:11211');

// Multiple IPv6 nodes
const client = new Memcache({
  nodes: [
    '[::1]:11211',
    '[2001:db8::1]:11211',
    'memcache://[2001:db8::2]:11212',
  ],
});

await client.connect();

IPv6 in Auto Discovery

When auto discovery returns IPv6 node addresses, the client automatically brackets them for correct URI handling:

const client = new Memcache({
  nodes: [],
  autoDiscover: {
    enabled: true,
    configEndpoint: '[2001:db8::1]:11211',
  },
});

await client.connect();
// Discovered IPv6 nodes are added as [host]:port automatically

IPv6 Node IDs

Node IDs for IPv6 addresses use bracket notation to avoid ambiguity:

const client = new Memcache({
  nodes: ['[::1]:11211', '[2001:db8::1]:11212'],
});

console.log(client.nodeIds);
// ['[::1]:11211', '[2001:db8::1]:11212']

TLS Support

The client can connect to TLS-enabled memcached servers (memcached ships TLS since 1.5.13 via -Z; the official Docker image supports it since 1.5.21). Connection readiness waits for the completed TLS handshake (secureConnect), so pipelined commands are never written into an unfinished handshake.

Connecting with TLS

Enable TLS for every node with the client-level tls option:

import { Memcache } from 'memcache';

// TLS with Node's default trust store (publicly-trusted server certs)
const client = new Memcache({
  nodes: ['my-cache.example.com:11211'],
  tls: true,
});

await client.set('mykey', 'Hello over TLS!');

Or per node with the memcaches:// URI scheme (enables TLS for that node even when the client-level option is unset):

const client = new Memcache('memcaches://my-cache.example.com:11211');

TLS Options

The tls option accepts:

Certificate verification is always on (standard Node behavior). To connect to a server with a self-signed cert, pass its CA — do not disable verification.

The node's host, port (or Unix socket path), and keep-alive settings always win over any host / port / path fields in a tls options object.

Per-Node TLS Configuration

You can also enable TLS when creating individual nodes:

import { createNode } from 'memcache';
import { readFileSync } from 'node:fs';

const node = createNode('memcached-internal', 11211, {
  tls: { ca: readFileSync('/etc/ssl/private-ca.pem') },
});

await node.connect();
await node.command('version');

TLS Node Properties

The client's connect event fires after the TLS handshake completes.

AWS ElastiCache Serverless

ElastiCache Serverless (Memcached) requires TLS and only speaks the text protocol — both are this client's defaults, so the only configuration needed is tls: true (the server presents a publicly-trusted ACM certificate):

const client = new Memcache({
  nodes: ['my-cache-xxxxxx.serverless.use1.cache.amazonaws.com:11211'],
  tls: true,
});

Custom certificate authorities

Pass any tls.connect() options — private CAs, client certificates, servername overrides — as an object:

import { readFileSync } from 'node:fs';

const client = new Memcache({
  nodes: ['memcached-internal:11211'],
  tls: {
    ca: readFileSync('/etc/ssl/private-ca.pem'),
    // cert, key, servername, ... are passed through to tls.connect()
  },
});

Auto Discovery with TLS

Auto Discovery uses the same client-level tls (and sasl) options as data nodes, including the configuration-endpoint connection. Set tls: true (or a CA options object) when the cluster requires in-transit encryption:

const client = new Memcache({
  nodes: [],
  tls: true,
  autoDiscover: {
    enabled: true,
    configEndpoint: 'my-cluster.cfg.use1.cache.amazonaws.com:11211',
  },
});

A memcaches:// configuration endpoint also enables TLS for discovery. When discovery returns a DNS hostname plus an IP, the client connects to the IP (stable node IDs) and sets TLS SNI/servername to the hostname so certificate verification matches ElastiCache node certs.

TLS and SASL together

TLS and SASL compose: the TCP handshake completes, then SASL PLAIN runs on the encrypted socket. Use both for ElastiCache clusters that require in-transit encryption and AUTH. SASL-enabled servers still require the binary protocol after authentication (see SASL Authentication).

const client = new Memcache({
  nodes: ['my-cluster.use1.cache.amazonaws.com:11211'],
  tls: true,
  sasl: { username: 'user', password: 'token' },
});

await client.connect();
const node = client.nodes[0];
await node.binarySet('mykey', 'hello');

TLS Server Configuration

To run memcached with TLS:

  1. Build or use an image with TLS — memcached 1.5.13+ (--enable-tls); the official Docker image supports it since 1.5.21

  2. Provide a certificate chain and key

  3. Start memcached with -Z:

    memcached -Z \
      -o ssl_chain_cert=/path/to/server_crt.pem \
      -o ssl_key=/path/to/server_key.pem \
      -o ssl_session_cache
    

    ElastiCache Serverless is ASCII-only; a local stand-in is:

    memcached -Z -B ascii -U 0 \
      -o ssl_chain_cert=/path/to/server_crt.pem \
      -o ssl_key=/path/to/server_key.pem
    

For more details, see the memcached TLS documentation.

Benchmarks

Tables below are auto-generated by pnpm benchmark:readme. Do not edit between the <!-- BENCHMARK:* --> markers.

Set, Get and Delete compares this client with other Node.js memcached clients on single-key and multi-key tasks run one at a time in random order, and the next section compares them under load and on multi-gets. The other tables show how this client behaves under load; in each one, every row does the same total work in a different shape.

All the tables come from one run of pnpm benchmark:readme, with Node.js 22 on Linux, against the benchmark containers' IPs (see Running the benchmarks). The absolute numbers depend on the machine; the comparisons within each table are what carry over.

Set, Get and Delete (one task at a time, in random order)

Each library runs 1,000 tasks that set, get and delete one key, and 1,000 that set, get and delete 10 keys. The tasks of all 3 libraries go into one queue in random order and run one at a time. The queue runs 5 times, shuffled each time, after an untimed warm-up of 100 tasks of each kind per library. Every result is checked.

Total time per library, the median of the 5 runs (lower is better). 🥇 marks the fastest library in each row, and the percentages compare the others with it. The text protocol has no multi-set or multi-delete, so for 10 keys every library sends its 10 sets, and then its 10 deletes, at once. memjs has no multi-get either, so it does the same with its gets. memcached pools up to 10 connections; the other clients use one.

Tasks memcache (v1.11.0) memjs (v1.3.2) memcached (v2.2.2)
1,000 × set, get, delete 1 key 🥇 290 ms 322 ms (+11%) 333 ms (+15%)
1,000 × set, get, delete 10 keys 🥇 424 ms 566 ms (+33%) 789 ms (+86%)
All 2,000 tasks 🥇 713 ms 893 ms (+25%) 1.12 s (+57%)

Compared with memjs and memcached

Each cell is the median of 3 rounds, with the clients taking turns. 🥇 marks the fastest client in each row, and the percentages compare the other clients with it.

Requests per second, 500 requests per run (higher is better). memcached pools up to 10 connections; the other clients use one.

Workload memcache (v1.11.0) memjs (v1.3.2) memcached (v2.2.2)
gets, 1 in flight 🥇 12.3K 11.8K (-3.5%) 10.5K (-14%)
gets, 10 in flight 🥇 86.8K 65.0K (-25%) 44.6K (-49%)
gets, 100 in flight 🥇 285.6K 104.0K (-64%) 59.7K (-79%)
gets, 500 in flight 🥇 298.1K 109.9K (-63%) 60.0K (-80%)
sets, 1 in flight 🥇 13.2K 11.8K (-11%) 10.7K (-19%)
sets, 10 in flight 🥇 106.8K 64.9K (-39%) 35.6K (-67%)
sets, 100 in flight 🥇 328.9K 92.1K (-72%) 42.9K (-87%)
sets, 500 in flight 🥇 272.1K 93.7K (-66%) 46.3K (-83%)

Time to fetch 10,000 keys in batches (lower is better). memjs has no multi-get, so it sends one get per key, all at once.

Batches memcache (v1.11.0) memjs (v1.3.2) memcached (v2.2.2)
100 × 100 keys 🥇 28 ms 93 ms (+230%) 68 ms (+141%)
10 × 1,000 keys 🥇 18 ms 97 ms (+443%) 132 ms (+642%)
1 × 10,000 keys 🥇 21 ms 146 ms (+606%) 2.63 s (+12649%)

Concurrent Requests (500 per operation)

name summary ops/sec time/op margin samples
500 sets, 100 in flight 🥇 725 1ms ±1.27% 697
500 gets, 500 in flight -16% 606 2ms ±1.71% 571
500 sets, 500 in flight -19% 588 2ms ±1.74% 558
500 gets, 100 in flight -24% 549 2ms ±1.50% 526
500 sets, 10 in flight -70% 216 5ms ±2.38% 209
500 gets, 10 in flight -75% 182 6ms ±1.97% 179
500 sets, 1 in flight -96% 27 37ms ±1.68% 64
500 gets, 1 in flight -96% 26 39ms ±1.91% 64

Multi-Get (10,000 keys per operation)

name summary ops/sec time/op margin samples
10 × gets() of 1,000 keys 🥇 56 18ms ±1.77% 166
1 × gets() of 10,000 keys -14% 48 21ms ±2.02% 143
100 × gets() of 100 keys -30% 39 26ms ±2.39% 116

Large Values (4 MB per operation)

name summary ops/sec time/op margin samples
TCP: 4 × 1 MB sets 🥇 157 7ms ±2.15% 154
TCP: 1 × 4 MB sets -1.6% 155 7ms ±1.68% 153
TCP: 16 × 256 KB sets -4.2% 151 7ms ±1.84% 149
TLS: 4 × 1 MB sets -23% 121 8ms ±2.74% 118
TLS: 1 × 4 MB sets -24% 120 8ms ±1.50% 120
TCP: 1 × 4 MB gets -29% 112 9ms ±2.23% 110
TCP: 16 × 256 KB gets -30% 110 10ms ±4.03% 104
TLS: 16 × 256 KB sets -34% 104 10ms ±3.18% 102
TCP: 4 × 1 MB gets -35% 102 10ms ±2.67% 100
TLS: 4 × 1 MB gets -40% 94 11ms ±4.72% 90
TLS: 1 × 4 MB gets -40% 94 11ms ±5.67% 87
TLS: 16 × 256 KB gets -54% 73 14ms ±3.92% 72

Bursts (60,000 gets per operation)

name summary ops/sec time/op margin samples
6 × 10,000 concurrent gets 🥇 4 268ms ±6.48% 19
2 × 30,000 concurrent gets -8.3% 4 292ms ±7.15% 18
1 × 60,000 concurrent gets -8.4% 4 293ms ±7.67% 18

Cold Start

concurrent first requests sockets opened
50 1

Running the benchmarks

Each table has its own script: pnpm benchmark:set-get, benchmark:compare, benchmark:concurrency, benchmark:multi-get, benchmark:large-values, benchmark:bursts and benchmark:cold-start. pnpm benchmark runs them all, and pnpm benchmark:readme runs them and updates the tables above; pass section ids, such as pnpm benchmark:readme compare, to update only those tables. They run against dedicated memcached containers that the test services don't start:

pnpm benchmark:services:start
pnpm benchmark
pnpm benchmark:services:stop

To target other servers, set MEMCACHE_BENCH_HOST and MEMCACHE_BENCH_PORT, plus MEMCACHE_BENCH_TLS_HOST and MEMCACHE_BENCH_TLS_PORT for the TLS measurements. TLS trusts the test CA by default. For a server with its own certificate, set MEMCACHE_BENCH_TLS_CA to its CA bundle, and MEMCACHE_BENCH_TLS_SERVERNAME if the certificate's name differs from the host. Set MEMCACHE_BENCH_SKIP_TLS=1 to leave TLS out.

On Linux, published Docker ports go through docker-proxy, which adds per-packet overhead. For numbers closer to a real network, use the containers' IPs with port 11211:

MEMCACHE_BENCH_HOST=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' memcached-server-bench) \
MEMCACHE_BENCH_PORT=11211 \
MEMCACHE_BENCH_TLS_HOST=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' memcached-server-bench-tls) \
MEMCACHE_BENCH_TLS_PORT=11211 \
pnpm benchmark

Contributing

Please read our Contributing Guidelines and also our Code of Conduct.

License and Copyright

MIT & Copyright (c) Jared Wray

Contributors

Changelog

v1.12.0 October 06, 2026

memcache@1.12.0 — 2026-10-06

Add maxPendingCommands, fix connection and timeout behavior, and speed up commands.

Features

  • add `maxPendingCommands` to fail requests fast under overload (#162)

    ```javascript
    const client = new Memcache({
    nodes: ['localhost:11211'],
    maxPendingCommands: 1000, // default 0: no limit
    });
    ```

    While a node has that many requests waiting for a response or for its connection to open, a new request to it fails at once instead of waiting: `set()` resolves `false` and `get()` resolves `undefined` on an open connection; while the connection is being opened, single-key commands reject. Refused requests are not retried, and `quit()` still goes out. `MemcacheNode` gains `connectForRequest()`, which the client's commands use to wait for a connection.

Bug Fixes

  • give concurrent binary/SASL requests their own responses: three concurrent `binaryGet()`s could all return the first key's value (#150)
  • open one connection per node and scope socket handlers to their socket: 50 concurrent requests on a cold client opened 50 sockets, and a replaced socket could close or feed data into the live one (#151)
  • time out commands the server doesn't answer, and keep idle connections open (#152)
  • fix a crash when a `hit` or `miss` listener closes the connection, such as `client.on("hit", () => client.disconnect())` (#160)
  • give the site favicon a transparent background (#146)

Performance

  • coalesce socket writes per tick: one write per batch of pipelined commands instead of one per command (#153)
  • find multi-get misses in linear time (#154)
  • buffer large values without re-copying them on every chunk (#155)
  • take commands off the queue in O(1) (#156)
  • write large storage values (64 KB and up) without copying them (#157)
  • look up a key's node without allocating, and skip hashing with one node (#158)
  • send commands without extra async layers (#159)
  • parse replies without slicing or decoding each line (#160)
  • build each binary packet in one allocation (#161)

The same benchmark files run against 1.11.0 and this branch in one session, taking turns (time per operation, so lower is better):

Benchmark 1.11.0 1.12.0 Speedup
500 gets, 100 in flight 5.8 ms 1.9 ms 3.1×
500 sets, 100 in flight 5.0 ms 1.6 ms 3.2×
500 gets, 10 in flight 7.6 ms 5.4 ms 1.4×
500 gets, 1 in flight 41 ms 40 ms unchanged (one round trip at a time)
`gets()` of 10,000 keys, one batch 1.69 s 22 ms 77×
`gets()` of 10,000 keys, 10 batches 228 ms 19 ms 12×
60,000 gets in one burst 6.7 s 296 ms 23×
get of a 4 MB value, TLS 105 ms 10 ms 10.6×
get of a 4 MB value, TCP 31 ms 8.5 ms 3.6×
4 sets of 1 MB values, TCP 8.5 ms 6.2 ms 1.4×
1,000 tasks setting, getting and deleting 10 keys, one at a time 519 ms 412 ms 1.26×

In the README's comparison with memjs and memcached, 1.12.0 is now the fastest client in every row. In the same session, 1.11.0 trailed memjs on gets with 100 or more in flight, on sets with 500, and on multi-gets of 1,000 or more keys per batch.

Behavior changes

  • `timeout` (#152) is now a connect timeout plus a deadline for pending commands, not an idle timer. Idle connections stay open, where before they were dropped after `timeout`. When pending commands get no response within `timeout`, the node emits `timeout`, fails them and closes the connection, even while the client keeps writing; the next command reconnects. Setting `client.timeout` also applies to commands already waiting.
  • `node.commandQueue` (#156) returns a copy, so changing it no longer changes the queue.
  • `getNodesByKey()` with `ModulaHash` (#158) returns frozen arrays, as `KetamaHash` already did, so mutating the result throws.
  • `execute()` and `node.command()` (#159) are no longer `async` functions. They return the same promises, and on a connected node without hooks a command is written before the call returns.
  • Multi-get `hit` and `miss` events (#160) fire after the multi-get's promise is settled. They still fire before code awaiting it continues.
  • `CommandOptions.data` (#157) is new: a storage command can pass its value separately from the command line.

Documentation

  • add the September 2026 hardening and performance plan (#147)
  • regenerate the README benchmark tables in one session (#163)

Internal

  • run flush tests against a dedicated memcached server (#148)
  • add a performance benchmark suite: set/get/delete against memjs and memcached, concurrency, multi-get, large values, bursts and cold start (#149)
  • sync defense-in-depth files from agentic (#164)
  • upgrade Biome to 2.5.14 (#165)
  • upgrade Vitest to 5.0.2 (#166)
  • keep the why-is-node-running override (#167)
  • upgrade TypeScript and build tooling (#168)
  • upgrade pnpm to 12.8.1 (#169)
  • upgrade GitHub Actions (#170)
  • upgrade the Dev Container image (#171)
  • upgrade docula to 3.0.1 (#172)
  • upgrade tinybench to 6.2.0 (#173)
  • upgrade wrangler to 4.143.0 (#174)
  • upgrade hashery to 3.0.2 (#175)
  • upgrade hookified to 3.0.4 (#176)
  • expand CODEOWNERS paths (#177)
  • bootstrap Safe Chain for Claude Code (#178)
  • use kebab-case workflow names (#179)
  • record the workflow-names merge (#180)
  • record lockdown and cloud setup (#181)

Contributors

  • @claude (55)
  • @jaredwray (19)

Full List of Changes

  • fix: give the site favicon a transparent background by @jaredwray in #146
  • docs: add September 2026 hardening and performance plan by @jaredwray in #147
  • test: run flush tests against a dedicated memcached server by @jaredwray in #148
  • chore: add a performance benchmark suite by @jaredwray in #149
  • fix: give concurrent binary/SASL requests their own responses by @jaredwray in #150
  • fix: open one connection per node and scope socket handlers by @jaredwray in #151
  • fix: time out commands the server doesn't answer, keep idle connections by @jaredwray in #152
  • perf: coalesce socket writes per tick by @jaredwray in #153
  • perf: find multi-get misses in linear time; bench: compare with memjs and memcached by @jaredwray in #154
  • perf: buffer large values without re-copying; bench: random-order client comparison and README refresh by @jaredwray in #155
  • perf: take commands off the queue in O(1) by @jaredwray in #156
  • perf: write large storage values without copying them by @jaredwray in #157
  • perf: look up a key's node without allocating, and skip hashing with one node by @jaredwray in #158
  • perf: send commands without extra async layers by @jaredwray in #159
  • perf: parse replies without slicing or decoding each line, and fix a listener crash by @jaredwray in #160
  • perf: build each binary packet in one allocation by @jaredwray in #161
  • feat: add maxPendingCommands to fail requests fast under overload by @jaredwray in #162
  • docs: regenerate the README benchmark tables and draft the 1.12.0 release notes by @jaredwray in #163
  • mono - chore: sync defense-in-depth files from agentic by @jaredwray in #164
  • mono - chore: upgrade code quality dependencies by @jaredwray in #165
  • mono - chore: upgrade Vitest by @jaredwray in #166
  • mono - chore: keep why-is-node-running override by @jaredwray in #167
  • mono - chore: upgrade TypeScript and build tooling by @jaredwray in #168
  • mono - chore: upgrade pnpm by @jaredwray in #169
  • mono - chore: upgrade GitHub Actions by @jaredwray in #170
  • mono - chore: upgrade Dev Container images by @jaredwray in #171
  • mono - chore: upgrade docula by @jaredwray in #172
  • mono - chore: upgrade tinybench by @jaredwray in #173
  • mono - chore: upgrade wrangler by @jaredwray in #174
  • mono - chore: upgrade hashery by @jaredwray in #175
  • mono - chore: upgrade hookified by @jaredwray in #176
  • mono - chore: defense - expand CODEOWNERS paths by @jaredwray in #177
  • mono - chore: defense - bootstrap Safe Chain for Claude Code by @jaredwray in #178
  • mono - chore: defense - use kebab-case workflow names by @jaredwray in #179
  • mono - chore: defense - record workflow-names merge by @jaredwray in #180
  • mono - chore: defense - record lockdown and cloud setup by @jaredwray in #181

Full diff: https://github.com/jaredwray/memcache/compare/v1.11.0...v1.12.0

v1.11.0 September 06, 2026

memcache@1.11.0 — 2026-09-06

TLS Auto Discovery and TLS+SASL composition, plus pnpm 12 and dependency maintenance.

Features

  • bring TLS to SASL-level support (`769ac99`, #134)

    ```javascript
    const client = new Memcache({
    nodes: [],
    tls: true,
    autoDiscover: {
    enabled: true,
    configEndpoint: 'my-cluster.cfg.use1.cache.amazonaws.com:11211',
    },
    });
    ```

    ```javascript
    const client = new Memcache({
    nodes: ['my-cluster.use1.cache.amazonaws.com:11211'],
    tls: true,
    sasl: { username: 'user', password: 'token' },
    });
    await client.connect();
    const node = client.nodes[0];
    node.tlsEnabled; // true
    await node.binarySet('mykey', 'hello');
    ```

Bug Fixes

  • create Cloudflare Pages project on first site deploy (`edbe105`, #133)

Internal

  • upgrade code quality dependencies — biome 2.5.11, vitest 4.1.11, faker 10.6.0 (`b992188`, #135)
  • upgrade TypeScript and build tooling — tsx 4.23.13 (`ac2e6f6`, #136)
  • upgrade pnpm to 12.1.0 with Corepack sha512 pin; `engines.pnpm` is now `>=12` (`d311970`, #137)
  • upgrade GitHub Actions (`153a774`, #138)
  • pin Dev Container image digest (`7b150d5`, #139)
  • upgrade tinybench to 6.1.4 (`b68f3f1`, #140)
  • upgrade wrangler to 4.127.1 (`42469e7`, #141)
  • upgrade docula to 3.0.0 (`29c7244`, #142)
  • upgrade hookified to 3.0.3 (`9383e6a`, #143)
  • pin memcached 1.6.45 test-service image digest (`dae908d`, #144)

Contributors

  • @jaredwray (12)

Full List of Changes

  • fix: create Cloudflare Pages project on first site deploy by @jaredwray in #133
  • feat: bring TLS to SASL-level support by @jaredwray in #134
  • root - chore: upgrade code quality dependencies by @jaredwray in #135
  • root - chore: upgrade TypeScript and build tooling by @jaredwray in #136
  • root - chore: upgrade package manager (breaking) by @jaredwray in #137
  • root - chore: upgrade GitHub Actions by @jaredwray in #138
  • root - chore: pin Dev Container images by @jaredwray in #139
  • root - chore: upgrade tinybench by @jaredwray in #140
  • root - chore: upgrade wrangler by @jaredwray in #141
  • root - chore: upgrade docula (breaking) by @jaredwray in #142
  • root - chore: upgrade hookified by @jaredwray in #143
  • root - chore: pin Docker memcached service image by @jaredwray in #144

Full diff: https://github.com/jaredwray/memcache/compare/v1.10.0...v1.11.0

v1.10.0 August 19, 2026

memcache@1.10.0 — 2026-08-19

Adds TLS connections (`tls` / `memcaches://`), publishes memcachejs.org, and hardens CI/publish.

Features

  • add TLS support (`7a822b9`, #115)

    ```javascript
    import { Memcache } from 'memcache';

    const client = new Memcache({
    nodes: ['my-cache.example.com:11211'],
    tls: true, // or a tls.ConnectionOptions object, e.g. { ca }
    });
    ```

    ```javascript
    // Per-node TLS without a client-level tls option
    const client = new Memcache('memcaches://my-cache.example.com:11211');
    ```

  • generate memcachejs.org site with Docula (`1f0507e`, #104)

Bug Fixes

  • emit `memcaches://` from `MemcacheNode.uri` when TLS is enabled so the URI round-trips through `parseUri()` / `addNode()` (`246dd3d`, #122)

Documentation

  • add Cursor Cloud dev environment setup notes to AGENTS.md (`fddc21e`, #103)

Internal

  • scaffold security docs (`b8203e5`, #105)
  • add CODEOWNERS (`a61167b`, #106)
  • bootstrap Aikido Safe Chain (`0fbd4e0`, #107)
  • set pnpm 7-day cooldown (`3121f16`, #108)
  • block pnpm lifecycle scripts (`367c832`, #109)
  • block exotic pnpm subdeps (`56d201b`, #110)
  • freeze CI installs behind Socket Firewall (`8b4058b`, #111)
  • pin GitHub Actions to SHAs (`0351c0f`, #113)
  • lint workflows with zizmor (`d74fefc`, #116)
  • stage npm releases from CI (`5cd58cf`, #117)
  • gate staged npm publish on Aikido scan-release (`27379f4`, #118)
  • record repository lockdown (`e87b6f9`, #119)
  • enable pnpm `trustPolicy: no-downgrade` (`1238008`, #120)
  • remove writr override (`5d86e40`, #123)
  • upgrade `@biomejs/biome` to 2.5.8 (`73045c9`, #124)
  • upgrade TypeScript build tooling — tsx 4.23.12, tsdown 0.22.14, @types/node 24.13.3 (`e6bb669`, #125)
  • upgrade pnpm to 11.21.0 (`a7067cb`, #126)
  • upgrade tinybench to 6.1.3 (`2a24f95`, #127)
  • upgrade wrangler to 4.121.0 (`a69a878`, #128)
  • upgrade `hashery` runtime dependency to 3.0.1 (`2da196b`, #129)
  • upgrade `hookified` runtime dependency to 3.0.2 (`1a4969a`, #130)
  • upgrade memcached test-service image to 1.6.45 (`5885484`, #131)

Contributors

  • @jaredwray (23)
  • @marceloboeira (1)

Full List of Changes

  • docs: add Cursor Cloud dev environment setup notes to AGENTS.md by @jaredwray in #103
  • feat: generate memcachejs.org site with Docula by @jaredwray in #104
  • memcache - chore: defense - scaffold security docs by @jaredwray in #105
  • memcache - chore: defense - add CODEOWNERS by @jaredwray in #106
  • memcache - chore: defense - bootstrap Aikido Safe Chain by @jaredwray in #107
  • memcache - chore: defense - set pnpm 7-day cooldown by @jaredwray in #108
  • memcache - chore: defense - block pnpm lifecycle scripts by @jaredwray in #109
  • memcache - chore: defense - block exotic pnpm subdeps by @jaredwray in #110
  • memcache - chore: defense - freeze CI installs behind sfw by @jaredwray in #111
  • memcache - chore: defense - pin GitHub Actions to SHAs by @jaredwray in #113
  • memcache - chore: defense - lint workflows with zizmor by @jaredwray in #116
  • memcache - chore: defense - stage npm releases from CI by @jaredwray in #117
  • memcache - chore: defense - gate staged npm publish on Aikido scan-release by @jaredwray in #118
  • memcache - chore: defense - record repository lockdown by @jaredwray in #119
  • memcache - chore: defense - enable pnpm trustPolicy no-downgrade by @jaredwray in #120
  • feat: add TLS support by @marceloboeira in #115
  • fix: emit memcaches:// from MemcacheNode.uri when TLS is enabled by @jaredwray in #122
  • root - chore: remove writr override by @jaredwray in #123
  • root - chore: upgrade code quality dependencies by @jaredwray in #124
  • root - chore: upgrade TypeScript and build tooling by @jaredwray in #125
  • root - chore: upgrade package manager by @jaredwray in #126
  • root - chore: upgrade tinybench by @jaredwray in #127
  • root - chore: upgrade wrangler by @jaredwray in #128
  • root - chore: upgrade hashery by @jaredwray in #129
  • root - chore: upgrade hookified by @jaredwray in #130
  • root - chore: upgrade Docker memcached service image by @jaredwray in #131

Full diff: https://github.com/jaredwray/memcache/compare/v1.9.0...v1.10.0

Full Changelog →