npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@verityinc/sdk

v0.1.0

Published

TypeScript SDK for the Verity Durable Execution Ledger — exactly-once effect protection for agentic systems

Downloads

85

Readme

@verityinc/sdk

Exactly-once effect protection for AI agents and autonomous workflows.

Verity is a durable execution ledger that prevents duplicate side effects (API calls, payments, emails, database writes) — even if your agent crashes, retries, or runs concurrently with other agents. It enforces exactly-once semantics per effect key using time-bound leases and fencing tokens, with optional crash recovery via observe().

npm install @verityinc/sdk

Quick Start

import { VerityClient } from '@verityinc/sdk';

const verity = new VerityClient({
  baseUrl: 'https://api.useverity.com/v1',
  apiKey: 'vt_live_xxxxxx',
  namespace: 'payments',
});

// Protect a side effect — Verity guarantees it runs exactly once
const charge = await verity.protect('charge-order-456', {
  act: () => stripe.charges.create({ amount: 5000, currency: 'usd' }),
});

If an agent crashes mid-flight, the next attempt either returns the cached result (if already committed) or uses observe() to detect the completed action in the external system and safely commit it — preventing duplicate charges.

How It Works

Every call to protect() follows the lease → observe → act → commit pattern:

  1. Lease — acquire exclusive, time-bound access to the effect
  2. Observe (optional) — check the external system for an already-completed action (crash recovery)
  3. Act — execute the real-world side effect
  4. Commit — record the result in Verity's ledger

protect() performs these steps under the hood:

Agent A                          Verity                         Stripe
  │                                │                              │
  ├── requestLease ───────────────►│                              │
  │◄── granted (fenceToken: 1) ───┤                              │
  │                                │                              │
  ├── stripe.charges.create ──────────────────────────────────────►
  │◄── { id: "ch_xxx" } ─────────────────────────────────────────┤
  │                                │                              │
  ├── commit(result) ─────────────►│                              │
  │◄── accepted ──────────────────┤                              │

If Agent A crashes after charging but before committing, Agent B picks up the expired lease. If you provided observe(), it checks Stripe to see if the charge already went through and commits the existing result — no duplicate. Without observe(), the SDK re-runs act(), so use downstream idempotency keys as a fallback.

When Should I Provide observe()?

  • Provide it when a retry could have already executed the real-world action (e.g., agent crashed after calling Stripe but before committing). observe() checks the external system so the retrier doesn't re-execute.
  • Skip it if the downstream system supports idempotency keys (e.g., Stripe's Idempotency-Key header, a database unique constraint). In that case, re-executing act() is inherently safe.
  • Best practice: use both — downstream idempotency keys as your primary defense, observe() as a safety net that avoids unnecessary calls.

Standalone Protection

For one-off effects where you control the idempotency key:

const result = await verity.protect('send-welcome-email:user_42', {
  act: () => emailService.send({ to: '[email protected]', template: 'welcome' }),
});

With Observe (Crash Recovery)

const charge = await verity.protect('charge-order-789', {
  // Called only when a prior agent's lease expired (potential crash)
  observe: async () => {
    const existing = await stripe.charges.list({ metadata: { orderId: '789' } });
    return existing.data.length > 0 ? existing.data[0] : null;
  },
  act: () => stripe.charges.create({
    amount: 5000,
    currency: 'usd',
    metadata: { orderId: '789' },
  }),
});

Key Suffix for Cardinality

When the same action targets multiple entities:

// Send emails to multiple recipients — each gets its own effect
for (const recipient of recipients) {
  await verity.protect('send-notification', {
    act: () => emailService.send({ to: recipient }),
  }, {
    keySuffix: recipient, // effectKey = "send-notification:[email protected]"
  });
}

Workflow Mode (Multi-Effect Orchestration)

For workflows with multiple steps that need to be tracked together:

const run = verity.workflow('refund_flow').case('order_123').run();

// Each effect gets a deterministic key: "order_123:notify_customer"
await run.protect('notify_customer', {
  observe: async () => {
    const sent = await emailService.checkSent('order_123');
    return sent ? { emailId: sent.id } : null;
  },
  act: () => emailService.send({ to: customer.email, template: 'refund_initiated' }),
});

// effectKey = "order_123:refund_payment"
await run.protect('refund_payment', {
  act: () => stripe.refunds.create({ charge: chargeId }),
});

Key behavior: if run_001 crashes after notify_customer succeeds but before refund_payment, then run_002 retries the same workflow — notify_customer returns the cached result instantly, and only refund_payment executes.

Per-Effect Namespace Override

A single workflow can span multiple namespaces. This matters because namespace is the kill-switch boundary — freezing "payments" won't block "notifications", even if both belong to the same workflow.

const run = verity.workflow('refund_flow').case('order_123').run();

await run.protect('notify_customer', {
  act: () => emailService.send({ to: customer.email, template: 'refund' }),
}, { namespace: 'notifications' });

await run.protect('refund_payment', {
  act: () => stripe.refunds.create({ charge: chargeId }),
}, { namespace: 'payments' });

await run.protect('release_inventory', {
  act: () => inventoryService.releaseHold('order_123'),
}, { namespace: 'inventory' });

The namespace resolution order is:

  1. Per-effectrun.protect('step', opts, { namespace: 'payments' })
  2. Per-run.run({ namespace: 'custom' })
  3. Default — falls back to workflowName

Namespace ≠ Workflow. Namespace is an operational boundary (freeze/unfreeze, scoped API keys). Workflow name is a logical grouping (Explorer UI, traceability). They compose independently — one workflow can touch many namespaces, and one namespace can serve many workflows.

Effect Key Construction

| Mode | Key derived from | Example | |---|---|---| | verity.protect(key, ...) | You provide it | "charge-order-456" | | run.protect(name, ...) | caseId:effectName | "order_123:refund_payment" | | run.protect(name, ..., { keySuffix }) | caseId:effectName:suffix | "order_123:email.send:[email protected]" |

In workflow mode, the runId is not part of the key — this is intentional. It ensures idempotency across retry runs for the same case.

Configuration

const verity = new VerityClient({
  // Required
  baseUrl: 'https://api.useverity.com/v1',
  apiKey: 'vt_live_xxxxxx',

  // Optional
  namespace: 'payments',         // default namespace for all calls
  agentId: 'worker-1',           // identifies this agent in audit logs
  autoRenew: true,               // auto-renew leases in background (default: true)
  renewAtFraction: 0.65,         // renew at 65% of lease duration (default: 0.65)
  requestTimeoutMs: 20_000,      // HTTP timeout per request (default: 20s)

  // Conflict retry (when another agent holds the lease)
  conflictRetry: {
    enabled: true,               // default: true
    maxAttempts: 12,             // default: 12
    initialDelayMs: 500,         // default: 500ms
    maxDelayMs: 15_000,          // default: 15s
    jitter: true,                // ±30% randomization (default: true)
  },

  // Custom logger (defaults to console.warn/error)
  logger: {
    warn: (msg, ...args) => pino.warn(msg, ...args),
    error: (msg, ...args) => pino.error(msg, ...args),
    debug: (msg, ...args) => pino.debug(msg, ...args), // optional
  },
});

Error Handling

The SDK provides structured error classes so you can handle each case precisely:

import {
  VerityError,
  LeaseConflictError,
  EffectPreviouslyFailedError,
  CommitUncertainError,
  VerityValidationError,
} from '@verityinc/sdk';

try {
  await verity.protect('charge-order-456', { act: () => charge() });
} catch (err) {
  if (err instanceof CommitUncertainError) {
    // The real-world action SUCCEEDED but Verity couldn't confirm the commit.
    // DO NOT RETRY protect() — you'll risk a duplicate side effect.
    //
    // Instead:
    //   1. HALT the workflow immediately
    //   2. Check the effect state via Explorer UI or query API
    //   3. Manually commit or reconcile if needed
    console.error('Action ran but commit uncertain:', err.result);
    await alertOps(err);
  }

  if (err instanceof LeaseConflictError) {
    // Another agent is processing this effect (retries exhausted)
    console.log('Effect is being handled by another agent');
  }

  if (err instanceof EffectPreviouslyFailedError) {
    // This effect failed on a prior attempt — admin reset required
    console.log('Prior failure:', err.cachedError);
  }

  if (err instanceof VerityValidationError) {
    // Payload too large (>64KB) or not JSON-serializable
    console.log('Fix your input:', err.message);
  }
}

Error Hierarchy

VerityError (base — catch all Verity errors)
├── VerityApiError               — non-2xx response from API
├── VerityConfigError            — missing baseUrl, apiKey, etc.
├── VerityValidationError        — payload not serializable or >64KB
├── LeaseConflictError           — 409: another agent holds the lease
├── EffectPreviouslyFailedError  — cached failure, needs admin reset
└── CommitUncertainError         — action succeeded, commit unconfirmed

Low-Level API

For power users who want manual control over the lease lifecycle:

// 1. Request a lease
const lease = await verity.requestLease('payments', {
  effectKey: 'charge-order-456',
  leaseDurationMs: 30_000,
});

if (lease.status === 'granted') {
  // 2. Execute your action
  const result = await stripe.charges.create({ amount: 5000 });

  // 3. Commit the result
  await verity.commit('payments', {
    effectKey: 'charge-order-456',
    fenceToken: lease.fenceToken,
    leaseToken: lease.leaseToken,
    result,
  });
}

if (lease.status === 'cached_completed') {
  // Already done — use lease.cachedResult
}

Auto Lease Renewal

By default, the SDK automatically renews leases in the background while act() runs. This prevents lease expiry during long-running actions.

  • Renewal fires at 65% of the lease duration (configurable via renewAtFraction)
  • Schedules based on the server-returned expiry (not a fixed interval)
  • Non-overlapping: won't fire a second renewal while one is in flight
  • Stops automatically on commit/fail or if the lease is lost (409/404)
  • Timers are unref()'d so they won't keep your Node.js process alive

Disable with autoRenew: false if you prefer manual control.

Requirements

  • Node.js >= 18 (uses native fetch and crypto.randomUUID())
  • TypeScript >= 5.0 (optional, but types are included)
  • Zero runtime dependencies

License

MIT