
Security News
Node.js Considers Public Workflow for Security Reports Amid AI-Driven Surge
Node.js is debating whether AI-driven security report volume warrants moving more vulnerability reports into public workflows.
@oncely/core
Advanced tools
Core idempotency library with memory storage adapter. Provides the lean kernel with full TypeScript support.
npm install @oncely/core
import { oncely, MemoryStorage } from '@oncely/core';
// Configure globally
oncely.configure({ ttl: '1h' });
// Create an instance
const idempotency = oncely.createInstance({
storage: new MemoryStorage(),
});
// Run with idempotency
const result = await idempotency.run({
key: 'unique-request-id',
handler: async () => {
return await createOrder({ amount: 100 });
},
});
console.log(result.data); // { id: 'ord_123' }
console.log(result.cached); // false (first request)
console.log(result.createdAt); // 1704888000000
oncely NamespaceGlobal namespace for configuration and instance creation.
// Configure global defaults
oncely.configure({
storage: new MemoryStorage(),
ttl: '24h',
debug: false,
});
// Get current configuration
const config = oncely.getConfig();
// Reset configuration
oncely.resetConfig();
// Get default storage
const storage = oncely.getDefaultStorage();
// Create instance with merged config
const instance = oncely.createInstance({
ttl: '1h', // Override global ttl
});
// Constants
oncely.HEADER; // 'Idempotency-Key'
oncely.HEADER_REPLAY; // 'Idempotency-Replay'
idempotency.run(options)Execute a handler with idempotency protection.
interface RunOptions<T = any> {
key: string; // Idempotency key (required)
hash?: string; // Request hash for mismatch detection
handler: () => Promise<T>; // Async function to execute
ttl?: number | string; // Override global TTL
}
interface RunResult<T = any> {
data: T; // Handler return value
cached: boolean; // Was this a cache hit?
status: 'created' | 'hit'; // Result status
createdAt: number; // Timestamp of original request
}
const result = await idempotency.run({
key: 'order-123',
hash: hashObject(req.body),
handler: async () => {
const order = await createOrder(req.body);
return order;
},
ttl: '2h',
});
oncely.configure({
// Storage adapter (required)
storage: new MemoryStorage(),
// Time-to-live for cached responses
// Accepts: milliseconds (number) or string ('1h', '30m', '5s')
ttl: '24h',
// Enable debug logging
debug: false,
// Callbacks
onHit: (key, response) => {
console.log(`Cache hit for key: ${key}`);
},
onMiss: (key) => {
console.log(`Cache miss for key: ${key}`);
},
onConflict: (key, error) => {
console.log(`Conflict for key: ${key}`);
},
});
const instance = oncely.createInstance({
storage: redis(),
ttl: '1h',
onHit: (key) => analytics.track('cache_hit', { key }),
});
All errors are instances of IdempotencyError and include a statusCode.
import {
IdempotencyError,
MissingKeyError,
ConflictError,
MismatchError,
StorageError,
} from '@oncely/core';
try {
const result = await idempotency.run({ key, handler });
} catch (error) {
if (error instanceof MissingKeyError) {
// status: 400
// Idempotency key is required but not provided
return new Response('Idempotency-Key header required', { status: 400 });
}
if (error instanceof ConflictError) {
// status: 409
// Request with this key is already in progress
return new Response('Request in progress', {
status: 409,
headers: { 'Retry-After': String(error.retryAfter ?? 30) },
});
}
if (error instanceof MismatchError) {
// status: 422
// Same key used with different request body
return new Response('Idempotency key mismatch', {
status: 422,
headers: {
'Content-Type': 'application/problem+json',
},
body: JSON.stringify({
type: 'https://oncely.dev/errors/mismatch',
title: 'Idempotency Key Mismatch',
detail: `Key was previously used with different request body`,
existingHash: error.existingHash,
providedHash: error.providedHash,
}),
});
}
if (error instanceof StorageError) {
// status: 500
// Storage adapter failed
console.error('Storage error:', error);
return new Response('Storage error', { status: 500 });
}
// Other errors
throw error;
}
import { generateKey, composeKey } from '@oncely/core';
// Generate UUID-based key
const key = generateKey();
// => '550e8400-e29b-41d4-a716-446655440000'
// Compose keys from parts
const key = composeKey('order', orderId, 'attempt', attemptNumber);
// => 'order:123:attempt:1'
import { hashObject } from '@oncely/core';
// Hash request body for mismatch detection
const hash = hashObject(req.body);
// => 'f8e9b4a2c1d5e3f6...'
// Works with any serializable object
const hash = hashObject({
amount: 100,
currency: 'USD',
items: [{ id: 1, qty: 2 }],
});
import { parseTtl } from '@oncely/core';
parseTtl(3600000); // 3600000 (milliseconds)
parseTtl('1h'); // 3600000
parseTtl('30m'); // 1800000
parseTtl('5s'); // 5000
parseTtl('1d'); // 86400000
// Invalid values throw Error
parseTtl('invalid'); // throws
In-memory storage for development and testing.
import { oncely, MemoryStorage } from '@oncely/core';
// Implicit (default)
const idempotency = oncely.createInstance();
// Explicit
const storage = new MemoryStorage();
const idempotency = oncely.createInstance({ storage });
// Shared singleton
import { memory } from '@oncely/core';
const idempotency = oncely.createInstance({ storage: memory });
Pros:
Cons:
For production deployments with ioredis.
npm install @oncely/redis ioredis
import { redis } from '@oncely/redis';
// From environment variable (ONCELY_REDIS_URL)
const storage = redis();
// From explicit URL
const storage = redis('redis://localhost:6379');
// With options
const storage = redis('redis://localhost:6379', {
keyPrefix: 'oncely:',
});
// From existing client
import Redis from 'ioredis';
const client = new Redis(process.env.REDIS_URL);
const storage = new RedisStorage(client, { keyPrefix: 'oncely:' });
See @oncely/redis for full documentation.
For serverless and edge deployments.
npm install @oncely/upstash
import { upstash } from '@oncely/upstash';
// From environment variables
const storage = upstash();
// With explicit config
const storage = upstash({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
See @oncely/upstash for full documentation.
Implement the StorageAdapter interface for custom backends.
import type { StorageAdapter, AcquireResult, StoredResponse } from '@oncely/core';
export class DatabaseStorage implements StorageAdapter {
async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {
// Acquire lock and check for existing response
// Return { status: 'acquired', startedAt: Date.now() }
// Or { status: 'conflict', startedAt, retryAfter }
// Or { status: 'mismatch', existingHash, providedHash }
}
async save(key: string, response: StoredResponse): Promise<void> {
// Save response and release lock
}
async release(key: string): Promise<void> {
// Release lock without saving
}
async delete(key: string): Promise<void> {
// Optional: delete stored response
}
async clear(): Promise<void> {
// Optional: clear all stored responses
}
}
// Usage
const storage = new DatabaseStorage();
const idempotency = oncely.createInstance({ storage });
The library includes testing utilities via @oncely/core/testing.
import { MockStorage, createTestInstance } from '@oncely/core/testing';
// Use MockStorage to track operations
const storage = new MockStorage();
const idempotency = createTestInstance({ storage });
// Run handler
await idempotency.run({
key: 'test-key',
handler: async () => ({ id: 1 }),
});
// Verify operations
const operations = storage.operations;
// [
// { type: 'acquire', key: 'test-key', timestamp: ... },
// { type: 'save', key: 'test-key', timestamp: ... },
// ]
const saveOps = storage.getOperationsForKey('test-key', 'save');
expect(saveOps).toHaveLength(1);
Hooks for monitoring and logging.
oncely.configure({
onHit: (key, response) => {
console.log(`Cache hit: ${key}`);
analytics.track('idempotency_hit', { key });
},
onMiss: (key) => {
console.log(`Cache miss: ${key}`);
analytics.track('idempotency_miss', { key });
},
onConflict: (key, error) => {
console.log(`Conflict: ${key}`);
metrics.increment('idempotency_conflicts');
},
});
Full TypeScript support with strict typing.
interface Oncely {
configure(options: OncelyConfig): void;
getConfig(): OncelyConfig;
resetConfig(): void;
getDefaultStorage(): StorageAdapter;
createInstance(options?: Partial<OncelyOptions>): OncelyInstance;
HEADER: string;
HEADER_REPLAY: string;
}
interface OncelyOptions {
storage: StorageAdapter;
ttl: number | string;
debug: boolean;
onHit?: (key: string, response: StoredResponse) => void;
onMiss?: (key: string) => void;
onConflict?: (key: string, error: ConflictError) => void;
}
interface OncelyInstance {
run<T>(options: RunOptions<T>): Promise<RunResult<T>>;
}
Q: Should I use memory storage in production?
A: Only for single-instance deployments. Use Redis or Upstash for distributed systems.
Q: How do I generate idempotency keys on the client?
A: See @oncely/client package.
Q: Can I customize error responses?
A: Yes, catch errors and format responses as needed. Framework packages (Express, Next.js) handle this automatically.
Q: How do I migrate from another idempotency library?
A: See migration guide in root README.
MIT © stacks0x
import { fromEnv } from 'oncely';
// Read REDIS_URL or UPSTASH_REDIS_REST_URL + TOKEN
const storage = fromEnv();
// With options
const storage = fromEnv({
preferUpstash: true, // Prefer Upstash when both configured
env: {
redisUrl: 'MY_REDIS_URL', // Custom env var names
upstashUrl: 'MY_UPSTASH_URL',
upstashToken: 'MY_UPSTASH_TOKEN',
},
});
redis(url, options?)Quick helper to create Redis storage from a URL.
import { redis } from 'oncely';
const storage = redis('redis://localhost:6379');
const storage = redis(process.env.REDIS_URL!, { keyPrefix: 'myapp:' });
idempotency.run(options)Run an operation with idempotency protection.
const result = await idempotency.run({
key: 'unique-key', // Required: idempotency key
hash: 'request-hash', // Optional: for mismatch detection
handler: async () => {
// Required: your operation
return { id: 1 };
},
});
// result.data - your handler's return value
// result.cached - boolean, was this a cache hit?
// result.status - 'created' | 'hit'
// result.createdAt - when the response was created
All errors extend IdempotencyError and include a statusCode property.
| Error | Status | When |
|---|---|---|
MissingKeyError | 400 | Key is undefined/null |
ConflictError | 409 | Same key already in progress |
MismatchError | 422 | Same key, different hash |
StorageError | 500 | Storage adapter failed |
import { IdempotencyError, ConflictError } from 'oncely';
try {
await idempotency.run({ key, handler });
} catch (err) {
if (err instanceof ConflictError) {
// Return 409 with Retry-After header
return new Response('Request in progress', {
status: 409,
headers: { 'Retry-After': String(err.retryAfter) },
});
}
}
import { generateKey, composeKey, hashObject } from 'oncely';
// Generate a UUID-like key
const key = generateKey(); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// Compose a deterministic key
const key = composeKey('user', userId, 'order', orderId); // "user:123:order:456"
// Hash an object for fingerprinting
const hash = hashObject({ amount: 100, currency: 'usd' });
For development and testing. Used automatically when no storage is configured:
import { oncely, memory, MemoryStorage } from 'oncely';
// Zero-config — uses shared memory
const idempotency = oncely();
// Explicit memory storage
const idempotency = oncely({ storage: memory });
// Custom instance
const myStorage = new MemoryStorage();
const idempotency = oncely({ storage: myStorage });
For production with self-hosted Redis or managed services:
npm install ioredis
import { oncely, redis, ioredis } from 'oncely';
import Redis from 'ioredis';
// Quick setup from URL
const idempotency = oncely({
storage: redis('redis://localhost:6379'),
});
// With existing client
const client = new Redis(process.env.REDIS_URL);
const idempotency = oncely({
storage: ioredis(client, { keyPrefix: 'idempotent:' }),
});
For serverless environments:
npm install @upstash/redis
import { oncely, upstash } from 'oncely';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const idempotency = oncely({
storage: upstash(redis, { keyPrefix: 'idempotent:' }),
});
Implement the StorageAdapter interface:
import type { StorageAdapter } from 'oncely';
const myAdapter: StorageAdapter = {
async acquire(key, hash, ttl) {
// Try to acquire lock, return { acquired: true/false, response?: cached }
},
async save(key, response) {
// Save response, release lock
},
async release(key) {
// Release lock without saving
},
async delete(key) {
// Delete stored response (optional)
},
async clear() {
// Clear all stored responses (optional)
},
};
MIT
FAQs
Idempotency engine for HTTP APIs - ensures operations execute exactly once
The npm package @oncely/core receives a total of 0 weekly downloads. As such, @oncely/core popularity was classified as not popular.
We found that @oncely/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Node.js is debating whether AI-driven security report volume warrants moving more vulnerability reports into public workflows.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.