{"version":3,"sources":["../../src/errors.ts","../../src/utils/sleep.ts","../../src/utils/id.ts","../../src/storage/redis.ts"],"names":[],"mappings":"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","file":"redis.cjs","sourcesContent":["import type { SerializedError } from './types.js';\n\n/** Mark an error as permanently fatal — the executor must not retry it. */\nexport class PermanentError extends Error {\n  readonly permanent = true as const;\n  readonly code?: string;\n\n  constructor(message: string, options?: { cause?: unknown; code?: string }) {\n    super(message);\n    this.name = 'PermanentError';\n    if (options?.cause !== undefined) (this as { cause?: unknown }).cause = options.cause;\n    if (options?.code) this.code = options.code;\n  }\n}\n\n/** Mark an error as transient — explicitly eligible for retry. */\nexport class TransientError extends Error {\n  readonly transient = true as const;\n  readonly code?: string;\n\n  constructor(message: string, options?: { cause?: unknown; code?: string }) {\n    super(message);\n    this.name = 'TransientError';\n    if (options?.cause !== undefined) (this as { cause?: unknown }).cause = options.cause;\n    if (options?.code) this.code = options.code;\n  }\n}\n\n/** Thrown when a step exceeds its configured timeout. Retryable by default. */\nexport class StepTimeoutError extends Error {\n  readonly timeout = true as const;\n\n  constructor(\n    public readonly stepName: string,\n    public readonly timeoutMs: number,\n  ) {\n    super(`step \"${stepName}\" timed out after ${timeoutMs}ms`);\n    this.name = 'StepTimeoutError';\n  }\n}\n\n/** Thrown when execution is cancelled via AbortSignal. */\nexport class FlowAbortedError extends Error {\n  readonly aborted = true as const;\n\n  constructor(reason?: unknown) {\n    const msg =\n      reason instanceof Error\n        ? reason.message\n        : typeof reason === 'string'\n          ? reason\n          : 'flow aborted';\n    super(msg);\n    this.name = 'FlowAbortedError';\n    if (reason !== undefined) (this as { cause?: unknown }).cause = reason;\n  }\n}\n\n/**\n * Thrown when the storage adapter cannot acquire a lock within the configured\n * wait timeout — typically because another worker is currently executing the\n * same idempotency key.\n */\nexport class LockAcquisitionError extends Error {\n  constructor(\n    public readonly flowName: string,\n    public readonly flowId: string,\n    reason?: string,\n  ) {\n    super(\n      reason\n        ? `failed to acquire lock for ${flowName}/${flowId}: ${reason}`\n        : `failed to acquire lock for ${flowName}/${flowId}`,\n    );\n    this.name = 'LockAcquisitionError';\n  }\n}\n\n/** Thrown by the executor when a step fails and compensation runs. */\nexport class FlowError extends Error {\n  constructor(\n    message: string,\n    public readonly flowId: string,\n    public readonly flowName: string,\n    public readonly failedStep: string,\n    public readonly originalError: unknown,\n    public readonly compensationErrors: Array<{ step: string; error: unknown }> = [],\n  ) {\n    super(message);\n    this.name = 'FlowError';\n    (this as { cause?: unknown }).cause = originalError;\n  }\n}\n\n/** Heuristic: errors are retryable unless explicitly marked permanent. */\nexport function isPermanent(err: unknown): boolean {\n  return (\n    err instanceof PermanentError ||\n    (typeof err === 'object' && err !== null && (err as { permanent?: boolean }).permanent === true)\n  );\n}\n\nexport function isTransient(err: unknown): boolean {\n  return (\n    err instanceof TransientError ||\n    err instanceof StepTimeoutError ||\n    (typeof err === 'object' && err !== null && (err as { transient?: boolean }).transient === true)\n  );\n}\n\nexport function serializeError(err: unknown): SerializedError {\n  if (err instanceof Error) {\n    const out: SerializedError = { name: err.name, message: err.message };\n    if (err.stack) out.stack = err.stack;\n    const withCode = err as { code?: unknown };\n    if (typeof withCode.code === 'string') out.code = withCode.code;\n    if (isTransient(err)) out.transient = true;\n    return out;\n  }\n  return { name: 'UnknownError', message: String(err) };\n}\n","import { FlowAbortedError } from '../errors.js';\n\n/**\n * Promise-based sleep that respects an AbortSignal. Rejects with\n * FlowAbortedError if the signal fires mid-wait.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n  if (ms <= 0) {\n    if (signal?.aborted) return Promise.reject(new FlowAbortedError(signal.reason));\n    return Promise.resolve();\n  }\n\n  return new Promise((resolve, reject) => {\n    if (signal?.aborted) {\n      reject(new FlowAbortedError(signal.reason));\n      return;\n    }\n\n    const timer = setTimeout(() => {\n      signal?.removeEventListener('abort', onAbort);\n      resolve();\n    }, ms);\n\n    const onAbort = () => {\n      clearTimeout(timer);\n      signal?.removeEventListener('abort', onAbort);\n      reject(new FlowAbortedError(signal?.reason));\n    };\n\n    signal?.addEventListener('abort', onAbort, { once: true });\n  });\n}\n","/**\n * Generate a URL-safe random id. Uses Web Crypto where available (Node 18+,\n * modern browsers, Hermes via polyfill); falls back to Math.random.\n */\nexport function generateId(prefix = 'flow'): string {\n  const globalCrypto = (globalThis as { crypto?: Crypto }).crypto;\n  if (globalCrypto?.randomUUID) {\n    return `${prefix}_${globalCrypto.randomUUID()}`;\n  }\n  if (globalCrypto?.getRandomValues) {\n    const bytes = new Uint8Array(16);\n    globalCrypto.getRandomValues(bytes);\n    const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n    return `${prefix}_${hex}`;\n  }\n  let rand = '';\n  for (let i = 0; i < 32; i++) {\n    rand += Math.floor(Math.random() * 16).toString(16);\n  }\n  return `${prefix}_${rand}`;\n}\n","import type { AcquireLockOptions, FlowState, Lock, StorageAdapter } from '../types.js';\nimport { LockAcquisitionError } from '../errors.js';\nimport { sleep } from '../utils/sleep.js';\nimport { generateId } from '../utils/id.js';\n\n/**\n * Structural subset of ioredis' `Redis` class. Only the methods we use are\n * required, so both `ioredis` and compatible clients work.\n */\nexport interface RedisLike {\n  set(key: string, value: string, ...args: (string | number)[]): Promise<string | null>;\n  get(key: string): Promise<string | null>;\n  del(...keys: string[]): Promise<number>;\n  eval(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;\n}\n\nexport interface RedisStorageOptions {\n  client: RedisLike;\n  /** Prefix applied to all keys written by this adapter. Default: `kompensa`. */\n  keyPrefix?: string;\n  /** Polling interval while waiting for a contested lock, in ms. Default: 50. */\n  lockPollMs?: number;\n}\n\n// Safe release — only delete the lock if the token matches. Prevents a\n// process whose TTL has expired from deleting a lock another worker now holds.\nconst RELEASE_SCRIPT = `\nif redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n  return redis.call(\"DEL\", KEYS[1])\nelse\n  return 0\nend\n`;\n\n// Atomic refresh — only extend TTL if the token still matches.\nconst REFRESH_SCRIPT = `\nif redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n  return redis.call(\"PEXPIRE\", KEYS[1], ARGV[2])\nelse\n  return 0\nend\n`;\n\n/**\n * Redis-backed storage with Redlock-style single-node locks.\n *\n * State is serialized to JSON. Locks use `SET NX PX` for atomic acquisition\n * plus a Lua-verified token on release, so a process whose TTL expired cannot\n * accidentally release the lock held by a newer owner.\n *\n * @example\n * import Redis from 'ioredis';\n * import { RedisStorage } from 'kompensa/storage/redis';\n *\n * const storage = new RedisStorage({ client: new Redis(process.env.REDIS_URL) });\n * const flow = createFlow('checkout', { storage }).step(...)\n */\nexport class RedisStorage implements StorageAdapter {\n  private readonly client: RedisLike;\n  private readonly keyPrefix: string;\n  private readonly lockPollMs: number;\n\n  constructor(opts: RedisStorageOptions) {\n    if (!opts.client) {\n      throw new Error('kompensa: RedisStorage requires a `client` option');\n    }\n    this.client = opts.client;\n    this.keyPrefix = opts.keyPrefix ?? 'kompensa';\n    this.lockPollMs = opts.lockPollMs ?? 50;\n  }\n\n  private stateKey(flowName: string, flowId: string): string {\n    return `${this.keyPrefix}:state:${flowName}:${flowId}`;\n  }\n\n  private lockKey(flowName: string, flowId: string): string {\n    return `${this.keyPrefix}:lock:${flowName}:${flowId}`;\n  }\n\n  async load(flowName: string, flowId: string): Promise<FlowState | null> {\n    const raw = await this.client.get(this.stateKey(flowName, flowId));\n    if (!raw) return null;\n    try {\n      return JSON.parse(raw) as FlowState;\n    } catch {\n      return null;\n    }\n  }\n\n  async save(state: FlowState): Promise<void> {\n    await this.client.set(\n      this.stateKey(state.flowName, state.flowId),\n      JSON.stringify(state),\n    );\n  }\n\n  async delete(flowName: string, flowId: string): Promise<void> {\n    await this.client.del(this.stateKey(flowName, flowId));\n  }\n\n  async acquireLock(\n    flowName: string,\n    flowId: string,\n    options: AcquireLockOptions,\n  ): Promise<Lock> {\n    const { ttlMs, timeoutMs } = options;\n    const key = this.lockKey(flowName, flowId);\n    const token = generateId('lk');\n\n    // Fast-path: atomic SET NX PX.\n    const ok = await this.client.set(key, token, 'PX', ttlMs, 'NX');\n    if (ok === 'OK') return this.makeLock(key, token, ttlMs);\n\n    if (timeoutMs <= 0) {\n      throw new LockAcquisitionError(flowName, flowId, 'lock is held');\n    }\n\n    // Poll until acquired or timeout elapses.\n    const start = Date.now();\n    while (Date.now() - start < timeoutMs) {\n      const remaining = timeoutMs - (Date.now() - start);\n      await sleep(Math.min(this.lockPollMs, remaining));\n      const retry = await this.client.set(key, token, 'PX', ttlMs, 'NX');\n      if (retry === 'OK') return this.makeLock(key, token, ttlMs);\n    }\n\n    throw new LockAcquisitionError(\n      flowName,\n      flowId,\n      `wait timeout after ${timeoutMs}ms`,\n    );\n  }\n\n  private makeLock(key: string, token: string, ttlMs: number): Lock {\n    const client = this.client;\n    let released = false;\n    return {\n      async release() {\n        if (released) return;\n        released = true;\n        await client.eval(RELEASE_SCRIPT, 1, key, token);\n      },\n      async refresh() {\n        if (released) return;\n        await client.eval(REFRESH_SCRIPT, 1, key, token, ttlMs);\n      },\n    };\n  }\n}\n\nexport function createRedisStorage(opts: RedisStorageOptions): RedisStorage {\n  return new RedisStorage(opts);\n}\n"]}