🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@oncely/core

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@oncely/core - npm Package Compare versions

Package was removed
Sorry, it seems this package was removed from the registry
Comparing version
0.2.2
to
0.3.0
+344
dist/errors-DVBjbOG7.d.cts
/**
* Storage adapter interface for oncely.
* Implement this interface to create custom storage backends.
*/
interface StorageAdapter {
/**
* Attempt to acquire a lock for the given key.
* Returns the current state of the key.
*/
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
/**
* Save a completed response for the given key.
*/
save(key: string, response: StoredResponse): Promise<void>;
/**
* Release a lock without saving a response.
* Called when the handler fails and the request should be retryable.
*/
release(key: string): Promise<void>;
/**
* Delete a key from storage.
* Useful for testing or manual cleanup.
*/
delete(key: string): Promise<void>;
/**
* Clear all keys from storage.
* Useful for testing.
*/
clear(): Promise<void>;
/**
* Maximum payload size in bytes that this adapter can store.
* If undefined, no limit is enforced.
* Responses exceeding this limit will skip caching (warn + continue).
*/
readonly maxPayloadSize?: number;
}
/**
* Result of attempting to acquire a lock.
*/
type AcquireResult = {
status: 'acquired';
} | {
status: 'hit';
response: StoredResponse;
} | {
status: 'conflict';
startedAt: number;
} | {
status: 'mismatch';
existingHash: string;
providedHash: string;
};
/**
* A stored idempotency response.
*/
interface StoredResponse {
/** The response data returned by the handler */
data: unknown;
/** When this response was created */
createdAt: number;
/** The hash of the original request (if provided) */
hash: string | null;
}
/**
* Callback fired when a cached response is returned.
*/
type OnHitCallback = (key: string, response: StoredResponse) => void;
/**
* Callback fired when a new request is processed (cache miss).
*/
type OnMissCallback = (key: string) => void;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
type OnConflictCallback = (key: string) => void;
/**
* Callback fired when an error occurs.
*/
type OnErrorCallback = (key: string, error: Error) => void;
/**
* Reason why caching was skipped.
*/
type SkipReason = 'payload_too_large';
/**
* Details about why caching was skipped.
*/
interface SkipDetails {
/** Size of the payload in bytes */
payloadSize: number;
/** Maximum allowed size in bytes */
maxSize: number;
}
/**
* Callback fired when caching is skipped (operation succeeds but result not cached).
*/
type OnSkipCallback = (key: string, reason: SkipReason, details: SkipDetails) => void;
/**
* Global configuration options for oncely.
*/
interface OncelyConfig {
/** Storage adapter for persisting idempotency records */
storage?: StorageAdapter;
/**
* Time-to-live for idempotency keys.
* Can be a number (milliseconds) or a string like '24h', '7d', '30m'.
* @default '24h'
*/
ttl?: number | string;
/**
* Whether to validate request fingerprints (hash).
* When true, reusing a key with different payload throws MismatchError.
* @default true
*/
fingerprint?: boolean;
/**
* Whether to log debug information.
* @default false
*/
debug?: boolean;
/**
* Callback fired when a cached response is returned.
*/
onHit?: OnHitCallback;
/**
* Callback fired when a new request is processed (cache miss).
*/
onMiss?: OnMissCallback;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
onConflict?: OnConflictCallback;
/**
* Callback fired when an error occurs.
*/
onError?: OnErrorCallback;
/**
* Callback fired when caching is skipped (e.g., payload too large).
* The operation still succeeds, but the result is not cached.
*/
onSkip?: OnSkipCallback;
}
/**
* Options for creating an oncely instance.
*/
interface OncelyOptions extends OncelyConfig {
/** Storage adapter for persisting idempotency records (required for instance) */
storage: StorageAdapter;
}
/**
* Options for running an idempotent operation.
*/
interface RunOptions<T> {
/** Unique idempotency key for this request */
key: string;
/**
* Hash of the request body/params for mismatch detection.
* If provided and a cached response exists with a different hash,
* a MismatchError will be thrown.
*/
hash?: string;
/** The handler function to execute */
handler: () => T | Promise<T>;
}
/**
* Result of running an idempotent operation.
*/
interface RunResult<T> {
/** The data returned by the handler (or cached) */
data: T;
/** Whether this was a cache hit */
cached: boolean;
/** Status of the operation */
status: 'created' | 'hit';
/** When the response was originally created (if cached) */
createdAt?: number;
}
/**
* Oncely idempotency service.
* Ensures operations are executed exactly once per idempotency key.
*/
declare class Oncely {
private readonly storage;
private readonly ttl;
private readonly debug;
private readonly onHit?;
private readonly onMiss?;
private readonly onConflict?;
private readonly onError?;
private readonly onSkip?;
constructor(options: OncelyOptions);
/**
* Run an operation with idempotency protection.
*
* @example
* ```typescript
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => {
* const order = await createOrder(data);
* return order;
* },
* });
*
* if (result.cached) {
* console.log('Returned cached response');
* }
* ```
*/
run<T>(options: RunOptions<T>): Promise<RunResult<T>>;
private log;
}
/**
* Create an oncely idempotency instance.
*
* Options are optional - will use global config or sensible defaults.
*
* @example
* ```typescript
* import { createInstance } from '@oncely/core';
*
* // Zero-config (uses memory storage)
* const idempotency = createInstance();
*
* // With explicit storage
* const idempotency = createInstance({ storage: myRedis });
*
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => createOrder(data),
* });
* ```
*/
declare function createInstance(options?: Partial<OncelyOptions>): Oncely;
/**
* In-memory storage adapter.
* Suitable for development, testing, and single-instance deployments.
*
* **Warning:** Data is lost on restart and not shared across instances.
* Use Redis for production multi-instance deployments.
*/
declare class MemoryStorage implements StorageAdapter {
/**
* Maximum payload size in bytes (default: 5MB).
* Prevents unbounded memory growth from large responses.
*/
readonly maxPayloadSize: number;
private store;
private cleanupInterval;
constructor();
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
save(key: string, response: StoredResponse): Promise<void>;
release(key: string): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
/**
* Stop the cleanup interval.
* Call this when shutting down to prevent memory leaks in tests.
*/
destroy(): void;
private cleanup;
}
/**
* RFC 7807 Problem Details response format.
* @see https://www.rfc-editor.org/rfc/rfc7807
*/
interface ProblemDetails {
/** URI reference identifying the problem type */
type: string;
/** Short human-readable summary */
title: string;
/** HTTP status code */
status: number;
/** Detailed human-readable explanation */
detail: string;
/** URI reference to the specific occurrence (optional) */
instance?: string;
/** Additional properties */
[key: string]: unknown;
}
/**
* Base class for all oncely errors.
* Provides RFC 7807 Problem Details format for HTTP responses.
*/
declare class IdempotencyError extends Error {
/** HTTP status code for this error */
readonly statusCode: number;
/** Error type identifier (URL) */
readonly type: string;
/** Short title for the error */
readonly title: string;
constructor(message: string, statusCode: number, type: string, title: string);
/**
* Convert to RFC 7807 Problem Details format.
*/
toProblemDetails(): ProblemDetails;
/**
* Convert to JSON (RFC 7807 format).
*/
toJSON(): ProblemDetails;
}
/**
* Thrown when an idempotency key is required but not provided.
* HTTP 400 Bad Request
*/
declare class MissingKeyError extends IdempotencyError {
constructor();
}
/**
* Thrown when a request with the same key is already being processed.
* HTTP 409 Conflict
*/
declare class ConflictError extends IdempotencyError {
/** When the in-progress request started */
readonly startedAt: number;
/** Suggested retry delay in seconds */
readonly retryAfter: number;
constructor(startedAt: number);
toProblemDetails(): ProblemDetails;
}
/**
* Thrown when the same idempotency key is used with a different request payload.
* HTTP 422 Unprocessable Content
*/
declare class MismatchError extends IdempotencyError {
/** Hash of the original request */
readonly existingHash: string;
/** Hash of the current request */
readonly providedHash: string;
constructor(existingHash: string, providedHash: string);
}
/**
* Thrown when the storage adapter encounters an error.
* HTTP 500 Internal Server Error
*/
declare class StorageError extends IdempotencyError {
/** The underlying error from the storage adapter */
readonly cause: Error;
constructor(message: string, cause: Error);
}
export { type AcquireResult as A, ConflictError as C, IdempotencyError as I, MemoryStorage as M, type OncelyConfig as O, type ProblemDetails as P, type RunOptions as R, type StorageAdapter as S, MismatchError as a, MissingKeyError as b, createInstance as c, type OnConflictCallback as d, type OnErrorCallback as e, type OnHitCallback as f, type OnMissCallback as g, type OnSkipCallback as h, Oncely as i, type OncelyOptions as j, type RunResult as k, type SkipDetails as l, type SkipReason as m, StorageError as n, type StoredResponse as o };
/**
* Storage adapter interface for oncely.
* Implement this interface to create custom storage backends.
*/
interface StorageAdapter {
/**
* Attempt to acquire a lock for the given key.
* Returns the current state of the key.
*/
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
/**
* Save a completed response for the given key.
*/
save(key: string, response: StoredResponse): Promise<void>;
/**
* Release a lock without saving a response.
* Called when the handler fails and the request should be retryable.
*/
release(key: string): Promise<void>;
/**
* Delete a key from storage.
* Useful for testing or manual cleanup.
*/
delete(key: string): Promise<void>;
/**
* Clear all keys from storage.
* Useful for testing.
*/
clear(): Promise<void>;
/**
* Maximum payload size in bytes that this adapter can store.
* If undefined, no limit is enforced.
* Responses exceeding this limit will skip caching (warn + continue).
*/
readonly maxPayloadSize?: number;
}
/**
* Result of attempting to acquire a lock.
*/
type AcquireResult = {
status: 'acquired';
} | {
status: 'hit';
response: StoredResponse;
} | {
status: 'conflict';
startedAt: number;
} | {
status: 'mismatch';
existingHash: string;
providedHash: string;
};
/**
* A stored idempotency response.
*/
interface StoredResponse {
/** The response data returned by the handler */
data: unknown;
/** When this response was created */
createdAt: number;
/** The hash of the original request (if provided) */
hash: string | null;
}
/**
* Callback fired when a cached response is returned.
*/
type OnHitCallback = (key: string, response: StoredResponse) => void;
/**
* Callback fired when a new request is processed (cache miss).
*/
type OnMissCallback = (key: string) => void;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
type OnConflictCallback = (key: string) => void;
/**
* Callback fired when an error occurs.
*/
type OnErrorCallback = (key: string, error: Error) => void;
/**
* Reason why caching was skipped.
*/
type SkipReason = 'payload_too_large';
/**
* Details about why caching was skipped.
*/
interface SkipDetails {
/** Size of the payload in bytes */
payloadSize: number;
/** Maximum allowed size in bytes */
maxSize: number;
}
/**
* Callback fired when caching is skipped (operation succeeds but result not cached).
*/
type OnSkipCallback = (key: string, reason: SkipReason, details: SkipDetails) => void;
/**
* Global configuration options for oncely.
*/
interface OncelyConfig {
/** Storage adapter for persisting idempotency records */
storage?: StorageAdapter;
/**
* Time-to-live for idempotency keys.
* Can be a number (milliseconds) or a string like '24h', '7d', '30m'.
* @default '24h'
*/
ttl?: number | string;
/**
* Whether to validate request fingerprints (hash).
* When true, reusing a key with different payload throws MismatchError.
* @default true
*/
fingerprint?: boolean;
/**
* Whether to log debug information.
* @default false
*/
debug?: boolean;
/**
* Callback fired when a cached response is returned.
*/
onHit?: OnHitCallback;
/**
* Callback fired when a new request is processed (cache miss).
*/
onMiss?: OnMissCallback;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
onConflict?: OnConflictCallback;
/**
* Callback fired when an error occurs.
*/
onError?: OnErrorCallback;
/**
* Callback fired when caching is skipped (e.g., payload too large).
* The operation still succeeds, but the result is not cached.
*/
onSkip?: OnSkipCallback;
}
/**
* Options for creating an oncely instance.
*/
interface OncelyOptions extends OncelyConfig {
/** Storage adapter for persisting idempotency records (required for instance) */
storage: StorageAdapter;
}
/**
* Options for running an idempotent operation.
*/
interface RunOptions<T> {
/** Unique idempotency key for this request */
key: string;
/**
* Hash of the request body/params for mismatch detection.
* If provided and a cached response exists with a different hash,
* a MismatchError will be thrown.
*/
hash?: string;
/** The handler function to execute */
handler: () => T | Promise<T>;
}
/**
* Result of running an idempotent operation.
*/
interface RunResult<T> {
/** The data returned by the handler (or cached) */
data: T;
/** Whether this was a cache hit */
cached: boolean;
/** Status of the operation */
status: 'created' | 'hit';
/** When the response was originally created (if cached) */
createdAt?: number;
}
/**
* Oncely idempotency service.
* Ensures operations are executed exactly once per idempotency key.
*/
declare class Oncely {
private readonly storage;
private readonly ttl;
private readonly debug;
private readonly onHit?;
private readonly onMiss?;
private readonly onConflict?;
private readonly onError?;
private readonly onSkip?;
constructor(options: OncelyOptions);
/**
* Run an operation with idempotency protection.
*
* @example
* ```typescript
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => {
* const order = await createOrder(data);
* return order;
* },
* });
*
* if (result.cached) {
* console.log('Returned cached response');
* }
* ```
*/
run<T>(options: RunOptions<T>): Promise<RunResult<T>>;
private log;
}
/**
* Create an oncely idempotency instance.
*
* Options are optional - will use global config or sensible defaults.
*
* @example
* ```typescript
* import { createInstance } from '@oncely/core';
*
* // Zero-config (uses memory storage)
* const idempotency = createInstance();
*
* // With explicit storage
* const idempotency = createInstance({ storage: myRedis });
*
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => createOrder(data),
* });
* ```
*/
declare function createInstance(options?: Partial<OncelyOptions>): Oncely;
/**
* In-memory storage adapter.
* Suitable for development, testing, and single-instance deployments.
*
* **Warning:** Data is lost on restart and not shared across instances.
* Use Redis for production multi-instance deployments.
*/
declare class MemoryStorage implements StorageAdapter {
/**
* Maximum payload size in bytes (default: 5MB).
* Prevents unbounded memory growth from large responses.
*/
readonly maxPayloadSize: number;
private store;
private cleanupInterval;
constructor();
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
save(key: string, response: StoredResponse): Promise<void>;
release(key: string): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
/**
* Stop the cleanup interval.
* Call this when shutting down to prevent memory leaks in tests.
*/
destroy(): void;
private cleanup;
}
/**
* RFC 7807 Problem Details response format.
* @see https://www.rfc-editor.org/rfc/rfc7807
*/
interface ProblemDetails {
/** URI reference identifying the problem type */
type: string;
/** Short human-readable summary */
title: string;
/** HTTP status code */
status: number;
/** Detailed human-readable explanation */
detail: string;
/** URI reference to the specific occurrence (optional) */
instance?: string;
/** Additional properties */
[key: string]: unknown;
}
/**
* Base class for all oncely errors.
* Provides RFC 7807 Problem Details format for HTTP responses.
*/
declare class IdempotencyError extends Error {
/** HTTP status code for this error */
readonly statusCode: number;
/** Error type identifier (URL) */
readonly type: string;
/** Short title for the error */
readonly title: string;
constructor(message: string, statusCode: number, type: string, title: string);
/**
* Convert to RFC 7807 Problem Details format.
*/
toProblemDetails(): ProblemDetails;
/**
* Convert to JSON (RFC 7807 format).
*/
toJSON(): ProblemDetails;
}
/**
* Thrown when an idempotency key is required but not provided.
* HTTP 400 Bad Request
*/
declare class MissingKeyError extends IdempotencyError {
constructor();
}
/**
* Thrown when a request with the same key is already being processed.
* HTTP 409 Conflict
*/
declare class ConflictError extends IdempotencyError {
/** When the in-progress request started */
readonly startedAt: number;
/** Suggested retry delay in seconds */
readonly retryAfter: number;
constructor(startedAt: number);
toProblemDetails(): ProblemDetails;
}
/**
* Thrown when the same idempotency key is used with a different request payload.
* HTTP 422 Unprocessable Content
*/
declare class MismatchError extends IdempotencyError {
/** Hash of the original request */
readonly existingHash: string;
/** Hash of the current request */
readonly providedHash: string;
constructor(existingHash: string, providedHash: string);
}
/**
* Thrown when the storage adapter encounters an error.
* HTTP 500 Internal Server Error
*/
declare class StorageError extends IdempotencyError {
/** The underlying error from the storage adapter */
readonly cause: Error;
constructor(message: string, cause: Error);
}
export { type AcquireResult as A, ConflictError as C, IdempotencyError as I, MemoryStorage as M, type OncelyConfig as O, type ProblemDetails as P, type RunOptions as R, type StorageAdapter as S, MismatchError as a, MissingKeyError as b, createInstance as c, type OnConflictCallback as d, type OnErrorCallback as e, type OnHitCallback as f, type OnMissCallback as g, type OnSkipCallback as h, Oncely as i, type OncelyOptions as j, type RunResult as k, type SkipDetails as l, type SkipReason as m, StorageError as n, type StoredResponse as o };
+37
-1

@@ -177,2 +177,7 @@ 'use strict';

var MemoryStorage = class {
/**
* Maximum payload size in bytes (default: 5MB).
* Prevents unbounded memory growth from large responses.
*/
maxPayloadSize = 5 * 1024 * 1024;
store = /* @__PURE__ */ new Map();

@@ -289,3 +294,4 @@ cleanupInterval = null;

onConflict: options?.onConflict ?? config.onConflict,
onError: options?.onError ?? config.onError
onError: options?.onError ?? config.onError,
onSkip: options?.onSkip ?? config.onSkip
};

@@ -295,2 +301,9 @@ }

// src/oncely.ts
function estimatePayloadSize(value) {
try {
return new TextEncoder().encode(JSON.stringify(value)).length;
} catch {
return 0;
}
}
var Oncely = class {

@@ -304,2 +317,3 @@ storage;

onError;
onSkip;
constructor(options) {

@@ -313,2 +327,3 @@ this.storage = options.storage;

this.onError = options.onError;
this.onSkip = options.onSkip;
}

@@ -383,2 +398,23 @@ /**

};
const maxSize = this.storage.maxPayloadSize;
if (maxSize !== void 0 && maxSize > 0) {
const payloadSize = estimatePayloadSize(storedResponse);
if (payloadSize > maxSize) {
this.log(
`Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`
);
this.onSkip?.(key, "payload_too_large", { payloadSize, maxSize });
try {
await this.storage.release(key);
} catch (releaseErr) {
this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);
}
return {
data,
cached: false,
status: "created",
createdAt: now
};
}
}
try {

@@ -385,0 +421,0 @@ await this.storage.save(key, storedResponse);

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/memory.ts","../src/config.ts","../src/oncely.ts","../src/index.ts"],"names":["createHash","randomUUID","memory"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { Oncely, createInstance } from './oncely.js';\nimport { MemoryStorage } from './memory.js';\nimport {\n configure as configureGlobal,\n getConfig,\n resetConfig,\n getDefaultStorage,\n} from './config.js';\nimport { HEADER, HEADER_REPLAY } from './constants.js';\nimport type { StorageAdapter } from './types.js';\n\n/**\n * Oncely namespace object.\n * All oncely functionality is accessed through this namespace.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n *\n * // Configure globally\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * });\n *\n * // Create an instance\n * const instance = oncely.createInstance();\n * const result = await instance.run({\n * key: 'order-123',\n * handler: () => createOrder(data),\n * });\n * ```\n */\nexport const oncely = {\n /**\n * Configure global defaults for oncely.\n * Call this once at application startup.\n */\n configure: configureGlobal,\n\n /**\n * Get the current global configuration.\n */\n getConfig,\n\n /**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\n resetConfig,\n\n /**\n * Get the default storage adapter.\n */\n getDefaultStorage,\n\n /**\n * Create an oncely instance with optional configuration.\n * Uses global config merged with provided options.\n */\n createInstance,\n\n /**\n * Standard header name for idempotency keys.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n HEADER,\n\n /**\n * Header indicating a response was replayed from cache.\n */\n HEADER_REPLAY,\n} as const;\n\n/**\n * Interface for oncely namespace - can be extended via module augmentation.\n */\nexport interface OncelyNamespace {\n readonly configure: typeof configureGlobal;\n readonly getConfig: typeof getConfig;\n readonly resetConfig: typeof resetConfig;\n readonly getDefaultStorage: typeof getDefaultStorage;\n readonly createInstance: typeof createInstance;\n readonly HEADER: typeof HEADER;\n readonly HEADER_REPLAY: typeof HEADER_REPLAY;\n // Allow extension via module augmentation\n [key: string]: unknown;\n}\n\n// Re-export the Oncely class for advanced usage\nexport { Oncely };\n\n// Re-export memory storage\nexport { MemoryStorage };\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n\n// Re-export configuration functions for direct import (backwards compatibility)\nexport { configure, getConfig, resetConfig, getDefaultStorage } from './config.js';\nexport type { GlobalConfig } from './config.js';\n\n// Re-export constants\nexport { HEADER, HEADER_REPLAY } from './constants.js';\n\n// Re-export errors\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n type ProblemDetails,\n} from './errors.js';\n\n// Re-export utilities\nexport {\n parseTtl,\n simpleHash,\n secureHash,\n hashObject,\n hashObjectSecure,\n generateKey,\n composeKey,\n} from './utils.js';\n\n// Re-export types\nexport type {\n StorageAdapter,\n AcquireResult,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n} from './types.js';\n"]}
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/memory.ts","../src/config.ts","../src/oncely.ts","../src/index.ts"],"names":["createHash","randomUUID","memory"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n /**\n * Maximum payload size in bytes (default: 5MB).\n * Prevents unbounded memory growth from large responses.\n */\n readonly maxPayloadSize: number = 5 * 1024 * 1024;\n\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n onSkip: options?.onSkip ?? config.onSkip,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n OnSkipCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Estimate the size of a value in bytes when serialized to JSON.\n */\nfunction estimatePayloadSize(value: unknown): number {\n try {\n return new TextEncoder().encode(JSON.stringify(value)).length;\n } catch {\n // If serialization fails, return 0 to allow normal flow\n // (save() will likely fail anyway)\n return 0;\n }\n}\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n private readonly onSkip?: OnSkipCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n this.onSkip = options.onSkip;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n // Check payload size limit\n const maxSize = this.storage.maxPayloadSize;\n if (maxSize !== undefined && maxSize > 0) {\n const payloadSize = estimatePayloadSize(storedResponse);\n if (payloadSize > maxSize) {\n this.log(\n `Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`\n );\n this.onSkip?.(key, 'payload_too_large', { payloadSize, maxSize });\n\n // Release lock and return without caching\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n }\n }\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { Oncely, createInstance } from './oncely.js';\nimport { MemoryStorage } from './memory.js';\nimport {\n configure as configureGlobal,\n getConfig,\n resetConfig,\n getDefaultStorage,\n} from './config.js';\nimport { HEADER, HEADER_REPLAY } from './constants.js';\nimport type { StorageAdapter } from './types.js';\n\n/**\n * Oncely namespace object.\n * All oncely functionality is accessed through this namespace.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n *\n * // Configure globally\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * });\n *\n * // Create an instance\n * const instance = oncely.createInstance();\n * const result = await instance.run({\n * key: 'order-123',\n * handler: () => createOrder(data),\n * });\n * ```\n */\nexport const oncely = {\n /**\n * Configure global defaults for oncely.\n * Call this once at application startup.\n */\n configure: configureGlobal,\n\n /**\n * Get the current global configuration.\n */\n getConfig,\n\n /**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\n resetConfig,\n\n /**\n * Get the default storage adapter.\n */\n getDefaultStorage,\n\n /**\n * Create an oncely instance with optional configuration.\n * Uses global config merged with provided options.\n */\n createInstance,\n\n /**\n * Standard header name for idempotency keys.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n HEADER,\n\n /**\n * Header indicating a response was replayed from cache.\n */\n HEADER_REPLAY,\n} as const;\n\n/**\n * Interface for oncely namespace - can be extended via module augmentation.\n */\nexport interface OncelyNamespace {\n readonly configure: typeof configureGlobal;\n readonly getConfig: typeof getConfig;\n readonly resetConfig: typeof resetConfig;\n readonly getDefaultStorage: typeof getDefaultStorage;\n readonly createInstance: typeof createInstance;\n readonly HEADER: typeof HEADER;\n readonly HEADER_REPLAY: typeof HEADER_REPLAY;\n // Allow extension via module augmentation\n [key: string]: unknown;\n}\n\n// Re-export the Oncely class for advanced usage\nexport { Oncely };\n\n// Re-export memory storage\nexport { MemoryStorage };\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n\n// Re-export configuration functions for direct import (backwards compatibility)\nexport { configure, getConfig, resetConfig, getDefaultStorage } from './config.js';\nexport type { GlobalConfig } from './config.js';\n\n// Re-export constants\nexport { HEADER, HEADER_REPLAY } from './constants.js';\n\n// Re-export errors\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n type ProblemDetails,\n} from './errors.js';\n\n// Re-export utilities\nexport {\n parseTtl,\n simpleHash,\n secureHash,\n hashObject,\n hashObjectSecure,\n generateKey,\n composeKey,\n} from './utils.js';\n\n// Re-export types\nexport type {\n StorageAdapter,\n AcquireResult,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n OnSkipCallback,\n SkipReason,\n SkipDetails,\n} from './types.js';\n"]}

@@ -1,3 +0,3 @@

import { O as OncelyConfig, S as StorageAdapter, c as createInstance } from './errors-BUehgS6t.cjs';
export { A as AcquireResult, C as ConflictError, I as IdempotencyError, M as MemoryStorage, d as MismatchError, b as MissingKeyError, k as OnConflictCallback, l as OnErrorCallback, i as OnHitCallback, j as OnMissCallback, a as Oncely, g as OncelyOptions, P as ProblemDetails, R as RunOptions, h as RunResult, e as StorageError, f as StoredResponse } from './errors-BUehgS6t.cjs';
import { O as OncelyConfig, S as StorageAdapter, c as createInstance } from './errors-DVBjbOG7.cjs';
export { A as AcquireResult, C as ConflictError, I as IdempotencyError, M as MemoryStorage, a as MismatchError, b as MissingKeyError, d as OnConflictCallback, e as OnErrorCallback, f as OnHitCallback, g as OnMissCallback, h as OnSkipCallback, i as Oncely, j as OncelyOptions, P as ProblemDetails, R as RunOptions, k as RunResult, l as SkipDetails, m as SkipReason, n as StorageError, o as StoredResponse } from './errors-DVBjbOG7.cjs';

@@ -4,0 +4,0 @@ type GlobalConfig = OncelyConfig;

@@ -1,3 +0,3 @@

import { O as OncelyConfig, S as StorageAdapter, c as createInstance } from './errors-BUehgS6t.js';
export { A as AcquireResult, C as ConflictError, I as IdempotencyError, M as MemoryStorage, d as MismatchError, b as MissingKeyError, k as OnConflictCallback, l as OnErrorCallback, i as OnHitCallback, j as OnMissCallback, a as Oncely, g as OncelyOptions, P as ProblemDetails, R as RunOptions, h as RunResult, e as StorageError, f as StoredResponse } from './errors-BUehgS6t.js';
import { O as OncelyConfig, S as StorageAdapter, c as createInstance } from './errors-DVBjbOG7.js';
export { A as AcquireResult, C as ConflictError, I as IdempotencyError, M as MemoryStorage, a as MismatchError, b as MissingKeyError, d as OnConflictCallback, e as OnErrorCallback, f as OnHitCallback, g as OnMissCallback, h as OnSkipCallback, i as Oncely, j as OncelyOptions, P as ProblemDetails, R as RunOptions, k as RunResult, l as SkipDetails, m as SkipReason, n as StorageError, o as StoredResponse } from './errors-DVBjbOG7.js';

@@ -4,0 +4,0 @@ type GlobalConfig = OncelyConfig;

@@ -175,2 +175,7 @@ import { createHash, randomUUID } from 'crypto';

var MemoryStorage = class {
/**
* Maximum payload size in bytes (default: 5MB).
* Prevents unbounded memory growth from large responses.
*/
maxPayloadSize = 5 * 1024 * 1024;
store = /* @__PURE__ */ new Map();

@@ -287,3 +292,4 @@ cleanupInterval = null;

onConflict: options?.onConflict ?? config.onConflict,
onError: options?.onError ?? config.onError
onError: options?.onError ?? config.onError,
onSkip: options?.onSkip ?? config.onSkip
};

@@ -293,2 +299,9 @@ }

// src/oncely.ts
function estimatePayloadSize(value) {
try {
return new TextEncoder().encode(JSON.stringify(value)).length;
} catch {
return 0;
}
}
var Oncely = class {

@@ -302,2 +315,3 @@ storage;

onError;
onSkip;
constructor(options) {

@@ -311,2 +325,3 @@ this.storage = options.storage;

this.onError = options.onError;
this.onSkip = options.onSkip;
}

@@ -381,2 +396,23 @@ /**

};
const maxSize = this.storage.maxPayloadSize;
if (maxSize !== void 0 && maxSize > 0) {
const payloadSize = estimatePayloadSize(storedResponse);
if (payloadSize > maxSize) {
this.log(
`Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`
);
this.onSkip?.(key, "payload_too_large", { payloadSize, maxSize });
try {
await this.storage.release(key);
} catch (releaseErr) {
this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);
}
return {
data,
cached: false,
status: "created",
createdAt: now
};
}
}
try {

@@ -383,0 +419,0 @@ await this.storage.save(key, storedResponse);

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/memory.ts","../src/config.ts","../src/oncely.ts","../src/index.ts"],"names":["memory"],"mappings":";;;AAMO,IAAM,MAAA,GAAS;AAGf,IAAM,aAAA,GAAgB;AAGtB,IAAM,aAAA,GAAgB,2BAAA;;;ACatB,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA;AAAA,EAEjC,UAAA;AAAA;AAAA,EAEA,IAAA;AAAA;AAAA,EAEA,KAAA;AAAA,EAET,WAAA,CAAY,OAAA,EAAiB,UAAA,EAAoB,IAAA,EAAc,KAAA,EAAe;AAC5E,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAA,GAAmC;AACjC,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,QAAQ,IAAA,CAAK,UAAA;AAAA,MACb,QAAQ,IAAA,CAAK;AAAA,KACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAyB;AACvB,IAAA,OAAO,KAAK,gBAAA,EAAiB;AAAA,EAC/B;AACF;AAMO,IAAM,eAAA,GAAN,cAA8B,gBAAA,CAAiB;AAAA,EACpD,WAAA,GAAc;AACZ,IAAA,KAAA;AAAA,MACE,oDAAA;AAAA,MACA,GAAA;AAAA,MACA,GAAG,aAAa,CAAA,YAAA,CAAA;AAAA,MAChB;AAAA,KACF;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF;AAMO,IAAM,aAAA,GAAN,cAA4B,gBAAA,CAAiB;AAAA;AAAA,EAEzC,SAAA;AAAA;AAAA,EAEA,UAAA;AAAA,EAET,YAAY,SAAA,EAAmB;AAC7B,IAAA,MAAM,aAAa,IAAA,CAAK,KAAA,CAAA,CAAO,KAAK,GAAA,EAAI,GAAI,aAAa,GAAI,CAAA;AAC7D,IAAA,KAAA;AAAA,MACE,uEAAA;AAAA,MACA,GAAA;AAAA,MACA,GAAG,aAAa,CAAA,SAAA,CAAA;AAAA,MAChB;AAAA,KACF;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AACjB,IAAA,IAAA,CAAK,UAAA,GAAa,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,UAAU,CAAC,CAAA;AAAA,EACvD;AAAA,EAES,gBAAA,GAAmC;AAC1C,IAAA,OAAO;AAAA,MACL,GAAG,MAAM,gBAAA,EAAiB;AAAA,MAC1B,YAAY,IAAA,CAAK;AAAA,KACnB;AAAA,EACF;AACF;AAMO,IAAM,aAAA,GAAN,cAA4B,gBAAA,CAAiB;AAAA;AAAA,EAEzC,YAAA;AAAA;AAAA,EAEA,YAAA;AAAA,EAET,WAAA,CAAY,cAAsB,YAAA,EAAsB;AACtD,IAAA,KAAA;AAAA,MACE,iEAAA;AAAA,MACA,GAAA;AAAA,MACA,GAAG,aAAa,CAAA,SAAA,CAAA;AAAA,MAChB;AAAA,KACF;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AACZ,IAAA,IAAA,CAAK,YAAA,GAAe,YAAA;AACpB,IAAA,IAAA,CAAK,YAAA,GAAe,YAAA;AAAA,EACtB;AACF;AAMO,IAAM,YAAA,GAAN,cAA2B,gBAAA,CAAiB;AAAA;AAAA,EAE/B,KAAA;AAAA,EAElB,WAAA,CAAY,SAAiB,KAAA,EAAc;AACzC,IAAA,KAAA,CAAM,kBAAkB,OAAO,CAAA,CAAA,EAAI,KAAK,CAAA,EAAG,aAAa,kBAAkB,eAAe,CAAA;AACzF,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AACF;AC3IO,SAAS,SAAS,GAAA,EAA8B;AACrD,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,IAAI,GAAA,CAAI,SAAS,CAAA,EAAG;AAClB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,wBAAwB,GAAG,CAAA,uEAAA;AAAA,KAC7B;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,GAAA,CAAI,MAAA,GAAS,CAAC,CAAA;AAC/B,EAAA,IAAI,SAAS,GAAA,IAAO,IAAA,KAAS,OAAO,IAAA,KAAS,GAAA,IAAO,SAAS,GAAA,EAAK;AAChE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,wBAAwB,GAAG,CAAA,uEAAA;AAAA,KAC7B;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAChC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,wBAAwB,GAAG,CAAA,uEAAA;AAAA,KAC7B;AAAA,EACF;AAEA,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,UAAA,CAAW,CAAC,CAAA;AAClC,IAAA,IAAI,IAAA,GAAO,EAAA,IAAM,IAAA,GAAO,EAAA,EAAI;AAC1B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,wBAAwB,GAAG,CAAA,uEAAA;AAAA,OAC7B;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,QAAA,EAAU,EAAE,CAAA;AAEnC,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,GAAA;AACH,MAAA,OAAO,KAAA,GAAQ,GAAA;AAAA,IACjB,KAAK,GAAA;AACH,MAAA,OAAO,QAAQ,EAAA,GAAK,GAAA;AAAA,IACtB,KAAK,GAAA;AACH,MAAA,OAAO,KAAA,GAAQ,KAAK,EAAA,GAAK,GAAA;AAAA,IAC3B,KAAK,GAAA;AACH,MAAA,OAAO,KAAA,GAAQ,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAAA,IAChC;AACE,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA;AAEnD;AAeO,SAAS,WAAW,GAAA,EAAqB;AAC9C,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA;AAC7B,IAAA,IAAA,GAAA,CAAQ,IAAA,IAAQ,KAAK,IAAA,GAAO,IAAA;AAC5B,IAAA,IAAA,GAAO,IAAA,GAAO,IAAA;AAAA,EAChB;AACA,EAAA,OAAO,IAAA,CAAK,IAAI,IAAI,CAAA,CAAE,SAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA;AACpD;AAQO,SAAS,WAAW,GAAA,EAAqB;AAC9C,EAAA,OAAO,WAAW,QAAQ,CAAA,CAAE,OAAO,GAAG,CAAA,CAAE,OAAO,KAAK,CAAA;AACtD;AAiBO,SAAS,WAAW,GAAA,EAAsB;AAC/C,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,IAAK,WAAA;AACnC,EAAA,OAAO,WAAW,GAAG,CAAA;AACvB;AAQO,SAAS,iBAAiB,GAAA,EAAsB;AACrD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,IAAK,WAAA;AACnC,EAAA,OAAO,WAAW,GAAG,CAAA;AACvB;AAOO,SAAS,WAAA,GAAsB;AACpC,EAAA,OAAO,UAAA,EAAW;AACpB;AAKO,SAAS,cAAc,KAAA,EAAoC;AAChE,EAAA,OAAO,KAAA,CAAM,KAAK,GAAG,CAAA;AACvB;;;ACrHO,IAAM,gBAAN,MAA8C;AAAA,EAC3C,KAAA,uBAAY,GAAA,EAA0B;AAAA,EACtC,eAAA,GAAyD,IAAA;AAAA,EAEjE,WAAA,GAAc;AAEZ,IAAA,IAAA,CAAK,kBAAkB,WAAA,CAAY,MAAM,IAAA,CAAK,OAAA,IAAW,GAAM,CAAA;AAE/D,IAAA,IAAI,IAAA,CAAK,gBAAgB,KAAA,EAAO;AAC9B,MAAA,IAAA,CAAK,gBAAgB,KAAA,EAAM;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,OAAA,CAAQ,GAAA,EAAa,IAAA,EAAqB,GAAA,EAAqC;AACnF,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAEjC,IAAA,IAAI,QAAA,IAAY,QAAA,CAAS,SAAA,IAAa,GAAA,EAAK;AACzC,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,QAAA,GAAW,MAAA;AAAA,IACb;AAGA,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,eAAe,QAAA,CAAS,IAAA;AAE9B,MAAA,IAAI,IAAA,IAAQ,YAAA,IAAgB,YAAA,KAAiB,IAAA,EAAM;AACjD,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,UAAA;AAAA,UACR,YAAA;AAAA,UACA,YAAA,EAAc;AAAA,SAChB;AAAA,MACF;AAGA,MAAA,IAAI,QAAA,CAAS,WAAW,WAAA,EAAa;AACnC,QAAA,MAAM,WAAW,QAAA,CAAS,QAAA;AAC1B,QAAA,IAAI,QAAA,EAAU;AACZ,UAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,QAAA,EAAS;AAAA,QACnC;AAAA,MACF;AAGA,MAAA,IAAI,QAAA,CAAS,WAAW,aAAA,EAAe;AACrC,QAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,SAAA,EAAW,SAAS,SAAA,EAAU;AAAA,MAC7D;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,KAAA,CAAM,IAAI,GAAA,EAAK;AAAA,MAClB,MAAA,EAAQ,aAAA;AAAA,MACR,IAAA;AAAA,MACA,SAAA,EAAW,GAAA;AAAA,MACX,WAAW,GAAA,GAAM;AAAA,KAClB,CAAA;AAED,IAAA,OAAO,EAAE,QAAQ,UAAA,EAAW;AAAA,EAC9B;AAAA,EAEA,MAAM,IAAA,CAAK,GAAA,EAAa,QAAA,EAAyC;AAC/D,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACnC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,MAAA,GAAS,WAAA;AAClB,MAAA,QAAA,CAAS,QAAA,GAAW,QAAA;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,GAAA,EAA4B;AACxC,IAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,EACvB;AAAA,EAEA,MAAM,OAAO,GAAA,EAA4B;AACvC,IAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,EACvB;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,GAAgB;AACd,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,aAAA,CAAc,KAAK,eAAe,CAAA;AAClC,MAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,OAAA,GAAgB;AACtB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,MAAM,CAAA,IAAK,KAAK,KAAA,EAAO;AACtC,MAAA,IAAI,MAAA,CAAO,aAAa,GAAA,EAAK;AAC3B,QAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;AAasC,IAAI,aAAA;;;ACzH1C,IAAI,eAA6B,EAAC;AAGlC,IAAI,oBAAA,GAA6C,IAAA;AAmB1C,SAAS,UAAU,MAAA,EAA4B;AACpD,EAAA,YAAA,GAAe,EAAE,GAAG,MAAA,EAAO;AAC7B;AAKO,SAAS,SAAA,GAA0B;AACxC,EAAA,OAAO,YAAA;AACT;AAMO,SAAS,WAAA,GAAoB;AAClC,EAAA,YAAA,GAAe,EAAC;AAChB,EAAA,oBAAA,GAAuB,IAAA;AACzB;AAMO,SAAS,iBAAA,GAAoC;AAClD,EAAA,IAAI,aAAa,OAAA,EAAS;AACxB,IAAA,OAAO,YAAA,CAAa,OAAA;AAAA,EACtB;AAEA,EAAA,IAAI,CAAC,oBAAA,EAAsB;AACzB,IAAA,oBAAA,GAAuB,IAAI,aAAA,EAAc;AAAA,EAC3C;AACA,EAAA,OAAO,oBAAA;AACT;AAKO,SAAS,eAAe,OAAA,EAAiD;AAC9E,EAAA,MAAM,SAAS,SAAA,EAAU;AACzB,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,OAAA,EAAS,OAAA,IAAW,MAAA,CAAO,WAAW,iBAAA,EAAkB;AAAA,IACjE,GAAA,EAAK,OAAA,EAAS,GAAA,IAAO,MAAA,CAAO,GAAA,IAAO,KAAA;AAAA,IACnC,WAAA,EAAa,OAAA,EAAS,WAAA,IAAe,MAAA,CAAO,WAAA,IAAe,IAAA;AAAA,IAC3D,KAAA,EAAO,OAAA,EAAS,KAAA,IAAS,MAAA,CAAO,KAAA,IAAS,KAAA;AAAA,IACzC,KAAA,EAAO,OAAA,EAAS,KAAA,IAAS,MAAA,CAAO,KAAA;AAAA,IAChC,MAAA,EAAQ,OAAA,EAAS,MAAA,IAAU,MAAA,CAAO,MAAA;AAAA,IAClC,UAAA,EAAY,OAAA,EAAS,UAAA,IAAc,MAAA,CAAO,UAAA;AAAA,IAC1C,OAAA,EAAS,OAAA,EAAS,OAAA,IAAW,MAAA,CAAO;AAAA,GACtC;AACF;;;AC5DO,IAAM,SAAN,MAAa;AAAA,EACD,OAAA;AAAA,EACA,GAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EAEjB,YAAY,OAAA,EAAwB;AAClC,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,GAAA,GAAM,QAAA,CAAS,OAAA,CAAQ,GAAA,IAAO,KAAK,CAAA;AACxC,IAAA,IAAA,CAAK,KAAA,GAAQ,QAAQ,KAAA,IAAS,KAAA;AAC9B,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,UAAA;AAC1B,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,IAAO,OAAA,EAA+C;AAC1D,IAAA,MAAM,EAAE,GAAA,EAAK,IAAA,GAAO,IAAA,EAAM,SAAQ,GAAI,OAAA;AAGtC,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,MAAM,IAAI,eAAA,EAAgB;AAAA,IAC5B;AAEA,IAAA,IAAA,CAAK,GAAA,CAAI,CAAA,wBAAA,EAA2B,GAAG,CAAA,CAAE,CAAA;AAGzC,IAAA,IAAI,aAAA;AACJ,IAAA,IAAI;AACF,MAAA,aAAA,GAAgB,MAAM,IAAA,CAAK,OAAA,CAAQ,QAAQ,GAAA,EAAK,IAAA,EAAM,KAAK,GAAG,CAAA;AAAA,IAChE,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,IAAI,YAAA,CAAa,wBAAA,EAA0B,GAAY,CAAA;AAAA,IAC/D;AAGA,IAAA,QAAQ,cAAc,MAAA;AAAQ,MAC5B,KAAK,KAAA,EAAO;AACV,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,mBAAA,EAAsB,GAAG,CAAA,CAAE,CAAA;AACpC,QAAA,IAAA,CAAK,KAAA,GAAQ,GAAA,EAAK,aAAA,CAAc,QAAQ,CAAA;AACxC,QAAA,OAAO;AAAA,UACL,IAAA,EAAM,cAAc,QAAA,CAAS,IAAA;AAAA,UAC7B,MAAA,EAAQ,IAAA;AAAA,UACR,MAAA,EAAQ,KAAA;AAAA,UACR,SAAA,EAAW,cAAc,QAAA,CAAS;AAAA,SACpC;AAAA,MACF;AAAA,MAEA,KAAK,UAAA,EAAY;AACf,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,kBAAA,EAAqB,GAAG,CAAA,CAAE,CAAA;AACnC,QAAA,MAAM,KAAA,GAAQ,IAAI,aAAA,CAAc,aAAA,CAAc,SAAS,CAAA;AACvD,QAAA,IAAA,CAAK,aAAa,GAAG,CAAA;AACrB,QAAA,IAAA,CAAK,OAAA,GAAU,KAAK,KAAK,CAAA;AACzB,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,MAEA,KAAK,UAAA,EAAY;AACf,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,uBAAA,EAA0B,GAAG,CAAA,CAAE,CAAA;AACxC,QAAA,MAAM,QAAQ,IAAI,aAAA,CAAc,aAAA,CAAc,YAAA,EAAc,cAAc,YAAY,CAAA;AACtF,QAAA,IAAA,CAAK,OAAA,GAAU,KAAK,KAAK,CAAA;AACzB,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,MAEA,KAAK,UAAA,EAAY;AACf,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,uBAAA,EAA0B,GAAG,CAAA,CAAE,CAAA;AACxC,QAAA,IAAA,CAAK,SAAS,GAAG,CAAA;AACjB,QAAA;AAAA,MACF;AAAA;AAIF,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,OAAA,EAAQ;AAC3B,MAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAGrB,MAAA,MAAM,cAAA,GAAiC;AAAA,QACrC,IAAA;AAAA,QACA,SAAA,EAAW,GAAA;AAAA,QACX;AAAA,OACF;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,GAAA,EAAK,cAAc,CAAA;AAC3C,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,wBAAA,EAA2B,GAAG,CAAA,CAAE,CAAA;AAAA,MAC3C,SAAS,GAAA,EAAK;AAEZ,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,iCAAA,EAAoC,GAAG,CAAA,GAAA,EAAM,GAAG,CAAA,CAAE,CAAA;AAAA,MAC7D;AAEA,MAAA,OAAO;AAAA,QACL,IAAA;AAAA,QACA,MAAA,EAAQ,KAAA;AAAA,QACR,MAAA,EAAQ,SAAA;AAAA,QACR,SAAA,EAAW;AAAA,OACb;AAAA,IACF,SAAS,GAAA,EAAK;AAEZ,MAAA,IAAA,CAAK,GAAA,CAAI,CAAA,wBAAA,EAA2B,GAAG,CAAA,gBAAA,CAAkB,CAAA;AACzD,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AAAA,MAChC,SAAS,UAAA,EAAY;AACnB,QAAA,IAAA,CAAK,GAAA,CAAI,CAAA,gCAAA,EAAmC,GAAG,CAAA,GAAA,EAAM,UAAU,CAAA,CAAE,CAAA;AAAA,MACnE;AACA,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,IAAI,OAAA,EAAuB;AACjC,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,SAAA,EAAY,OAAO,CAAA,CAAE,CAAA;AAAA,IACnC;AAAA,EACF;AACF;AAuBO,SAAS,eAAe,OAAA,EAA0C;AACvE,EAAA,OAAO,IAAI,MAAA,CAAO,cAAA,CAAe,OAAO,CAAC,CAAA;AAC3C;;;AChJO,IAAM,MAAA,GAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,SAAA;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAA;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AACF;AA2BO,IAAMA,OAAAA,GAAyB,IAAI,aAAA","file":"index.js","sourcesContent":["/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { Oncely, createInstance } from './oncely.js';\nimport { MemoryStorage } from './memory.js';\nimport {\n configure as configureGlobal,\n getConfig,\n resetConfig,\n getDefaultStorage,\n} from './config.js';\nimport { HEADER, HEADER_REPLAY } from './constants.js';\nimport type { StorageAdapter } from './types.js';\n\n/**\n * Oncely namespace object.\n * All oncely functionality is accessed through this namespace.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n *\n * // Configure globally\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * });\n *\n * // Create an instance\n * const instance = oncely.createInstance();\n * const result = await instance.run({\n * key: 'order-123',\n * handler: () => createOrder(data),\n * });\n * ```\n */\nexport const oncely = {\n /**\n * Configure global defaults for oncely.\n * Call this once at application startup.\n */\n configure: configureGlobal,\n\n /**\n * Get the current global configuration.\n */\n getConfig,\n\n /**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\n resetConfig,\n\n /**\n * Get the default storage adapter.\n */\n getDefaultStorage,\n\n /**\n * Create an oncely instance with optional configuration.\n * Uses global config merged with provided options.\n */\n createInstance,\n\n /**\n * Standard header name for idempotency keys.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n HEADER,\n\n /**\n * Header indicating a response was replayed from cache.\n */\n HEADER_REPLAY,\n} as const;\n\n/**\n * Interface for oncely namespace - can be extended via module augmentation.\n */\nexport interface OncelyNamespace {\n readonly configure: typeof configureGlobal;\n readonly getConfig: typeof getConfig;\n readonly resetConfig: typeof resetConfig;\n readonly getDefaultStorage: typeof getDefaultStorage;\n readonly createInstance: typeof createInstance;\n readonly HEADER: typeof HEADER;\n readonly HEADER_REPLAY: typeof HEADER_REPLAY;\n // Allow extension via module augmentation\n [key: string]: unknown;\n}\n\n// Re-export the Oncely class for advanced usage\nexport { Oncely };\n\n// Re-export memory storage\nexport { MemoryStorage };\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n\n// Re-export configuration functions for direct import (backwards compatibility)\nexport { configure, getConfig, resetConfig, getDefaultStorage } from './config.js';\nexport type { GlobalConfig } from './config.js';\n\n// Re-export constants\nexport { HEADER, HEADER_REPLAY } from './constants.js';\n\n// Re-export errors\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n type ProblemDetails,\n} from './errors.js';\n\n// Re-export utilities\nexport {\n parseTtl,\n simpleHash,\n secureHash,\n hashObject,\n hashObjectSecure,\n generateKey,\n composeKey,\n} from './utils.js';\n\n// Re-export types\nexport type {\n StorageAdapter,\n AcquireResult,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n} from './types.js';\n"]}
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/memory.ts","../src/config.ts","../src/oncely.ts","../src/index.ts"],"names":["memory"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n /**\n * Maximum payload size in bytes (default: 5MB).\n * Prevents unbounded memory growth from large responses.\n */\n readonly maxPayloadSize: number = 5 * 1024 * 1024;\n\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n onSkip: options?.onSkip ?? config.onSkip,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n OnSkipCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Estimate the size of a value in bytes when serialized to JSON.\n */\nfunction estimatePayloadSize(value: unknown): number {\n try {\n return new TextEncoder().encode(JSON.stringify(value)).length;\n } catch {\n // If serialization fails, return 0 to allow normal flow\n // (save() will likely fail anyway)\n return 0;\n }\n}\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n private readonly onSkip?: OnSkipCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n this.onSkip = options.onSkip;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n // Check payload size limit\n const maxSize = this.storage.maxPayloadSize;\n if (maxSize !== undefined && maxSize > 0) {\n const payloadSize = estimatePayloadSize(storedResponse);\n if (payloadSize > maxSize) {\n this.log(\n `Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`\n );\n this.onSkip?.(key, 'payload_too_large', { payloadSize, maxSize });\n\n // Release lock and return without caching\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n }\n }\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { Oncely, createInstance } from './oncely.js';\nimport { MemoryStorage } from './memory.js';\nimport {\n configure as configureGlobal,\n getConfig,\n resetConfig,\n getDefaultStorage,\n} from './config.js';\nimport { HEADER, HEADER_REPLAY } from './constants.js';\nimport type { StorageAdapter } from './types.js';\n\n/**\n * Oncely namespace object.\n * All oncely functionality is accessed through this namespace.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n *\n * // Configure globally\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * });\n *\n * // Create an instance\n * const instance = oncely.createInstance();\n * const result = await instance.run({\n * key: 'order-123',\n * handler: () => createOrder(data),\n * });\n * ```\n */\nexport const oncely = {\n /**\n * Configure global defaults for oncely.\n * Call this once at application startup.\n */\n configure: configureGlobal,\n\n /**\n * Get the current global configuration.\n */\n getConfig,\n\n /**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\n resetConfig,\n\n /**\n * Get the default storage adapter.\n */\n getDefaultStorage,\n\n /**\n * Create an oncely instance with optional configuration.\n * Uses global config merged with provided options.\n */\n createInstance,\n\n /**\n * Standard header name for idempotency keys.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n HEADER,\n\n /**\n * Header indicating a response was replayed from cache.\n */\n HEADER_REPLAY,\n} as const;\n\n/**\n * Interface for oncely namespace - can be extended via module augmentation.\n */\nexport interface OncelyNamespace {\n readonly configure: typeof configureGlobal;\n readonly getConfig: typeof getConfig;\n readonly resetConfig: typeof resetConfig;\n readonly getDefaultStorage: typeof getDefaultStorage;\n readonly createInstance: typeof createInstance;\n readonly HEADER: typeof HEADER;\n readonly HEADER_REPLAY: typeof HEADER_REPLAY;\n // Allow extension via module augmentation\n [key: string]: unknown;\n}\n\n// Re-export the Oncely class for advanced usage\nexport { Oncely };\n\n// Re-export memory storage\nexport { MemoryStorage };\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n\n// Re-export configuration functions for direct import (backwards compatibility)\nexport { configure, getConfig, resetConfig, getDefaultStorage } from './config.js';\nexport type { GlobalConfig } from './config.js';\n\n// Re-export constants\nexport { HEADER, HEADER_REPLAY } from './constants.js';\n\n// Re-export errors\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n type ProblemDetails,\n} from './errors.js';\n\n// Re-export utilities\nexport {\n parseTtl,\n simpleHash,\n secureHash,\n hashObject,\n hashObjectSecure,\n generateKey,\n composeKey,\n} from './utils.js';\n\n// Re-export types\nexport type {\n StorageAdapter,\n AcquireResult,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n OnSkipCallback,\n SkipReason,\n SkipDetails,\n} from './types.js';\n"]}

@@ -7,2 +7,7 @@ 'use strict';

var MemoryStorage = class {
/**
* Maximum payload size in bytes (default: 5MB).
* Prevents unbounded memory growth from large responses.
*/
maxPayloadSize = 5 * 1024 * 1024;
store = /* @__PURE__ */ new Map();

@@ -261,3 +266,4 @@ cleanupInterval = null;

onConflict: options?.onConflict ?? config.onConflict,
onError: options?.onError ?? config.onError
onError: options?.onError ?? config.onError,
onSkip: options?.onSkip ?? config.onSkip
};

@@ -267,2 +273,9 @@ }

// src/oncely.ts
function estimatePayloadSize(value) {
try {
return new TextEncoder().encode(JSON.stringify(value)).length;
} catch {
return 0;
}
}
var Oncely = class {

@@ -276,2 +289,3 @@ storage;

onError;
onSkip;
constructor(options) {

@@ -285,2 +299,3 @@ this.storage = options.storage;

this.onError = options.onError;
this.onSkip = options.onSkip;
}

@@ -355,2 +370,23 @@ /**

};
const maxSize = this.storage.maxPayloadSize;
if (maxSize !== void 0 && maxSize > 0) {
const payloadSize = estimatePayloadSize(storedResponse);
if (payloadSize > maxSize) {
this.log(
`Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`
);
this.onSkip?.(key, "payload_too_large", { payloadSize, maxSize });
try {
await this.storage.release(key);
} catch (releaseErr) {
this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);
}
return {
data,
cached: false,
status: "created",
createdAt: now
};
}
}
try {

@@ -357,0 +393,0 @@ await this.storage.save(key, storedResponse);

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/memory.ts","../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/config.ts","../src/oncely.ts","../src/testing.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { MemoryStorage } from './memory.js';\nimport { createInstance } from './oncely.js';\nimport { resetConfig, configure } from './config.js';\nimport type { OncelyOptions, OncelyConfig, StoredResponse, AcquireResult } from './types.js';\n\n/**\n * Mock storage for testing.\n * Same as MemoryStorage but with additional test utilities.\n */\nexport class MockStorage extends MemoryStorage {\n /** Track all operations for assertions */\n readonly operations: Array<{ type: string; key: string; timestamp: number }> = [];\n\n /** Simulated conflict keys */\n private simulatedConflicts = new Set<string>();\n\n /** Simulated mismatch keys */\n private simulatedMismatches = new Map<string, { existingHash: string; providedHash: string }>();\n\n override async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n this.operations.push({ type: 'acquire', key, timestamp: Date.now() });\n\n // Check for simulated conflict\n if (this.simulatedConflicts.has(key)) {\n return { status: 'conflict', startedAt: Date.now() - 1000 };\n }\n\n // Check for simulated mismatch\n const mismatch = this.simulatedMismatches.get(key);\n if (mismatch) {\n return { status: 'mismatch', ...mismatch };\n }\n\n return super.acquire(key, hash, ttl);\n }\n\n override async save(key: string, response: StoredResponse): Promise<void> {\n this.operations.push({ type: 'save', key, timestamp: Date.now() });\n return super.save(key, response);\n }\n\n override async release(key: string): Promise<void> {\n this.operations.push({ type: 'release', key, timestamp: Date.now() });\n return super.release(key);\n }\n\n override async delete(key: string): Promise<void> {\n this.operations.push({ type: 'delete', key, timestamp: Date.now() });\n return super.delete(key);\n }\n\n override async clear(): Promise<void> {\n this.operations.length = 0;\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n return super.clear();\n }\n\n /**\n * Get operations for a specific key.\n */\n getOperationsForKey(key: string) {\n return this.operations.filter((op) => op.key === key);\n }\n\n /**\n * Assert that a key was acquired.\n */\n assertAcquired(key: string): void {\n const acquired = this.operations.some((op) => op.type === 'acquire' && op.key === key);\n if (!acquired) {\n throw new Error(`Expected key \"${key}\" to be acquired, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was saved (cached).\n */\n assertCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (!saved) {\n throw new Error(`Expected key \"${key}\" to be cached, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was NOT saved (not cached).\n */\n assertNotCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (saved) {\n throw new Error(`Expected key \"${key}\" to NOT be cached, but it was.`);\n }\n }\n\n /**\n * Assert that a key was released.\n */\n assertReleased(key: string): void {\n const released = this.operations.some((op) => op.type === 'release' && op.key === key);\n if (!released) {\n throw new Error(`Expected key \"${key}\" to be released, but it was not.`);\n }\n }\n\n /**\n * Simulate a conflict error for a specific key.\n * The next acquire for this key will return conflict status.\n */\n simulateConflict(key: string): void {\n this.simulatedConflicts.add(key);\n }\n\n /**\n * Simulate a mismatch error for a specific key.\n * The next acquire for this key will return mismatch status.\n */\n simulateMismatch(\n key: string,\n existingHash = 'existing-hash',\n providedHash = 'provided-hash'\n ): void {\n this.simulatedMismatches.set(key, { existingHash, providedHash });\n }\n\n /**\n * Clear all simulations.\n */\n clearSimulations(): void {\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n }\n}\n\n/**\n * Create an oncely instance configured for testing.\n *\n * @example\n * ```typescript\n * import { createTestInstance, MockStorage } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const storage = new MockStorage();\n * const idempotency = createTestInstance({ storage });\n *\n * beforeEach(() => storage.clear());\n *\n * it('handles duplicate requests', async () => {\n * const handler = vi.fn().mockResolvedValue({ id: 1 });\n *\n * await idempotency.run({ key: 'test', handler });\n * await idempotency.run({ key: 'test', handler });\n *\n * expect(handler).toHaveBeenCalledTimes(1);\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function createTestInstance(options?: Partial<OncelyOptions>) {\n return createInstance({\n storage: options?.storage ?? new MockStorage(),\n ttl: options?.ttl ?? '1h',\n debug: options?.debug ?? false,\n ...options,\n });\n}\n\n/**\n * @deprecated Use createTestInstance instead.\n */\nexport const createTestOncely = createTestInstance;\n\n/**\n * Set up oncely for testing with auto-reset between tests.\n * Returns utilities for testing assertions.\n *\n * @example\n * ```typescript\n * import { setupTest } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const { storage, instance, reset } = setupTest();\n *\n * beforeEach(() => reset());\n *\n * it('caches responses', async () => {\n * await instance.run({ key: 'test', handler: () => ({ ok: true }) });\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function setupTest(config?: OncelyConfig) {\n const storage = new MockStorage();\n const instance = createInstance({ storage, ...config });\n\n return {\n storage,\n instance,\n reset: () => {\n storage.clear();\n resetConfig();\n if (config) {\n configure(config);\n }\n },\n };\n}\n\n// Re-export useful types\nexport type {\n StorageAdapter,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n AcquireResult,\n} from './types.js';\n\n// Re-export errors for test assertions\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n} from './errors.js';\n"]}
{"version":3,"sources":["../src/memory.ts","../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/config.ts","../src/oncely.ts","../src/testing.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n /**\n * Maximum payload size in bytes (default: 5MB).\n * Prevents unbounded memory growth from large responses.\n */\n readonly maxPayloadSize: number = 5 * 1024 * 1024;\n\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n onSkip: options?.onSkip ?? config.onSkip,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n OnSkipCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Estimate the size of a value in bytes when serialized to JSON.\n */\nfunction estimatePayloadSize(value: unknown): number {\n try {\n return new TextEncoder().encode(JSON.stringify(value)).length;\n } catch {\n // If serialization fails, return 0 to allow normal flow\n // (save() will likely fail anyway)\n return 0;\n }\n}\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n private readonly onSkip?: OnSkipCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n this.onSkip = options.onSkip;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n // Check payload size limit\n const maxSize = this.storage.maxPayloadSize;\n if (maxSize !== undefined && maxSize > 0) {\n const payloadSize = estimatePayloadSize(storedResponse);\n if (payloadSize > maxSize) {\n this.log(\n `Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`\n );\n this.onSkip?.(key, 'payload_too_large', { payloadSize, maxSize });\n\n // Release lock and return without caching\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n }\n }\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { MemoryStorage } from './memory.js';\nimport { createInstance } from './oncely.js';\nimport { resetConfig, configure } from './config.js';\nimport type { OncelyOptions, OncelyConfig, StoredResponse, AcquireResult } from './types.js';\n\n/**\n * Mock storage for testing.\n * Same as MemoryStorage but with additional test utilities.\n */\nexport class MockStorage extends MemoryStorage {\n /** Track all operations for assertions */\n readonly operations: Array<{ type: string; key: string; timestamp: number }> = [];\n\n /** Simulated conflict keys */\n private simulatedConflicts = new Set<string>();\n\n /** Simulated mismatch keys */\n private simulatedMismatches = new Map<string, { existingHash: string; providedHash: string }>();\n\n override async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n this.operations.push({ type: 'acquire', key, timestamp: Date.now() });\n\n // Check for simulated conflict\n if (this.simulatedConflicts.has(key)) {\n return { status: 'conflict', startedAt: Date.now() - 1000 };\n }\n\n // Check for simulated mismatch\n const mismatch = this.simulatedMismatches.get(key);\n if (mismatch) {\n return { status: 'mismatch', ...mismatch };\n }\n\n return super.acquire(key, hash, ttl);\n }\n\n override async save(key: string, response: StoredResponse): Promise<void> {\n this.operations.push({ type: 'save', key, timestamp: Date.now() });\n return super.save(key, response);\n }\n\n override async release(key: string): Promise<void> {\n this.operations.push({ type: 'release', key, timestamp: Date.now() });\n return super.release(key);\n }\n\n override async delete(key: string): Promise<void> {\n this.operations.push({ type: 'delete', key, timestamp: Date.now() });\n return super.delete(key);\n }\n\n override async clear(): Promise<void> {\n this.operations.length = 0;\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n return super.clear();\n }\n\n /**\n * Get operations for a specific key.\n */\n getOperationsForKey(key: string) {\n return this.operations.filter((op) => op.key === key);\n }\n\n /**\n * Assert that a key was acquired.\n */\n assertAcquired(key: string): void {\n const acquired = this.operations.some((op) => op.type === 'acquire' && op.key === key);\n if (!acquired) {\n throw new Error(`Expected key \"${key}\" to be acquired, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was saved (cached).\n */\n assertCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (!saved) {\n throw new Error(`Expected key \"${key}\" to be cached, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was NOT saved (not cached).\n */\n assertNotCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (saved) {\n throw new Error(`Expected key \"${key}\" to NOT be cached, but it was.`);\n }\n }\n\n /**\n * Assert that a key was released.\n */\n assertReleased(key: string): void {\n const released = this.operations.some((op) => op.type === 'release' && op.key === key);\n if (!released) {\n throw new Error(`Expected key \"${key}\" to be released, but it was not.`);\n }\n }\n\n /**\n * Simulate a conflict error for a specific key.\n * The next acquire for this key will return conflict status.\n */\n simulateConflict(key: string): void {\n this.simulatedConflicts.add(key);\n }\n\n /**\n * Simulate a mismatch error for a specific key.\n * The next acquire for this key will return mismatch status.\n */\n simulateMismatch(\n key: string,\n existingHash = 'existing-hash',\n providedHash = 'provided-hash'\n ): void {\n this.simulatedMismatches.set(key, { existingHash, providedHash });\n }\n\n /**\n * Clear all simulations.\n */\n clearSimulations(): void {\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n }\n}\n\n/**\n * Create an oncely instance configured for testing.\n *\n * @example\n * ```typescript\n * import { createTestInstance, MockStorage } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const storage = new MockStorage();\n * const idempotency = createTestInstance({ storage });\n *\n * beforeEach(() => storage.clear());\n *\n * it('handles duplicate requests', async () => {\n * const handler = vi.fn().mockResolvedValue({ id: 1 });\n *\n * await idempotency.run({ key: 'test', handler });\n * await idempotency.run({ key: 'test', handler });\n *\n * expect(handler).toHaveBeenCalledTimes(1);\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function createTestInstance(options?: Partial<OncelyOptions>) {\n return createInstance({\n storage: options?.storage ?? new MockStorage(),\n ttl: options?.ttl ?? '1h',\n debug: options?.debug ?? false,\n ...options,\n });\n}\n\n/**\n * @deprecated Use createTestInstance instead.\n */\nexport const createTestOncely = createTestInstance;\n\n/**\n * Set up oncely for testing with auto-reset between tests.\n * Returns utilities for testing assertions.\n *\n * @example\n * ```typescript\n * import { setupTest } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const { storage, instance, reset } = setupTest();\n *\n * beforeEach(() => reset());\n *\n * it('caches responses', async () => {\n * await instance.run({ key: 'test', handler: () => ({ ok: true }) });\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function setupTest(config?: OncelyConfig) {\n const storage = new MockStorage();\n const instance = createInstance({ storage, ...config });\n\n return {\n storage,\n instance,\n reset: () => {\n storage.clear();\n resetConfig();\n if (config) {\n configure(config);\n }\n },\n };\n}\n\n// Re-export useful types\nexport type {\n StorageAdapter,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n AcquireResult,\n} from './types.js';\n\n// Re-export errors for test assertions\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n} from './errors.js';\n"]}

@@ -1,3 +0,3 @@

import { M as MemoryStorage, A as AcquireResult, f as StoredResponse, g as OncelyOptions, a as Oncely, O as OncelyConfig } from './errors-BUehgS6t.cjs';
export { C as ConflictError, I as IdempotencyError, d as MismatchError, b as MissingKeyError, R as RunOptions, h as RunResult, S as StorageAdapter, e as StorageError } from './errors-BUehgS6t.cjs';
import { M as MemoryStorage, A as AcquireResult, o as StoredResponse, j as OncelyOptions, i as Oncely, O as OncelyConfig } from './errors-DVBjbOG7.cjs';
export { C as ConflictError, I as IdempotencyError, a as MismatchError, b as MissingKeyError, R as RunOptions, k as RunResult, S as StorageAdapter, n as StorageError } from './errors-DVBjbOG7.cjs';

@@ -4,0 +4,0 @@ /**

@@ -1,3 +0,3 @@

import { M as MemoryStorage, A as AcquireResult, f as StoredResponse, g as OncelyOptions, a as Oncely, O as OncelyConfig } from './errors-BUehgS6t.js';
export { C as ConflictError, I as IdempotencyError, d as MismatchError, b as MissingKeyError, R as RunOptions, h as RunResult, S as StorageAdapter, e as StorageError } from './errors-BUehgS6t.js';
import { M as MemoryStorage, A as AcquireResult, o as StoredResponse, j as OncelyOptions, i as Oncely, O as OncelyConfig } from './errors-DVBjbOG7.js';
export { C as ConflictError, I as IdempotencyError, a as MismatchError, b as MissingKeyError, R as RunOptions, k as RunResult, S as StorageAdapter, n as StorageError } from './errors-DVBjbOG7.js';

@@ -4,0 +4,0 @@ /**

@@ -5,2 +5,7 @@ import 'crypto';

var MemoryStorage = class {
/**
* Maximum payload size in bytes (default: 5MB).
* Prevents unbounded memory growth from large responses.
*/
maxPayloadSize = 5 * 1024 * 1024;
store = /* @__PURE__ */ new Map();

@@ -259,3 +264,4 @@ cleanupInterval = null;

onConflict: options?.onConflict ?? config.onConflict,
onError: options?.onError ?? config.onError
onError: options?.onError ?? config.onError,
onSkip: options?.onSkip ?? config.onSkip
};

@@ -265,2 +271,9 @@ }

// src/oncely.ts
function estimatePayloadSize(value) {
try {
return new TextEncoder().encode(JSON.stringify(value)).length;
} catch {
return 0;
}
}
var Oncely = class {

@@ -274,2 +287,3 @@ storage;

onError;
onSkip;
constructor(options) {

@@ -283,2 +297,3 @@ this.storage = options.storage;

this.onError = options.onError;
this.onSkip = options.onSkip;
}

@@ -353,2 +368,23 @@ /**

};
const maxSize = this.storage.maxPayloadSize;
if (maxSize !== void 0 && maxSize > 0) {
const payloadSize = estimatePayloadSize(storedResponse);
if (payloadSize > maxSize) {
this.log(
`Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`
);
this.onSkip?.(key, "payload_too_large", { payloadSize, maxSize });
try {
await this.storage.release(key);
} catch (releaseErr) {
this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);
}
return {
data,
cached: false,
status: "created",
createdAt: now
};
}
}
try {

@@ -355,0 +391,0 @@ await this.storage.save(key, storedResponse);

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/memory.ts","../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/config.ts","../src/oncely.ts","../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { MemoryStorage } from './memory.js';\nimport { createInstance } from './oncely.js';\nimport { resetConfig, configure } from './config.js';\nimport type { OncelyOptions, OncelyConfig, StoredResponse, AcquireResult } from './types.js';\n\n/**\n * Mock storage for testing.\n * Same as MemoryStorage but with additional test utilities.\n */\nexport class MockStorage extends MemoryStorage {\n /** Track all operations for assertions */\n readonly operations: Array<{ type: string; key: string; timestamp: number }> = [];\n\n /** Simulated conflict keys */\n private simulatedConflicts = new Set<string>();\n\n /** Simulated mismatch keys */\n private simulatedMismatches = new Map<string, { existingHash: string; providedHash: string }>();\n\n override async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n this.operations.push({ type: 'acquire', key, timestamp: Date.now() });\n\n // Check for simulated conflict\n if (this.simulatedConflicts.has(key)) {\n return { status: 'conflict', startedAt: Date.now() - 1000 };\n }\n\n // Check for simulated mismatch\n const mismatch = this.simulatedMismatches.get(key);\n if (mismatch) {\n return { status: 'mismatch', ...mismatch };\n }\n\n return super.acquire(key, hash, ttl);\n }\n\n override async save(key: string, response: StoredResponse): Promise<void> {\n this.operations.push({ type: 'save', key, timestamp: Date.now() });\n return super.save(key, response);\n }\n\n override async release(key: string): Promise<void> {\n this.operations.push({ type: 'release', key, timestamp: Date.now() });\n return super.release(key);\n }\n\n override async delete(key: string): Promise<void> {\n this.operations.push({ type: 'delete', key, timestamp: Date.now() });\n return super.delete(key);\n }\n\n override async clear(): Promise<void> {\n this.operations.length = 0;\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n return super.clear();\n }\n\n /**\n * Get operations for a specific key.\n */\n getOperationsForKey(key: string) {\n return this.operations.filter((op) => op.key === key);\n }\n\n /**\n * Assert that a key was acquired.\n */\n assertAcquired(key: string): void {\n const acquired = this.operations.some((op) => op.type === 'acquire' && op.key === key);\n if (!acquired) {\n throw new Error(`Expected key \"${key}\" to be acquired, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was saved (cached).\n */\n assertCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (!saved) {\n throw new Error(`Expected key \"${key}\" to be cached, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was NOT saved (not cached).\n */\n assertNotCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (saved) {\n throw new Error(`Expected key \"${key}\" to NOT be cached, but it was.`);\n }\n }\n\n /**\n * Assert that a key was released.\n */\n assertReleased(key: string): void {\n const released = this.operations.some((op) => op.type === 'release' && op.key === key);\n if (!released) {\n throw new Error(`Expected key \"${key}\" to be released, but it was not.`);\n }\n }\n\n /**\n * Simulate a conflict error for a specific key.\n * The next acquire for this key will return conflict status.\n */\n simulateConflict(key: string): void {\n this.simulatedConflicts.add(key);\n }\n\n /**\n * Simulate a mismatch error for a specific key.\n * The next acquire for this key will return mismatch status.\n */\n simulateMismatch(\n key: string,\n existingHash = 'existing-hash',\n providedHash = 'provided-hash'\n ): void {\n this.simulatedMismatches.set(key, { existingHash, providedHash });\n }\n\n /**\n * Clear all simulations.\n */\n clearSimulations(): void {\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n }\n}\n\n/**\n * Create an oncely instance configured for testing.\n *\n * @example\n * ```typescript\n * import { createTestInstance, MockStorage } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const storage = new MockStorage();\n * const idempotency = createTestInstance({ storage });\n *\n * beforeEach(() => storage.clear());\n *\n * it('handles duplicate requests', async () => {\n * const handler = vi.fn().mockResolvedValue({ id: 1 });\n *\n * await idempotency.run({ key: 'test', handler });\n * await idempotency.run({ key: 'test', handler });\n *\n * expect(handler).toHaveBeenCalledTimes(1);\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function createTestInstance(options?: Partial<OncelyOptions>) {\n return createInstance({\n storage: options?.storage ?? new MockStorage(),\n ttl: options?.ttl ?? '1h',\n debug: options?.debug ?? false,\n ...options,\n });\n}\n\n/**\n * @deprecated Use createTestInstance instead.\n */\nexport const createTestOncely = createTestInstance;\n\n/**\n * Set up oncely for testing with auto-reset between tests.\n * Returns utilities for testing assertions.\n *\n * @example\n * ```typescript\n * import { setupTest } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const { storage, instance, reset } = setupTest();\n *\n * beforeEach(() => reset());\n *\n * it('caches responses', async () => {\n * await instance.run({ key: 'test', handler: () => ({ ok: true }) });\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function setupTest(config?: OncelyConfig) {\n const storage = new MockStorage();\n const instance = createInstance({ storage, ...config });\n\n return {\n storage,\n instance,\n reset: () => {\n storage.clear();\n resetConfig();\n if (config) {\n configure(config);\n }\n },\n };\n}\n\n// Re-export useful types\nexport type {\n StorageAdapter,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n AcquireResult,\n} from './types.js';\n\n// Re-export errors for test assertions\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n} from './errors.js';\n"]}
{"version":3,"sources":["../src/memory.ts","../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/config.ts","../src/oncely.ts","../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["import type { StorageAdapter, AcquireResult, StoredResponse } from './types.js';\n\ninterface MemoryRecord {\n status: 'in_progress' | 'completed';\n hash: string | null;\n response?: StoredResponse;\n startedAt: number;\n expiresAt: number;\n}\n\n/**\n * In-memory storage adapter.\n * Suitable for development, testing, and single-instance deployments.\n *\n * **Warning:** Data is lost on restart and not shared across instances.\n * Use Redis for production multi-instance deployments.\n */\nexport class MemoryStorage implements StorageAdapter {\n /**\n * Maximum payload size in bytes (default: 5MB).\n * Prevents unbounded memory growth from large responses.\n */\n readonly maxPayloadSize: number = 5 * 1024 * 1024;\n\n private store = new Map<string, MemoryRecord>();\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n // Cleanup expired entries every 60 seconds\n this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);\n // Don't prevent Node from exiting\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n const now = Date.now();\n let existing = this.store.get(key);\n\n if (existing && existing.expiresAt <= now) {\n this.store.delete(key);\n existing = undefined;\n }\n\n // Check if key exists and hasn't expired\n if (existing) {\n const existingHash = existing.hash;\n // Check for hash mismatch\n if (hash && existingHash && existingHash !== hash) {\n return {\n status: 'mismatch',\n existingHash,\n providedHash: hash,\n };\n }\n\n // If completed, return cached response\n if (existing.status === 'completed') {\n const response = existing.response;\n if (response) {\n return { status: 'hit', response };\n }\n }\n\n // If in progress, return conflict\n if (existing.status === 'in_progress') {\n return { status: 'conflict', startedAt: existing.startedAt };\n }\n }\n\n // Acquire lock\n this.store.set(key, {\n status: 'in_progress',\n hash,\n startedAt: now,\n expiresAt: now + ttl,\n });\n\n return { status: 'acquired' };\n }\n\n async save(key: string, response: StoredResponse): Promise<void> {\n const existing = this.store.get(key);\n if (existing) {\n existing.status = 'completed';\n existing.response = response;\n }\n }\n\n async release(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n /**\n * Stop the cleanup interval.\n * Call this when shutting down to prevent memory leaks in tests.\n */\n destroy(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n\n private cleanup(): void {\n const now = Date.now();\n for (const [key, record] of this.store) {\n if (record.expiresAt <= now) {\n this.store.delete(key);\n }\n }\n }\n}\n\n/**\n * Pre-configured memory storage instance.\n * Use this for quick setup in development.\n *\n * @example\n * ```typescript\n * import { oncely, memory } from 'oncely';\n *\n * const idempotency = oncely({ storage: memory });\n * ```\n */\nexport const memory: StorageAdapter = new MemoryStorage();\n","/**\n * HTTP header constants following IETF draft-ietf-httpapi-idempotency-key-header.\n * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header\n */\n\n/** Standard header name for idempotency keys */\nexport const HEADER = 'Idempotency-Key';\n\n/** Header indicating a response was replayed from cache */\nexport const HEADER_REPLAY = 'Idempotency-Replay';\n\n/** Base URL for error documentation */\nexport const DOCS_BASE_URL = 'https://oncely.dev/errors';\n","import { DOCS_BASE_URL } from './constants.js';\n\n/**\n * RFC 7807 Problem Details response format.\n * @see https://www.rfc-editor.org/rfc/rfc7807\n */\nexport interface ProblemDetails {\n /** URI reference identifying the problem type */\n type: string;\n /** Short human-readable summary */\n title: string;\n /** HTTP status code */\n status: number;\n /** Detailed human-readable explanation */\n detail: string;\n /** URI reference to the specific occurrence (optional) */\n instance?: string;\n /** Additional properties */\n [key: string]: unknown;\n}\n\n/**\n * Base class for all oncely errors.\n * Provides RFC 7807 Problem Details format for HTTP responses.\n */\nexport class IdempotencyError extends Error {\n /** HTTP status code for this error */\n readonly statusCode: number;\n /** Error type identifier (URL) */\n readonly type: string;\n /** Short title for the error */\n readonly title: string;\n\n constructor(message: string, statusCode: number, type: string, title: string) {\n super(message);\n this.name = 'IdempotencyError';\n this.statusCode = statusCode;\n this.type = type;\n this.title = title;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Convert to RFC 7807 Problem Details format.\n */\n toProblemDetails(): ProblemDetails {\n return {\n type: this.type,\n title: this.title,\n status: this.statusCode,\n detail: this.message,\n };\n }\n\n /**\n * Convert to JSON (RFC 7807 format).\n */\n toJSON(): ProblemDetails {\n return this.toProblemDetails();\n }\n}\n\n/**\n * Thrown when an idempotency key is required but not provided.\n * HTTP 400 Bad Request\n */\nexport class MissingKeyError extends IdempotencyError {\n constructor() {\n super(\n 'This operation requires an Idempotency-Key header.',\n 400,\n `${DOCS_BASE_URL}/missing-key`,\n 'Idempotency-Key is missing'\n );\n this.name = 'MissingKeyError';\n }\n}\n\n/**\n * Thrown when a request with the same key is already being processed.\n * HTTP 409 Conflict\n */\nexport class ConflictError extends IdempotencyError {\n /** When the in-progress request started */\n readonly startedAt: number;\n /** Suggested retry delay in seconds */\n readonly retryAfter: number;\n\n constructor(startedAt: number) {\n const ageSeconds = Math.round((Date.now() - startedAt) / 1000);\n super(\n 'A request with the same Idempotency-Key is currently being processed.',\n 409,\n `${DOCS_BASE_URL}/conflict`,\n 'Request in progress'\n );\n this.name = 'ConflictError';\n this.startedAt = startedAt;\n this.retryAfter = Math.max(1, Math.min(5, ageSeconds));\n }\n\n override toProblemDetails(): ProblemDetails {\n return {\n ...super.toProblemDetails(),\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/**\n * Thrown when the same idempotency key is used with a different request payload.\n * HTTP 422 Unprocessable Content\n */\nexport class MismatchError extends IdempotencyError {\n /** Hash of the original request */\n readonly existingHash: string;\n /** Hash of the current request */\n readonly providedHash: string;\n\n constructor(existingHash: string, providedHash: string) {\n super(\n 'This Idempotency-Key was used with a different request payload.',\n 422,\n `${DOCS_BASE_URL}/mismatch`,\n 'Idempotency-Key reused'\n );\n this.name = 'MismatchError';\n this.existingHash = existingHash;\n this.providedHash = providedHash;\n }\n}\n\n/**\n * Thrown when the storage adapter encounters an error.\n * HTTP 500 Internal Server Error\n */\nexport class StorageError extends IdempotencyError {\n /** The underlying error from the storage adapter */\n override readonly cause: Error;\n\n constructor(message: string, cause: Error) {\n super(`Storage error: ${message}`, 500, `${DOCS_BASE_URL}/storage-error`, 'Storage error');\n this.name = 'StorageError';\n this.cause = cause;\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\n\n/**\n * Parse a TTL string into milliseconds.\n * Supports: '30s', '5m', '24h', '7d'\n */\nexport function parseTtl(ttl: number | string): number {\n if (typeof ttl === 'number') {\n return ttl;\n }\n\n if (ttl.length < 2) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const unit = ttl[ttl.length - 1];\n if (unit !== 's' && unit !== 'm' && unit !== 'h' && unit !== 'd') {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n const valueStr = ttl.slice(0, -1);\n if (!valueStr) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n\n for (let i = 0; i < valueStr.length; i++) {\n const code = valueStr.charCodeAt(i);\n if (code < 48 || code > 57) {\n throw new Error(\n `Invalid TTL format: \"${ttl}\". Use a number (milliseconds) or string like '30s', '5m', '24h', '7d'.`\n );\n }\n }\n\n const value = parseInt(valueStr, 10);\n\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n throw new Error(`Invalid TTL unit: \"${unit}\"`);\n }\n}\n\n/**\n * Generate a simple hash from a string using DJB2 algorithm.\n *\n * **Note:** This is a fast, non-cryptographic hash suitable for request\n * fingerprinting. It has a 32-bit output space (~4 billion unique values).\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision (birthday problem). For high-volume payment systems or\n * security-sensitive applications, use {@link hashObjectSecure} instead.\n *\n * For typical API use cases with moderate volume, DJB2 provides excellent\n * performance (~2.4M ops/sec vs ~600K ops/sec for SHA-256).\n */\nexport function simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, '0');\n}\n\n/**\n * Generate a SHA-256 hash from a string.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link simpleHash} but cryptographically secure.\n */\nexport function secureHash(str: string): string {\n return createHash('sha256').update(str).digest('hex');\n}\n\n/**\n * Hash an object by JSON stringifying it.\n *\n * Uses the fast DJB2 algorithm (~2.4M ops/sec). For high-volume payment\n * systems or security-sensitive applications where collision resistance\n * is critical, use {@link hashObjectSecure} instead.\n *\n * **Collision Risk:** At ~77,000 unique payloads, there's a 50% probability\n * of collision. A collision could cause:\n * - False mismatch errors (if hashes differ)\n * - Or incorrectly matching different payloads (if hashes collide)\n *\n * **Note:** Object key order affects the hash. If you need order-independent\n * hashing, sort the keys before passing to this function.\n */\nexport function hashObject(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return simpleHash(str);\n}\n\n/**\n * Hash an object using SHA-256.\n *\n * Use this for security-sensitive applications where collision resistance\n * is important. Slower than {@link hashObject} but cryptographically secure.\n */\nexport function hashObjectSecure(obj: unknown): string {\n const str = JSON.stringify(obj) ?? 'undefined';\n return secureHash(str);\n}\n\n/**\n * Generate a cryptographically secure unique key (UUID v4).\n *\n * Uses Node.js crypto.randomUUID() for secure random generation.\n */\nexport function generateKey(): string {\n return randomUUID();\n}\n\n/**\n * Compose a deterministic key from multiple parts.\n */\nexport function composeKey(...parts: (string | number)[]): string {\n return parts.join(':');\n}\n","import type { StorageAdapter, OncelyOptions, OncelyConfig } from './types.js';\nimport { MemoryStorage } from './memory.js';\n\n// Re-export GlobalConfig as alias for backwards compatibility\nexport type GlobalConfig = OncelyConfig;\n\n// Global configuration store\nlet globalConfig: OncelyConfig = {};\n\n// Lazy-initialized default memory storage\nlet defaultMemoryStorage: MemoryStorage | null = null;\n\n/**\n * Configure global defaults for oncely.\n * Call this once at application startup to set defaults for all oncely operations.\n *\n * @example\n * ```typescript\n * import { oncely } from '@oncely/core';\n * import { redis } from '@oncely/redis';\n *\n * // Set up once at app startup\n * oncely.configure({\n * storage: redis(),\n * ttl: '1h',\n * onHit: (key) => console.log(`Cache hit: ${key}`),\n * });\n * ```\n */\nexport function configure(config: OncelyConfig): void {\n globalConfig = { ...config };\n}\n\n/**\n * Get the current global configuration.\n */\nexport function getConfig(): OncelyConfig {\n return globalConfig;\n}\n\n/**\n * Reset global configuration to defaults.\n * Useful for testing.\n */\nexport function resetConfig(): void {\n globalConfig = {};\n defaultMemoryStorage = null;\n}\n\n/**\n * Get the default storage adapter.\n * Returns the configured global storage, or a shared memory instance.\n */\nexport function getDefaultStorage(): StorageAdapter {\n if (globalConfig.storage) {\n return globalConfig.storage;\n }\n // Lazy-init shared memory storage\n if (!defaultMemoryStorage) {\n defaultMemoryStorage = new MemoryStorage();\n }\n return defaultMemoryStorage;\n}\n\n/**\n * Resolve oncely options by merging with global config.\n */\nexport function resolveOptions(options?: Partial<OncelyOptions>): OncelyOptions {\n const config = getConfig();\n return {\n storage: options?.storage ?? config.storage ?? getDefaultStorage(),\n ttl: options?.ttl ?? config.ttl ?? '24h',\n fingerprint: options?.fingerprint ?? config.fingerprint ?? true,\n debug: options?.debug ?? config.debug ?? false,\n onHit: options?.onHit ?? config.onHit,\n onMiss: options?.onMiss ?? config.onMiss,\n onConflict: options?.onConflict ?? config.onConflict,\n onError: options?.onError ?? config.onError,\n onSkip: options?.onSkip ?? config.onSkip,\n };\n}\n","import type {\n StorageAdapter,\n OncelyOptions,\n RunOptions,\n RunResult,\n StoredResponse,\n OnHitCallback,\n OnMissCallback,\n OnConflictCallback,\n OnErrorCallback,\n OnSkipCallback,\n} from './types.js';\nimport { MissingKeyError, ConflictError, MismatchError, StorageError } from './errors.js';\nimport { parseTtl } from './utils.js';\nimport { resolveOptions } from './config.js';\n\n/**\n * Estimate the size of a value in bytes when serialized to JSON.\n */\nfunction estimatePayloadSize(value: unknown): number {\n try {\n return new TextEncoder().encode(JSON.stringify(value)).length;\n } catch {\n // If serialization fails, return 0 to allow normal flow\n // (save() will likely fail anyway)\n return 0;\n }\n}\n\n/**\n * Oncely idempotency service.\n * Ensures operations are executed exactly once per idempotency key.\n */\nexport class Oncely {\n private readonly storage: StorageAdapter;\n private readonly ttl: number;\n private readonly debug: boolean;\n private readonly onHit?: OnHitCallback;\n private readonly onMiss?: OnMissCallback;\n private readonly onConflict?: OnConflictCallback;\n private readonly onError?: OnErrorCallback;\n private readonly onSkip?: OnSkipCallback;\n\n constructor(options: OncelyOptions) {\n this.storage = options.storage;\n this.ttl = parseTtl(options.ttl ?? '24h');\n this.debug = options.debug ?? false;\n this.onHit = options.onHit;\n this.onMiss = options.onMiss;\n this.onConflict = options.onConflict;\n this.onError = options.onError;\n this.onSkip = options.onSkip;\n }\n\n /**\n * Run an operation with idempotency protection.\n *\n * @example\n * ```typescript\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => {\n * const order = await createOrder(data);\n * return order;\n * },\n * });\n *\n * if (result.cached) {\n * console.log('Returned cached response');\n * }\n * ```\n */\n async run<T>(options: RunOptions<T>): Promise<RunResult<T>> {\n const { key, hash = null, handler } = options;\n\n // Validate key\n if (!key) {\n throw new MissingKeyError();\n }\n\n this.log(`Acquiring lock for key: ${key}`);\n\n // Try to acquire lock\n let acquireResult;\n try {\n acquireResult = await this.storage.acquire(key, hash, this.ttl);\n } catch (err) {\n throw new StorageError('Failed to acquire lock', err as Error);\n }\n\n // Handle different acquire results\n switch (acquireResult.status) {\n case 'hit': {\n this.log(`Cache hit for key: ${key}`);\n this.onHit?.(key, acquireResult.response);\n return {\n data: acquireResult.response.data as T,\n cached: true,\n status: 'hit',\n createdAt: acquireResult.response.createdAt,\n };\n }\n\n case 'conflict': {\n this.log(`Conflict for key: ${key}`);\n const error = new ConflictError(acquireResult.startedAt);\n this.onConflict?.(key);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'mismatch': {\n this.log(`Hash mismatch for key: ${key}`);\n const error = new MismatchError(acquireResult.existingHash, acquireResult.providedHash);\n this.onError?.(key, error);\n throw error;\n }\n\n case 'acquired': {\n this.log(`Lock acquired for key: ${key}`);\n this.onMiss?.(key);\n break;\n }\n }\n\n // Execute handler\n try {\n const data = await handler();\n const now = Date.now();\n\n // Save response\n const storedResponse: StoredResponse = {\n data,\n createdAt: now,\n hash,\n };\n\n // Check payload size limit\n const maxSize = this.storage.maxPayloadSize;\n if (maxSize !== undefined && maxSize > 0) {\n const payloadSize = estimatePayloadSize(storedResponse);\n if (payloadSize > maxSize) {\n this.log(\n `Payload too large for key: ${key} (${payloadSize} bytes > ${maxSize} bytes limit). Skipping cache.`\n );\n this.onSkip?.(key, 'payload_too_large', { payloadSize, maxSize });\n\n // Release lock and return without caching\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n }\n }\n\n try {\n await this.storage.save(key, storedResponse);\n this.log(`Saved response for key: ${key}`);\n } catch (err) {\n // Log but don't fail - the operation succeeded\n this.log(`Failed to save response for key: ${key} - ${err}`);\n }\n\n return {\n data,\n cached: false,\n status: 'created',\n createdAt: now,\n };\n } catch (err) {\n // Release lock on failure to allow retry\n this.log(`Handler failed for key: ${key}, releasing lock`);\n try {\n await this.storage.release(key);\n } catch (releaseErr) {\n this.log(`Failed to release lock for key: ${key} - ${releaseErr}`);\n }\n throw err;\n }\n }\n\n private log(message: string): void {\n if (this.debug) {\n console.log(`[oncely] ${message}`);\n }\n }\n}\n\n/**\n * Create an oncely idempotency instance.\n *\n * Options are optional - will use global config or sensible defaults.\n *\n * @example\n * ```typescript\n * import { createInstance } from '@oncely/core';\n *\n * // Zero-config (uses memory storage)\n * const idempotency = createInstance();\n *\n * // With explicit storage\n * const idempotency = createInstance({ storage: myRedis });\n *\n * const result = await idempotency.run({\n * key: 'order-123',\n * handler: async () => createOrder(data),\n * });\n * ```\n */\nexport function createInstance(options?: Partial<OncelyOptions>): Oncely {\n return new Oncely(resolveOptions(options));\n}\n","import { MemoryStorage } from './memory.js';\nimport { createInstance } from './oncely.js';\nimport { resetConfig, configure } from './config.js';\nimport type { OncelyOptions, OncelyConfig, StoredResponse, AcquireResult } from './types.js';\n\n/**\n * Mock storage for testing.\n * Same as MemoryStorage but with additional test utilities.\n */\nexport class MockStorage extends MemoryStorage {\n /** Track all operations for assertions */\n readonly operations: Array<{ type: string; key: string; timestamp: number }> = [];\n\n /** Simulated conflict keys */\n private simulatedConflicts = new Set<string>();\n\n /** Simulated mismatch keys */\n private simulatedMismatches = new Map<string, { existingHash: string; providedHash: string }>();\n\n override async acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult> {\n this.operations.push({ type: 'acquire', key, timestamp: Date.now() });\n\n // Check for simulated conflict\n if (this.simulatedConflicts.has(key)) {\n return { status: 'conflict', startedAt: Date.now() - 1000 };\n }\n\n // Check for simulated mismatch\n const mismatch = this.simulatedMismatches.get(key);\n if (mismatch) {\n return { status: 'mismatch', ...mismatch };\n }\n\n return super.acquire(key, hash, ttl);\n }\n\n override async save(key: string, response: StoredResponse): Promise<void> {\n this.operations.push({ type: 'save', key, timestamp: Date.now() });\n return super.save(key, response);\n }\n\n override async release(key: string): Promise<void> {\n this.operations.push({ type: 'release', key, timestamp: Date.now() });\n return super.release(key);\n }\n\n override async delete(key: string): Promise<void> {\n this.operations.push({ type: 'delete', key, timestamp: Date.now() });\n return super.delete(key);\n }\n\n override async clear(): Promise<void> {\n this.operations.length = 0;\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n return super.clear();\n }\n\n /**\n * Get operations for a specific key.\n */\n getOperationsForKey(key: string) {\n return this.operations.filter((op) => op.key === key);\n }\n\n /**\n * Assert that a key was acquired.\n */\n assertAcquired(key: string): void {\n const acquired = this.operations.some((op) => op.type === 'acquire' && op.key === key);\n if (!acquired) {\n throw new Error(`Expected key \"${key}\" to be acquired, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was saved (cached).\n */\n assertCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (!saved) {\n throw new Error(`Expected key \"${key}\" to be cached, but it was not.`);\n }\n }\n\n /**\n * Assert that a key was NOT saved (not cached).\n */\n assertNotCached(key: string): void {\n const saved = this.operations.some((op) => op.type === 'save' && op.key === key);\n if (saved) {\n throw new Error(`Expected key \"${key}\" to NOT be cached, but it was.`);\n }\n }\n\n /**\n * Assert that a key was released.\n */\n assertReleased(key: string): void {\n const released = this.operations.some((op) => op.type === 'release' && op.key === key);\n if (!released) {\n throw new Error(`Expected key \"${key}\" to be released, but it was not.`);\n }\n }\n\n /**\n * Simulate a conflict error for a specific key.\n * The next acquire for this key will return conflict status.\n */\n simulateConflict(key: string): void {\n this.simulatedConflicts.add(key);\n }\n\n /**\n * Simulate a mismatch error for a specific key.\n * The next acquire for this key will return mismatch status.\n */\n simulateMismatch(\n key: string,\n existingHash = 'existing-hash',\n providedHash = 'provided-hash'\n ): void {\n this.simulatedMismatches.set(key, { existingHash, providedHash });\n }\n\n /**\n * Clear all simulations.\n */\n clearSimulations(): void {\n this.simulatedConflicts.clear();\n this.simulatedMismatches.clear();\n }\n}\n\n/**\n * Create an oncely instance configured for testing.\n *\n * @example\n * ```typescript\n * import { createTestInstance, MockStorage } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const storage = new MockStorage();\n * const idempotency = createTestInstance({ storage });\n *\n * beforeEach(() => storage.clear());\n *\n * it('handles duplicate requests', async () => {\n * const handler = vi.fn().mockResolvedValue({ id: 1 });\n *\n * await idempotency.run({ key: 'test', handler });\n * await idempotency.run({ key: 'test', handler });\n *\n * expect(handler).toHaveBeenCalledTimes(1);\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function createTestInstance(options?: Partial<OncelyOptions>) {\n return createInstance({\n storage: options?.storage ?? new MockStorage(),\n ttl: options?.ttl ?? '1h',\n debug: options?.debug ?? false,\n ...options,\n });\n}\n\n/**\n * @deprecated Use createTestInstance instead.\n */\nexport const createTestOncely = createTestInstance;\n\n/**\n * Set up oncely for testing with auto-reset between tests.\n * Returns utilities for testing assertions.\n *\n * @example\n * ```typescript\n * import { setupTest } from '@oncely/core/testing';\n *\n * describe('my api', () => {\n * const { storage, instance, reset } = setupTest();\n *\n * beforeEach(() => reset());\n *\n * it('caches responses', async () => {\n * await instance.run({ key: 'test', handler: () => ({ ok: true }) });\n * storage.assertCached('test');\n * });\n * });\n * ```\n */\nexport function setupTest(config?: OncelyConfig) {\n const storage = new MockStorage();\n const instance = createInstance({ storage, ...config });\n\n return {\n storage,\n instance,\n reset: () => {\n storage.clear();\n resetConfig();\n if (config) {\n configure(config);\n }\n },\n };\n}\n\n// Re-export useful types\nexport type {\n StorageAdapter,\n StoredResponse,\n OncelyOptions,\n OncelyConfig,\n RunOptions,\n RunResult,\n AcquireResult,\n} from './types.js';\n\n// Re-export errors for test assertions\nexport {\n IdempotencyError,\n MissingKeyError,\n ConflictError,\n MismatchError,\n StorageError,\n} from './errors.js';\n"]}
{
"name": "@oncely/core",
"version": "0.2.2",
"version": "0.3.0",
"description": "Idempotency engine for HTTP APIs - ensures operations execute exactly once",

@@ -5,0 +5,0 @@ "author": "stacks0x",

/**
* Storage adapter interface for oncely.
* Implement this interface to create custom storage backends.
*/
interface StorageAdapter {
/**
* Attempt to acquire a lock for the given key.
* Returns the current state of the key.
*/
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
/**
* Save a completed response for the given key.
*/
save(key: string, response: StoredResponse): Promise<void>;
/**
* Release a lock without saving a response.
* Called when the handler fails and the request should be retryable.
*/
release(key: string): Promise<void>;
/**
* Delete a key from storage.
* Useful for testing or manual cleanup.
*/
delete(key: string): Promise<void>;
/**
* Clear all keys from storage.
* Useful for testing.
*/
clear(): Promise<void>;
}
/**
* Result of attempting to acquire a lock.
*/
type AcquireResult = {
status: 'acquired';
} | {
status: 'hit';
response: StoredResponse;
} | {
status: 'conflict';
startedAt: number;
} | {
status: 'mismatch';
existingHash: string;
providedHash: string;
};
/**
* A stored idempotency response.
*/
interface StoredResponse {
/** The response data returned by the handler */
data: unknown;
/** When this response was created */
createdAt: number;
/** The hash of the original request (if provided) */
hash: string | null;
}
/**
* Callback fired when a cached response is returned.
*/
type OnHitCallback = (key: string, response: StoredResponse) => void;
/**
* Callback fired when a new request is processed (cache miss).
*/
type OnMissCallback = (key: string) => void;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
type OnConflictCallback = (key: string) => void;
/**
* Callback fired when an error occurs.
*/
type OnErrorCallback = (key: string, error: Error) => void;
/**
* Global configuration options for oncely.
*/
interface OncelyConfig {
/** Storage adapter for persisting idempotency records */
storage?: StorageAdapter;
/**
* Time-to-live for idempotency keys.
* Can be a number (milliseconds) or a string like '24h', '7d', '30m'.
* @default '24h'
*/
ttl?: number | string;
/**
* Whether to validate request fingerprints (hash).
* When true, reusing a key with different payload throws MismatchError.
* @default true
*/
fingerprint?: boolean;
/**
* Whether to log debug information.
* @default false
*/
debug?: boolean;
/**
* Callback fired when a cached response is returned.
*/
onHit?: OnHitCallback;
/**
* Callback fired when a new request is processed (cache miss).
*/
onMiss?: OnMissCallback;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
onConflict?: OnConflictCallback;
/**
* Callback fired when an error occurs.
*/
onError?: OnErrorCallback;
}
/**
* Options for creating an oncely instance.
*/
interface OncelyOptions extends OncelyConfig {
/** Storage adapter for persisting idempotency records (required for instance) */
storage: StorageAdapter;
}
/**
* Options for running an idempotent operation.
*/
interface RunOptions<T> {
/** Unique idempotency key for this request */
key: string;
/**
* Hash of the request body/params for mismatch detection.
* If provided and a cached response exists with a different hash,
* a MismatchError will be thrown.
*/
hash?: string;
/** The handler function to execute */
handler: () => T | Promise<T>;
}
/**
* Result of running an idempotent operation.
*/
interface RunResult<T> {
/** The data returned by the handler (or cached) */
data: T;
/** Whether this was a cache hit */
cached: boolean;
/** Status of the operation */
status: 'created' | 'hit';
/** When the response was originally created (if cached) */
createdAt?: number;
}
/**
* Oncely idempotency service.
* Ensures operations are executed exactly once per idempotency key.
*/
declare class Oncely {
private readonly storage;
private readonly ttl;
private readonly debug;
private readonly onHit?;
private readonly onMiss?;
private readonly onConflict?;
private readonly onError?;
constructor(options: OncelyOptions);
/**
* Run an operation with idempotency protection.
*
* @example
* ```typescript
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => {
* const order = await createOrder(data);
* return order;
* },
* });
*
* if (result.cached) {
* console.log('Returned cached response');
* }
* ```
*/
run<T>(options: RunOptions<T>): Promise<RunResult<T>>;
private log;
}
/**
* Create an oncely idempotency instance.
*
* Options are optional - will use global config or sensible defaults.
*
* @example
* ```typescript
* import { createInstance } from '@oncely/core';
*
* // Zero-config (uses memory storage)
* const idempotency = createInstance();
*
* // With explicit storage
* const idempotency = createInstance({ storage: myRedis });
*
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => createOrder(data),
* });
* ```
*/
declare function createInstance(options?: Partial<OncelyOptions>): Oncely;
/**
* In-memory storage adapter.
* Suitable for development, testing, and single-instance deployments.
*
* **Warning:** Data is lost on restart and not shared across instances.
* Use Redis for production multi-instance deployments.
*/
declare class MemoryStorage implements StorageAdapter {
private store;
private cleanupInterval;
constructor();
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
save(key: string, response: StoredResponse): Promise<void>;
release(key: string): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
/**
* Stop the cleanup interval.
* Call this when shutting down to prevent memory leaks in tests.
*/
destroy(): void;
private cleanup;
}
/**
* RFC 7807 Problem Details response format.
* @see https://www.rfc-editor.org/rfc/rfc7807
*/
interface ProblemDetails {
/** URI reference identifying the problem type */
type: string;
/** Short human-readable summary */
title: string;
/** HTTP status code */
status: number;
/** Detailed human-readable explanation */
detail: string;
/** URI reference to the specific occurrence (optional) */
instance?: string;
/** Additional properties */
[key: string]: unknown;
}
/**
* Base class for all oncely errors.
* Provides RFC 7807 Problem Details format for HTTP responses.
*/
declare class IdempotencyError extends Error {
/** HTTP status code for this error */
readonly statusCode: number;
/** Error type identifier (URL) */
readonly type: string;
/** Short title for the error */
readonly title: string;
constructor(message: string, statusCode: number, type: string, title: string);
/**
* Convert to RFC 7807 Problem Details format.
*/
toProblemDetails(): ProblemDetails;
/**
* Convert to JSON (RFC 7807 format).
*/
toJSON(): ProblemDetails;
}
/**
* Thrown when an idempotency key is required but not provided.
* HTTP 400 Bad Request
*/
declare class MissingKeyError extends IdempotencyError {
constructor();
}
/**
* Thrown when a request with the same key is already being processed.
* HTTP 409 Conflict
*/
declare class ConflictError extends IdempotencyError {
/** When the in-progress request started */
readonly startedAt: number;
/** Suggested retry delay in seconds */
readonly retryAfter: number;
constructor(startedAt: number);
toProblemDetails(): ProblemDetails;
}
/**
* Thrown when the same idempotency key is used with a different request payload.
* HTTP 422 Unprocessable Content
*/
declare class MismatchError extends IdempotencyError {
/** Hash of the original request */
readonly existingHash: string;
/** Hash of the current request */
readonly providedHash: string;
constructor(existingHash: string, providedHash: string);
}
/**
* Thrown when the storage adapter encounters an error.
* HTTP 500 Internal Server Error
*/
declare class StorageError extends IdempotencyError {
/** The underlying error from the storage adapter */
readonly cause: Error;
constructor(message: string, cause: Error);
}
export { type AcquireResult as A, ConflictError as C, IdempotencyError as I, MemoryStorage as M, type OncelyConfig as O, type ProblemDetails as P, type RunOptions as R, type StorageAdapter as S, Oncely as a, MissingKeyError as b, createInstance as c, MismatchError as d, StorageError as e, type StoredResponse as f, type OncelyOptions as g, type RunResult as h, type OnHitCallback as i, type OnMissCallback as j, type OnConflictCallback as k, type OnErrorCallback as l };
/**
* Storage adapter interface for oncely.
* Implement this interface to create custom storage backends.
*/
interface StorageAdapter {
/**
* Attempt to acquire a lock for the given key.
* Returns the current state of the key.
*/
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
/**
* Save a completed response for the given key.
*/
save(key: string, response: StoredResponse): Promise<void>;
/**
* Release a lock without saving a response.
* Called when the handler fails and the request should be retryable.
*/
release(key: string): Promise<void>;
/**
* Delete a key from storage.
* Useful for testing or manual cleanup.
*/
delete(key: string): Promise<void>;
/**
* Clear all keys from storage.
* Useful for testing.
*/
clear(): Promise<void>;
}
/**
* Result of attempting to acquire a lock.
*/
type AcquireResult = {
status: 'acquired';
} | {
status: 'hit';
response: StoredResponse;
} | {
status: 'conflict';
startedAt: number;
} | {
status: 'mismatch';
existingHash: string;
providedHash: string;
};
/**
* A stored idempotency response.
*/
interface StoredResponse {
/** The response data returned by the handler */
data: unknown;
/** When this response was created */
createdAt: number;
/** The hash of the original request (if provided) */
hash: string | null;
}
/**
* Callback fired when a cached response is returned.
*/
type OnHitCallback = (key: string, response: StoredResponse) => void;
/**
* Callback fired when a new request is processed (cache miss).
*/
type OnMissCallback = (key: string) => void;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
type OnConflictCallback = (key: string) => void;
/**
* Callback fired when an error occurs.
*/
type OnErrorCallback = (key: string, error: Error) => void;
/**
* Global configuration options for oncely.
*/
interface OncelyConfig {
/** Storage adapter for persisting idempotency records */
storage?: StorageAdapter;
/**
* Time-to-live for idempotency keys.
* Can be a number (milliseconds) or a string like '24h', '7d', '30m'.
* @default '24h'
*/
ttl?: number | string;
/**
* Whether to validate request fingerprints (hash).
* When true, reusing a key with different payload throws MismatchError.
* @default true
*/
fingerprint?: boolean;
/**
* Whether to log debug information.
* @default false
*/
debug?: boolean;
/**
* Callback fired when a cached response is returned.
*/
onHit?: OnHitCallback;
/**
* Callback fired when a new request is processed (cache miss).
*/
onMiss?: OnMissCallback;
/**
* Callback fired when a conflict occurs (request already in progress).
*/
onConflict?: OnConflictCallback;
/**
* Callback fired when an error occurs.
*/
onError?: OnErrorCallback;
}
/**
* Options for creating an oncely instance.
*/
interface OncelyOptions extends OncelyConfig {
/** Storage adapter for persisting idempotency records (required for instance) */
storage: StorageAdapter;
}
/**
* Options for running an idempotent operation.
*/
interface RunOptions<T> {
/** Unique idempotency key for this request */
key: string;
/**
* Hash of the request body/params for mismatch detection.
* If provided and a cached response exists with a different hash,
* a MismatchError will be thrown.
*/
hash?: string;
/** The handler function to execute */
handler: () => T | Promise<T>;
}
/**
* Result of running an idempotent operation.
*/
interface RunResult<T> {
/** The data returned by the handler (or cached) */
data: T;
/** Whether this was a cache hit */
cached: boolean;
/** Status of the operation */
status: 'created' | 'hit';
/** When the response was originally created (if cached) */
createdAt?: number;
}
/**
* Oncely idempotency service.
* Ensures operations are executed exactly once per idempotency key.
*/
declare class Oncely {
private readonly storage;
private readonly ttl;
private readonly debug;
private readonly onHit?;
private readonly onMiss?;
private readonly onConflict?;
private readonly onError?;
constructor(options: OncelyOptions);
/**
* Run an operation with idempotency protection.
*
* @example
* ```typescript
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => {
* const order = await createOrder(data);
* return order;
* },
* });
*
* if (result.cached) {
* console.log('Returned cached response');
* }
* ```
*/
run<T>(options: RunOptions<T>): Promise<RunResult<T>>;
private log;
}
/**
* Create an oncely idempotency instance.
*
* Options are optional - will use global config or sensible defaults.
*
* @example
* ```typescript
* import { createInstance } from '@oncely/core';
*
* // Zero-config (uses memory storage)
* const idempotency = createInstance();
*
* // With explicit storage
* const idempotency = createInstance({ storage: myRedis });
*
* const result = await idempotency.run({
* key: 'order-123',
* handler: async () => createOrder(data),
* });
* ```
*/
declare function createInstance(options?: Partial<OncelyOptions>): Oncely;
/**
* In-memory storage adapter.
* Suitable for development, testing, and single-instance deployments.
*
* **Warning:** Data is lost on restart and not shared across instances.
* Use Redis for production multi-instance deployments.
*/
declare class MemoryStorage implements StorageAdapter {
private store;
private cleanupInterval;
constructor();
acquire(key: string, hash: string | null, ttl: number): Promise<AcquireResult>;
save(key: string, response: StoredResponse): Promise<void>;
release(key: string): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
/**
* Stop the cleanup interval.
* Call this when shutting down to prevent memory leaks in tests.
*/
destroy(): void;
private cleanup;
}
/**
* RFC 7807 Problem Details response format.
* @see https://www.rfc-editor.org/rfc/rfc7807
*/
interface ProblemDetails {
/** URI reference identifying the problem type */
type: string;
/** Short human-readable summary */
title: string;
/** HTTP status code */
status: number;
/** Detailed human-readable explanation */
detail: string;
/** URI reference to the specific occurrence (optional) */
instance?: string;
/** Additional properties */
[key: string]: unknown;
}
/**
* Base class for all oncely errors.
* Provides RFC 7807 Problem Details format for HTTP responses.
*/
declare class IdempotencyError extends Error {
/** HTTP status code for this error */
readonly statusCode: number;
/** Error type identifier (URL) */
readonly type: string;
/** Short title for the error */
readonly title: string;
constructor(message: string, statusCode: number, type: string, title: string);
/**
* Convert to RFC 7807 Problem Details format.
*/
toProblemDetails(): ProblemDetails;
/**
* Convert to JSON (RFC 7807 format).
*/
toJSON(): ProblemDetails;
}
/**
* Thrown when an idempotency key is required but not provided.
* HTTP 400 Bad Request
*/
declare class MissingKeyError extends IdempotencyError {
constructor();
}
/**
* Thrown when a request with the same key is already being processed.
* HTTP 409 Conflict
*/
declare class ConflictError extends IdempotencyError {
/** When the in-progress request started */
readonly startedAt: number;
/** Suggested retry delay in seconds */
readonly retryAfter: number;
constructor(startedAt: number);
toProblemDetails(): ProblemDetails;
}
/**
* Thrown when the same idempotency key is used with a different request payload.
* HTTP 422 Unprocessable Content
*/
declare class MismatchError extends IdempotencyError {
/** Hash of the original request */
readonly existingHash: string;
/** Hash of the current request */
readonly providedHash: string;
constructor(existingHash: string, providedHash: string);
}
/**
* Thrown when the storage adapter encounters an error.
* HTTP 500 Internal Server Error
*/
declare class StorageError extends IdempotencyError {
/** The underlying error from the storage adapter */
readonly cause: Error;
constructor(message: string, cause: Error);
}
export { type AcquireResult as A, ConflictError as C, IdempotencyError as I, MemoryStorage as M, type OncelyConfig as O, type ProblemDetails as P, type RunOptions as R, type StorageAdapter as S, Oncely as a, MissingKeyError as b, createInstance as c, MismatchError as d, StorageError as e, type StoredResponse as f, type OncelyOptions as g, type RunResult as h, type OnHitCallback as i, type OnMissCallback as j, type OnConflictCallback as k, type OnErrorCallback as l };