@drip-sdk/node 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stream-meter.ts","../src/index.ts"],"names":["StreamMeter","chargeFn","options","quantity","idempotencyKey","chargeResult","result","DripError","_DripError","message","statusCode","code","Drip","config","path","controller","timeoutId","res","data","error","healthBaseUrl","start","response","latencyMs","status","timestamp","params","customerId","query","chargeId","webhookId","runId","events","plan","startTime","workflowId","workflowName","existing","w","created","c","run","eventsCreated","eventsDuplicates","batchEvents","event","index","batchResult","endResult","durationMs","eventSummary","summary","components","hash","str","i","char","payload","signature","secret","tolerance","parts","timestampPart","p","signaturePart","providedSignature","now","signaturePayload","encoder","keyData","payloadData","cryptoKey","signatureBuffer","expectedSignature","b","crypto","sigBuffer","expectedBuffer","ts","index_default"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * StreamMeter - Accumulate usage locally and charge once at the end.\n *\n * Perfect for LLM token streaming and other high-frequency metering scenarios\n * where you want to avoid making an API call for every small increment.\n *\n * @example\n * ```typescript\n * const meter = drip.createStreamMeter({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * });\n *\n * for await (const chunk of llmStream) {\n * meter.add(chunk.tokens);\n * }\n *\n * const result = await meter.flush();\n * console.log(`Charged ${result.charge.amountUsdc} for ${result.quantity} tokens`);\n * ```\n */\n\nimport type { ChargeResult, ChargeParams } from './index.js';\n\n/**\n * Options for creating a StreamMeter.\n */\nexport interface StreamMeterOptions {\n /**\n * The Drip customer ID to charge.\n */\n customerId: string;\n\n /**\n * The usage meter/type to record against.\n * Must match a meter configured in your pricing plan.\n */\n meter: string;\n\n /**\n * Unique key to prevent duplicate charges.\n * If not provided, one will be generated.\n */\n idempotencyKey?: string;\n\n /**\n * Additional metadata to attach to the charge.\n */\n metadata?: Record<string, unknown>;\n\n /**\n * Auto-flush when accumulated quantity reaches this threshold.\n * Useful for long-running streams where you want periodic charges.\n */\n flushThreshold?: number;\n\n /**\n * Callback invoked on each add() call.\n * Useful for logging or progress tracking.\n */\n onAdd?: (quantity: number, total: number) => void;\n\n /**\n * Callback invoked after each successful flush.\n */\n onFlush?: (result: StreamMeterFlushResult) => void;\n}\n\n/**\n * Result of flushing a StreamMeter.\n */\nexport interface StreamMeterFlushResult {\n /** Whether the flush was successful */\n success: boolean;\n\n /** The quantity that was charged */\n quantity: number;\n\n /** The charge result from the API (if quantity > 0) */\n charge: ChargeResult['charge'] | null;\n\n /** Whether this was an idempotent replay */\n isReplay: boolean;\n}\n\n/**\n * Internal charge function type (injected by Drip class).\n */\nexport type ChargeFn = (params: ChargeParams) => Promise<ChargeResult>;\n\n/**\n * StreamMeter accumulates usage locally and charges once when flushed.\n *\n * This is ideal for:\n * - LLM token streaming (charge once at end of stream)\n * - High-frequency events (batch small increments)\n * - Partial failure handling (charge for what was delivered)\n */\nexport class StreamMeter {\n private _total: number = 0;\n private _flushed: boolean = false;\n private _flushCount: number = 0;\n private readonly _chargeFn: ChargeFn;\n private readonly _options: StreamMeterOptions;\n\n /**\n * Creates a new StreamMeter.\n *\n * @param chargeFn - The charge function from Drip client\n * @param options - StreamMeter configuration\n */\n constructor(chargeFn: ChargeFn, options: StreamMeterOptions) {\n this._chargeFn = chargeFn;\n this._options = options;\n }\n\n /**\n * Current accumulated quantity (not yet charged).\n */\n get total(): number {\n return this._total;\n }\n\n /**\n * Whether this meter has been flushed at least once.\n */\n get isFlushed(): boolean {\n return this._flushed;\n }\n\n /**\n * Number of times this meter has been flushed.\n */\n get flushCount(): number {\n return this._flushCount;\n }\n\n /**\n * Add quantity to the accumulated total.\n *\n * If a flushThreshold is set and the total exceeds it,\n * this will automatically trigger a flush.\n *\n * @param quantity - Amount to add (must be positive)\n * @returns Promise that resolves when add completes (may trigger auto-flush)\n */\n async add(quantity: number): Promise<StreamMeterFlushResult | null> {\n if (quantity <= 0) {\n return null;\n }\n\n this._total += quantity;\n\n // Invoke callback if provided\n this._options.onAdd?.(quantity, this._total);\n\n // Check for auto-flush threshold\n if (\n this._options.flushThreshold !== undefined &&\n this._total >= this._options.flushThreshold\n ) {\n return this.flush();\n }\n\n return null;\n }\n\n /**\n * Synchronously add quantity without auto-flush.\n * Use this for maximum performance when you don't need threshold-based flushing.\n *\n * @param quantity - Amount to add (must be positive)\n */\n addSync(quantity: number): void {\n if (quantity <= 0) {\n return;\n }\n\n this._total += quantity;\n\n // Invoke callback if provided\n this._options.onAdd?.(quantity, this._total);\n }\n\n /**\n * Flush accumulated usage and charge the customer.\n *\n * If total is 0, returns a success result with no charge.\n * After flush, the meter resets to 0 and can be reused.\n *\n * @returns The flush result including charge details\n */\n async flush(): Promise<StreamMeterFlushResult> {\n const quantity = this._total;\n\n // Reset total before charging to avoid double-counting on retry\n this._total = 0;\n\n // Nothing to charge\n if (quantity === 0) {\n const result: StreamMeterFlushResult = {\n success: true,\n quantity: 0,\n charge: null,\n isReplay: false,\n };\n return result;\n }\n\n // Generate idempotency key for this flush\n const idempotencyKey = this._options.idempotencyKey\n ? `${this._options.idempotencyKey}_flush_${this._flushCount}`\n : undefined;\n\n // Charge the customer\n const chargeResult = await this._chargeFn({\n customerId: this._options.customerId,\n meter: this._options.meter,\n quantity,\n idempotencyKey,\n metadata: this._options.metadata,\n });\n\n this._flushed = true;\n this._flushCount++;\n\n const result: StreamMeterFlushResult = {\n success: chargeResult.success,\n quantity,\n charge: chargeResult.charge,\n isReplay: chargeResult.isReplay,\n };\n\n // Invoke callback if provided\n this._options.onFlush?.(result);\n\n return result;\n }\n\n /**\n * Reset the meter without charging.\n * Use this to discard accumulated usage (e.g., on error before delivery).\n */\n reset(): void {\n this._total = 0;\n }\n}\n","/**\n * Drip SDK - Usage-based billing for Node.js\n *\n * The official SDK for integrating with the Drip billing platform.\n * Provides methods for managing customers, recording usage, handling charges,\n * and configuring webhooks.\n *\n * @packageDocumentation\n */\n\nimport { StreamMeter, type StreamMeterOptions } from './stream-meter.js';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration options for the Drip SDK client.\n */\nexport interface DripConfig {\n /**\n * Your Drip API key. Obtain this from the Drip dashboard.\n * @example \"drip_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Drip API. Defaults to production API.\n * Override for staging/development environments.\n * @default \"https://api.drip.dev/v1\"\n */\n baseUrl?: string;\n\n /**\n * Request timeout in milliseconds.\n * @default 30000\n */\n timeout?: number;\n}\n\n// ============================================================================\n// Customer Types\n// ============================================================================\n\n/**\n * Parameters for creating a new customer.\n */\nexport interface CreateCustomerParams {\n /**\n * Your internal customer/user ID for reconciliation.\n * @example \"user_12345\"\n */\n externalCustomerId?: string;\n\n /**\n * The customer's Drip Smart Account address (derived from their EOA).\n * @example \"0x1234567890abcdef...\"\n */\n onchainAddress: string;\n\n /**\n * Additional metadata to store with the customer.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * A Drip customer record.\n */\nexport interface Customer {\n /** Unique customer ID in Drip */\n id: string;\n\n /** Your business ID */\n businessId: string;\n\n /** Your external customer ID (if provided) */\n externalCustomerId: string | null;\n\n /** Customer's on-chain address */\n onchainAddress: string;\n\n /** Custom metadata */\n metadata: Record<string, unknown> | null;\n\n /** ISO timestamp of creation */\n createdAt: string;\n\n /** ISO timestamp of last update */\n updatedAt: string;\n}\n\n/**\n * Options for listing customers.\n */\nexport interface ListCustomersOptions {\n /**\n * Maximum number of customers to return (1-100).\n * @default 100\n */\n limit?: number;\n\n /**\n * Filter by customer status.\n */\n status?: 'ACTIVE' | 'LOW_BALANCE' | 'PAUSED';\n}\n\n/**\n * Response from listing customers.\n */\nexport interface ListCustomersResponse {\n /** Array of customers */\n data: Customer[];\n\n /** Total count returned */\n count: number;\n}\n\n/**\n * Customer balance information.\n */\nexport interface BalanceResult {\n /** Customer ID */\n customerId: string;\n\n /** On-chain address */\n onchainAddress: string;\n\n /** Balance in USDC (6 decimals) - matches backend field name */\n balanceUsdc: string;\n\n /** Pending charges in USDC */\n pendingChargesUsdc: string;\n\n /** Available USDC (balance minus pending) */\n availableUsdc: string;\n\n /** ISO timestamp of last balance sync */\n lastSyncedAt: string | null;\n}\n\n// ============================================================================\n// Usage & Charge Types\n// ============================================================================\n\n/**\n * Parameters for recording usage and charging a customer.\n */\nexport interface ChargeParams {\n /**\n * The Drip customer ID to charge.\n */\n customerId: string;\n\n /**\n * The usage meter/type to record against.\n * Must match a meter configured in your pricing plan.\n * @example \"api_calls\", \"compute_seconds\", \"storage_gb\"\n */\n meter: string;\n\n /**\n * The quantity of usage to record.\n * Will be multiplied by the meter's unit price.\n */\n quantity: number;\n\n /**\n * Unique key to prevent duplicate charges.\n * If provided, retrying with the same key returns the original charge.\n * @example \"req_abc123\"\n */\n idempotencyKey?: string;\n\n /**\n * Additional metadata to attach to this usage event.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of a successful charge operation.\n */\nexport interface ChargeResult {\n /** Whether the charge was successful */\n success: boolean;\n\n /** The usage event ID */\n usageEventId: string;\n\n /** True if this was an idempotent replay (returned cached result from previous request) */\n isReplay: boolean;\n\n /** Details about the charge */\n charge: {\n /** Unique charge ID */\n id: string;\n\n /** Amount charged in USDC (6 decimals) */\n amountUsdc: string;\n\n /** Amount in native token */\n amountToken: string;\n\n /** Blockchain transaction hash */\n txHash: string;\n\n /** Current status of the charge */\n status: ChargeStatus;\n };\n}\n\n/**\n * Possible charge statuses.\n */\nexport type ChargeStatus =\n | 'PENDING'\n | 'SUBMITTED'\n | 'CONFIRMED'\n | 'FAILED'\n | 'REFUNDED';\n\n/**\n * Parameters for tracking usage without billing.\n * Use this for internal visibility, pilots, or pre-billing tracking.\n */\nexport interface TrackUsageParams {\n /**\n * The Drip customer ID to track usage for.\n * @example \"cust_abc123\"\n */\n customerId: string;\n\n /**\n * The meter/usage type (e.g., 'api_calls', 'tokens').\n */\n meter: string;\n\n /**\n * The quantity of usage to record.\n */\n quantity: number;\n\n /**\n * Unique key to prevent duplicate records.\n */\n idempotencyKey?: string;\n\n /**\n * Human-readable unit label (e.g., 'tokens', 'requests').\n */\n units?: string;\n\n /**\n * Human-readable description of this usage event.\n */\n description?: string;\n\n /**\n * Additional metadata to attach to this usage event.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of tracking usage (no billing).\n */\nexport interface TrackUsageResult {\n /** Whether the usage was recorded */\n success: boolean;\n\n /** The usage event ID */\n usageEventId: string;\n\n /** Customer ID */\n customerId: string;\n\n /** Usage type that was recorded */\n usageType: string;\n\n /** Quantity recorded */\n quantity: number;\n\n /** Whether this customer is internal-only */\n isInternal: boolean;\n\n /** Confirmation message */\n message: string;\n}\n\n/**\n * A detailed charge record.\n */\nexport interface Charge {\n /** Unique charge ID */\n id: string;\n\n /** Associated usage event ID */\n usageId: string;\n\n /** Customer ID */\n customerId: string;\n\n /** Customer details */\n customer: {\n id: string;\n onchainAddress: string;\n externalCustomerId: string | null;\n };\n\n /** Usage event details */\n usageEvent: {\n id: string;\n type: string;\n quantity: string;\n metadata: Record<string, unknown> | null;\n };\n\n /** Amount in USDC */\n amountUsdc: string;\n\n /** Amount in native token */\n amountToken: string;\n\n /** Transaction hash (if submitted) */\n txHash: string | null;\n\n /** Block number (if confirmed) */\n blockNumber: string | null;\n\n /** Current status */\n status: ChargeStatus;\n\n /** Failure reason (if failed) */\n failureReason: string | null;\n\n /** ISO timestamp of creation */\n createdAt: string;\n\n /** ISO timestamp of confirmation */\n confirmedAt: string | null;\n}\n\n/**\n * Options for listing charges.\n */\nexport interface ListChargesOptions {\n /**\n * Filter by customer ID.\n */\n customerId?: string;\n\n /**\n * Filter by charge status.\n */\n status?: ChargeStatus;\n\n /**\n * Maximum number of charges to return (1-100).\n * @default 100\n */\n limit?: number;\n\n /**\n * Number of charges to skip (for pagination).\n * @default 0\n */\n offset?: number;\n}\n\n/**\n * Response from listing charges.\n */\nexport interface ListChargesResponse {\n /** Array of charges */\n data: Charge[];\n\n /** Total count returned */\n count: number;\n}\n\n// ============================================================================\n// Webhook Types\n// ============================================================================\n\n/**\n * Available webhook event types.\n */\nexport type WebhookEventType =\n | 'customer.balance.low'\n | 'usage.recorded'\n | 'charge.succeeded'\n | 'charge.failed'\n | 'customer.deposit.confirmed'\n | 'customer.withdraw.confirmed'\n | 'customer.usage_cap.reached'\n | 'webhook.endpoint.unhealthy'\n | 'customer.created'\n | 'api_key.created'\n | 'pricing_plan.updated'\n | 'transaction.created'\n | 'transaction.pending'\n | 'transaction.confirmed'\n | 'transaction.failed';\n\n/**\n * Parameters for creating a webhook.\n */\nexport interface CreateWebhookParams {\n /**\n * The URL to send webhook events to.\n * Must be HTTPS in production.\n * @example \"https://api.yourapp.com/webhooks/drip\"\n */\n url: string;\n\n /**\n * Array of event types to subscribe to.\n * @example [\"charge.succeeded\", \"charge.failed\"]\n */\n events: WebhookEventType[];\n\n /**\n * Optional description for the webhook.\n */\n description?: string;\n}\n\n/**\n * A webhook configuration.\n */\nexport interface Webhook {\n /** Unique webhook ID */\n id: string;\n\n /** Webhook endpoint URL */\n url: string;\n\n /** Subscribed event types */\n events: string[];\n\n /** Description */\n description: string | null;\n\n /** Whether the webhook is active */\n isActive: boolean;\n\n /** ISO timestamp of creation */\n createdAt: string;\n\n /** ISO timestamp of last update */\n updatedAt: string;\n\n /** Delivery statistics */\n stats?: {\n totalDeliveries: number;\n successfulDeliveries: number;\n failedDeliveries: number;\n lastDeliveryAt: string | null;\n };\n}\n\n/**\n * Response from creating a webhook.\n */\nexport interface CreateWebhookResponse extends Webhook {\n /**\n * The webhook signing secret.\n * Only returned once at creation time - save it securely!\n */\n secret: string;\n\n /** Reminder to save the secret */\n message: string;\n}\n\n/**\n * Response from listing webhooks.\n */\nexport interface ListWebhooksResponse {\n /** Array of webhooks */\n data: Webhook[];\n\n /** Total count */\n count: number;\n}\n\n/**\n * Response from deleting a webhook.\n */\nexport interface DeleteWebhookResponse {\n /** Whether the deletion was successful */\n success: boolean;\n}\n\n// ============================================================================\n// Checkout Types\n// ============================================================================\n\n/**\n * Parameters for creating a checkout session.\n * This is the primary way to get money into a customer's account.\n */\nexport interface CheckoutParams {\n /**\n * Existing customer ID (optional).\n * If not provided, a new customer is created after payment.\n */\n customerId?: string;\n\n /**\n * Your internal customer/user ID for new customers.\n * Used to link the Drip customer to your system.\n */\n externalCustomerId?: string;\n\n /**\n * Amount in cents (e.g., 5000 = $50.00).\n * Minimum: 500 ($5.00)\n * Maximum: 1000000 ($10,000.00)\n */\n amount: number;\n\n /**\n * URL to redirect after successful payment.\n * Query params will be added: session_id, customer_id, status\n */\n returnUrl: string;\n\n /**\n * URL to redirect if user cancels (optional).\n */\n cancelUrl?: string;\n\n /**\n * Custom metadata to attach to this checkout.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of creating a checkout session.\n */\nexport interface CheckoutResult {\n /** Checkout session ID */\n id: string;\n\n /** URL to redirect user to for payment */\n url: string;\n\n /** ISO timestamp when session expires (30 minutes) */\n expiresAt: string;\n\n /** Amount in USD */\n amountUsd: number;\n}\n\n// ============================================================================\n// Run & Event Types (Execution Ledger)\n// ============================================================================\n\n/**\n * Parameters for creating a new workflow.\n */\nexport interface CreateWorkflowParams {\n /** Human-readable workflow name */\n name: string;\n\n /** URL-safe identifier (lowercase alphanumeric with underscores/hyphens) */\n slug: string;\n\n /** Type of workflow */\n productSurface?: 'RPC' | 'WEBHOOK' | 'AGENT' | 'PIPELINE' | 'CUSTOM';\n\n /** Optional description */\n description?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * A workflow definition.\n */\nexport interface Workflow {\n id: string;\n name: string;\n slug: string;\n productSurface: string;\n description: string | null;\n isActive: boolean;\n createdAt: string;\n}\n\n/**\n * Parameters for starting a new agent run.\n */\nexport interface StartRunParams {\n /** Customer ID this run belongs to */\n customerId: string;\n\n /** Workflow ID this run executes */\n workflowId: string;\n\n /** Your external run ID for correlation */\n externalRunId?: string;\n\n /** Correlation ID for distributed tracing */\n correlationId?: string;\n\n /** Parent run ID for nested runs */\n parentRunId?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of starting a run.\n */\nexport interface RunResult {\n id: string;\n customerId: string;\n workflowId: string;\n workflowName: string;\n status: RunStatus;\n correlationId: string | null;\n createdAt: string;\n}\n\n/**\n * Parameters for ending/updating a run.\n */\nexport interface EndRunParams {\n /** New status for the run */\n status: 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';\n\n /** Error message if failed */\n errorMessage?: string;\n\n /** Error code for categorization */\n errorCode?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Possible run statuses.\n */\nexport type RunStatus =\n | 'PENDING'\n | 'RUNNING'\n | 'COMPLETED'\n | 'FAILED'\n | 'CANCELLED'\n | 'TIMEOUT';\n\n/**\n * Parameters for emitting an event to a run.\n */\nexport interface EmitEventParams {\n /** Run ID to attach this event to */\n runId: string;\n\n /** Event type (e.g., \"agent.step\", \"rpc.request\") */\n eventType: string;\n\n /** Quantity of units consumed */\n quantity?: number;\n\n /** Human-readable unit label */\n units?: string;\n\n /** Human-readable description */\n description?: string;\n\n /** Cost in abstract units */\n costUnits?: number;\n\n /** Currency for cost */\n costCurrency?: string;\n\n /** Correlation ID for tracing */\n correlationId?: string;\n\n /** Parent event ID for trace tree */\n parentEventId?: string;\n\n /** OpenTelemetry-style span ID */\n spanId?: string;\n\n /** Idempotency key (auto-generated if not provided) */\n idempotencyKey?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of emitting an event.\n */\nexport interface EventResult {\n id: string;\n runId: string;\n eventType: string;\n quantity: number;\n costUnits: number | null;\n isDuplicate: boolean;\n timestamp: string;\n}\n\n// ============================================================================\n// Meter Types\n// ============================================================================\n\n/**\n * A meter (usage type) from a pricing plan.\n */\nexport interface Meter {\n /** Pricing plan ID */\n id: string;\n\n /** Human-readable name */\n name: string;\n\n /** The meter/usage type identifier (use this in charge() calls) */\n meter: string;\n\n /** Price per unit in USD */\n unitPriceUsd: string;\n\n /** Whether this meter is active */\n isActive: boolean;\n}\n\n/**\n * Response from listing meters.\n */\nexport interface ListMetersResponse {\n /** Array of available meters */\n data: Meter[];\n\n /** Total count */\n count: number;\n}\n\n// ============================================================================\n// Record Run Types (Simplified API)\n// ============================================================================\n\n/**\n * A single event to record in a run.\n */\nexport interface RecordRunEvent {\n /** Event type (e.g., \"agent.step\", \"tool.call\") */\n eventType: string;\n\n /** Quantity of units consumed */\n quantity?: number;\n\n /** Human-readable unit label */\n units?: string;\n\n /** Human-readable description */\n description?: string;\n\n /** Cost in abstract units */\n costUnits?: number;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for recording a complete run in one call.\n * This is the simplified API that combines workflow, run, and events.\n */\nexport interface RecordRunParams {\n /** Customer ID this run belongs to */\n customerId: string;\n\n /**\n * Workflow identifier. Can be:\n * - An existing workflow ID (e.g., \"wf_abc123\")\n * - A slug that will be auto-created if it doesn't exist (e.g., \"my_agent\")\n */\n workflow: string;\n\n /** Events that occurred during the run */\n events: RecordRunEvent[];\n\n /** Final status of the run */\n status: 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';\n\n /** Error message if status is FAILED */\n errorMessage?: string;\n\n /** Error code if status is FAILED */\n errorCode?: string;\n\n /** Your external run ID for correlation */\n externalRunId?: string;\n\n /** Correlation ID for distributed tracing */\n correlationId?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of recording a run.\n */\nexport interface RecordRunResult {\n /** The created run */\n run: {\n id: string;\n workflowId: string;\n workflowName: string;\n status: RunStatus;\n durationMs: number | null;\n };\n\n /** Summary of events created */\n events: {\n created: number;\n duplicates: number;\n };\n\n /** Total cost computed */\n totalCostUnits: string | null;\n\n /** Human-readable summary */\n summary: string;\n}\n\n/**\n * Full run timeline response.\n */\nexport interface RunTimeline {\n run: {\n id: string;\n customerId: string;\n customerName: string | null;\n workflowId: string;\n workflowName: string;\n status: RunStatus;\n startedAt: string | null;\n endedAt: string | null;\n durationMs: number | null;\n errorMessage: string | null;\n errorCode: string | null;\n correlationId: string | null;\n metadata: Record<string, unknown> | null;\n };\n timeline: Array<{\n id: string;\n eventType: string;\n quantity: number;\n units: string | null;\n description: string | null;\n costUnits: number | null;\n timestamp: string;\n correlationId: string | null;\n parentEventId: string | null;\n charge: {\n id: string;\n amountUsdc: string;\n status: string;\n } | null;\n }>;\n totals: {\n eventCount: number;\n totalQuantity: string;\n totalCostUnits: string;\n totalChargedUsdc: string;\n };\n summary: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Error thrown by Drip SDK operations.\n */\nexport class DripError extends Error {\n /**\n * Creates a new DripError.\n * @param message - Human-readable error message\n * @param statusCode - HTTP status code from the API\n * @param code - Machine-readable error code\n */\n constructor(\n message: string,\n public statusCode: number,\n public code?: string,\n ) {\n super(message);\n this.name = 'DripError';\n Object.setPrototypeOf(this, DripError.prototype);\n }\n}\n\n// ============================================================================\n// Main SDK Class\n// ============================================================================\n\n/**\n * The main Drip SDK client.\n *\n * @example\n * ```typescript\n * import { Drip } from '@drip-sdk/node';\n *\n * const drip = new Drip({\n * apiKey: process.env.DRIP_API_KEY!,\n * });\n *\n * // Create a customer\n * const customer = await drip.createCustomer({\n * onchainAddress: '0x...',\n * externalCustomerId: 'user_123',\n * });\n *\n * // Record usage and charge\n * const result = await drip.charge({\n * customerId: customer.id,\n * meter: 'api_calls',\n * quantity: 100,\n * });\n *\n * console.log(`Charged ${result.charge.amountUsdc} USDC`);\n * ```\n */\nexport class Drip {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n /**\n * Creates a new Drip SDK client.\n *\n * @param config - Configuration options\n * @throws {Error} If apiKey is not provided\n *\n * @example\n * ```typescript\n * const drip = new Drip({\n * apiKey: 'drip_live_abc123...',\n * });\n * ```\n */\n constructor(config: DripConfig) {\n if (!config.apiKey) {\n throw new Error('Drip API key is required');\n }\n\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://api.drip.dev/v1';\n this.timeout = config.timeout || 30000;\n }\n\n /**\n * Makes an authenticated request to the Drip API.\n * @internal\n */\n private async request<T>(\n path: string,\n options: RequestInit = {},\n ): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}${path}`, {\n ...options,\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n // Handle 204 No Content\n if (res.status === 204) {\n return { success: true } as T;\n }\n\n const data = await res.json();\n\n if (!res.ok) {\n throw new DripError(\n data.message || data.error || 'Request failed',\n res.status,\n data.code,\n );\n }\n\n return data as T;\n } catch (error) {\n if (error instanceof DripError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new DripError('Request timed out', 408, 'TIMEOUT');\n }\n throw new DripError(\n error instanceof Error ? error.message : 'Unknown error',\n 0,\n 'UNKNOWN',\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // ==========================================================================\n // Health Check Methods\n // ==========================================================================\n\n /**\n * Pings the Drip API to check connectivity and measure latency.\n *\n * @returns Health status with latency information\n * @throws {DripError} If the request fails or times out\n *\n * @example\n * ```typescript\n * const health = await drip.ping();\n * if (health.ok) {\n * console.log(`API healthy, latency: ${health.latencyMs}ms`);\n * }\n * ```\n */\n async ping(): Promise<{ ok: boolean; status: string; latencyMs: number; timestamp: number }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Safely construct health endpoint URL\n let healthBaseUrl = this.baseUrl;\n if (healthBaseUrl.endsWith('/v1/')) {\n healthBaseUrl = healthBaseUrl.slice(0, -4);\n } else if (healthBaseUrl.endsWith('/v1')) {\n healthBaseUrl = healthBaseUrl.slice(0, -3);\n }\n healthBaseUrl = healthBaseUrl.replace(/\\/+$/, '');\n\n const start = Date.now();\n\n try {\n const response = await fetch(`${healthBaseUrl}/health`, {\n signal: controller.signal,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n const latencyMs = Date.now() - start;\n\n // Try to parse JSON, but handle non-JSON responses gracefully\n let status = 'unknown';\n let timestamp = Date.now();\n\n try {\n const data = await response.json() as { status?: string; timestamp?: number };\n if (typeof data.status === 'string') {\n status = data.status;\n }\n if (typeof data.timestamp === 'number') {\n timestamp = data.timestamp;\n }\n } catch {\n // Non-JSON response, derive status from HTTP code\n status = response.ok ? 'healthy' : `error:${response.status}`;\n }\n\n // For non-OK HTTP responses, set appropriate status\n if (!response.ok && status === 'unknown') {\n status = `error:${response.status}`;\n }\n\n return {\n ok: response.ok && status === 'healthy',\n status,\n latencyMs,\n timestamp,\n };\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new DripError('Request timed out', 408, 'TIMEOUT');\n }\n throw new DripError(\n error instanceof Error ? error.message : 'Unknown error',\n 0,\n 'UNKNOWN',\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // ==========================================================================\n // Customer Methods\n // ==========================================================================\n\n /**\n * Creates a new customer in your Drip account.\n *\n * @param params - Customer creation parameters\n * @returns The created customer\n * @throws {DripError} If creation fails (e.g., duplicate customer)\n *\n * @example\n * ```typescript\n * const customer = await drip.createCustomer({\n * onchainAddress: '0x1234567890abcdef...',\n * externalCustomerId: 'user_123',\n * metadata: { plan: 'pro' },\n * });\n * ```\n */\n async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n return this.request<Customer>('/customers', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Retrieves a customer by their Drip ID.\n *\n * @param customerId - The Drip customer ID\n * @returns The customer details\n * @throws {DripError} If customer not found (404)\n *\n * @example\n * ```typescript\n * const customer = await drip.getCustomer('cust_abc123');\n * console.log(customer.onchainAddress);\n * ```\n */\n async getCustomer(customerId: string): Promise<Customer> {\n return this.request<Customer>(`/customers/${customerId}`);\n }\n\n /**\n * Lists all customers for your business.\n *\n * @param options - Optional filtering and pagination\n * @returns List of customers\n *\n * @example\n * ```typescript\n * // List all customers\n * const { data: customers } = await drip.listCustomers();\n *\n * // List with filters\n * const { data: activeCustomers } = await drip.listCustomers({\n * status: 'ACTIVE',\n * limit: 50,\n * });\n * ```\n */\n async listCustomers(\n options?: ListCustomersOptions,\n ): Promise<ListCustomersResponse> {\n const params = new URLSearchParams();\n\n if (options?.limit) {\n params.set('limit', options.limit.toString());\n }\n if (options?.status) {\n params.set('status', options.status);\n }\n\n const query = params.toString();\n const path = query ? `/customers?${query}` : '/customers';\n\n return this.request<ListCustomersResponse>(path);\n }\n\n /**\n * Gets the current balance for a customer.\n *\n * @param customerId - The Drip customer ID\n * @returns Current balance in USDC and native token\n *\n * @example\n * ```typescript\n * const balance = await drip.getBalance('cust_abc123');\n * console.log(`Balance: ${balance.balanceUSDC} USDC`);\n * ```\n */\n async getBalance(customerId: string): Promise<BalanceResult> {\n return this.request<BalanceResult>(`/customers/${customerId}/balance`);\n }\n\n // ==========================================================================\n // Charge Methods\n // ==========================================================================\n\n /**\n * Records usage and charges a customer.\n *\n * This is the primary method for billing customers. It:\n * 1. Records the usage event\n * 2. Calculates the charge based on your pricing plan\n * 3. Executes the on-chain charge\n *\n * @param params - Charge parameters\n * @returns The charge result\n * @throws {DripError} If charge fails (insufficient balance, invalid customer, etc.)\n *\n * @example\n * ```typescript\n * const result = await drip.charge({\n * customerId: 'cust_abc123',\n * meter: 'api_calls',\n * quantity: 100,\n * idempotencyKey: 'req_unique_123',\n * });\n *\n * if (result.success) {\n * console.log(`Charged ${result.charge.amountUsdc} USDC`);\n * console.log(`TX: ${result.charge.txHash}`);\n * }\n * ```\n */\n async charge(params: ChargeParams): Promise<ChargeResult> {\n return this.request<ChargeResult>('/usage', {\n method: 'POST',\n body: JSON.stringify({\n customerId: params.customerId,\n usageType: params.meter,\n quantity: params.quantity,\n idempotencyKey: params.idempotencyKey,\n metadata: params.metadata,\n }),\n });\n }\n\n /**\n * Records usage for internal visibility WITHOUT billing.\n *\n * Use this for:\n * - Tracking internal team usage without charging\n * - Pilot programs where you want visibility before billing\n * - Pre-billing tracking before customer has on-chain wallet\n *\n * This does NOT:\n * - Create a Charge record\n * - Require customer balance\n * - Require blockchain/wallet setup\n *\n * For billing, use `charge()` instead.\n *\n * @param params - The usage tracking parameters\n * @returns The tracked usage event\n *\n * @example\n * ```typescript\n * const result = await drip.trackUsage({\n * customerId: 'cust_abc123',\n * meter: 'api_calls',\n * quantity: 100,\n * description: 'API calls during trial period',\n * });\n *\n * console.log(`Tracked: ${result.usageEventId}`);\n * ```\n */\n async trackUsage(params: TrackUsageParams): Promise<TrackUsageResult> {\n return this.request<TrackUsageResult>('/usage/internal', {\n method: 'POST',\n body: JSON.stringify({\n customerId: params.customerId,\n usageType: params.meter,\n quantity: params.quantity,\n idempotencyKey: params.idempotencyKey,\n units: params.units,\n description: params.description,\n metadata: params.metadata,\n }),\n });\n }\n\n /**\n * Retrieves a specific charge by ID.\n *\n * @param chargeId - The charge ID\n * @returns The charge details\n * @throws {DripError} If charge not found (404)\n *\n * @example\n * ```typescript\n * const charge = await drip.getCharge('chg_abc123');\n * console.log(`Status: ${charge.status}`);\n * ```\n */\n async getCharge(chargeId: string): Promise<Charge> {\n return this.request<Charge>(`/charges/${chargeId}`);\n }\n\n /**\n * Lists charges for your business.\n *\n * @param options - Optional filtering and pagination\n * @returns List of charges\n *\n * @example\n * ```typescript\n * // List all charges\n * const { data: charges } = await drip.listCharges();\n *\n * // List charges for a specific customer\n * const { data: customerCharges } = await drip.listCharges({\n * customerId: 'cust_abc123',\n * status: 'CONFIRMED',\n * });\n * ```\n */\n async listCharges(options?: ListChargesOptions): Promise<ListChargesResponse> {\n const params = new URLSearchParams();\n\n if (options?.customerId) {\n params.set('customerId', options.customerId);\n }\n if (options?.status) {\n params.set('status', options.status);\n }\n if (options?.limit) {\n params.set('limit', options.limit.toString());\n }\n if (options?.offset) {\n params.set('offset', options.offset.toString());\n }\n\n const query = params.toString();\n const path = query ? `/charges?${query}` : '/charges';\n\n return this.request<ListChargesResponse>(path);\n }\n\n /**\n * Gets the current status of a charge.\n *\n * Useful for polling charge status after async operations.\n *\n * @param chargeId - The charge ID\n * @returns Current charge status\n *\n * @example\n * ```typescript\n * const status = await drip.getChargeStatus('chg_abc123');\n * if (status.status === 'CONFIRMED') {\n * console.log('Charge confirmed!');\n * }\n * ```\n */\n async getChargeStatus(\n chargeId: string,\n ): Promise<{ status: ChargeStatus; txHash?: string }> {\n return this.request<{ status: ChargeStatus; txHash?: string }>(\n `/charges/${chargeId}/status`,\n );\n }\n\n // ==========================================================================\n // Checkout Methods (Fiat On-Ramp)\n // ==========================================================================\n\n /**\n * Creates a checkout session to add funds to a customer's account.\n *\n * This is the PRIMARY method for getting money into Drip. It returns a URL\n * to a hosted checkout page where customers can pay via:\n * - Bank transfer (ACH) - $0.50 flat fee, 1-2 business days\n * - Debit card - 1.5% fee, instant\n * - Direct USDC - no fee, instant\n *\n * After payment, the customer is redirected to your returnUrl with:\n * - session_id: The checkout session ID\n * - customer_id: The Drip customer ID\n * - status: \"success\" or \"failed\"\n *\n * @param params - Checkout parameters\n * @returns Checkout session with redirect URL\n *\n * @example\n * ```typescript\n * // Basic checkout\n * const { url } = await drip.checkout({\n * customerId: 'cust_abc123',\n * amount: 5000, // $50.00\n * returnUrl: 'https://myapp.com/dashboard',\n * });\n *\n * // Redirect user to checkout\n * res.redirect(url);\n * ```\n *\n * @example\n * ```typescript\n * // Checkout for new customer\n * const { url, id } = await drip.checkout({\n * externalCustomerId: 'user_123', // Your user ID\n * amount: 10000, // $100.00\n * returnUrl: 'https://myapp.com/welcome',\n * metadata: { plan: 'pro' },\n * });\n * ```\n */\n async checkout(params: CheckoutParams): Promise<CheckoutResult> {\n const response = await this.request<{\n id: string;\n url: string;\n expires_at: string;\n amount_usd: number;\n }>('/checkout', {\n method: 'POST',\n body: JSON.stringify({\n customer_id: params.customerId,\n external_customer_id: params.externalCustomerId,\n amount: params.amount,\n return_url: params.returnUrl,\n cancel_url: params.cancelUrl,\n metadata: params.metadata,\n }),\n });\n\n return {\n id: response.id,\n url: response.url,\n expiresAt: response.expires_at,\n amountUsd: response.amount_usd,\n };\n }\n\n // ==========================================================================\n // Webhook Methods\n // ==========================================================================\n\n /**\n * Creates a new webhook endpoint.\n *\n * The webhook secret is only returned once at creation time.\n * Store it securely for verifying webhook signatures.\n *\n * @param config - Webhook configuration\n * @returns The created webhook with its secret\n *\n * @example\n * ```typescript\n * const webhook = await drip.createWebhook({\n * url: 'https://api.yourapp.com/webhooks/drip',\n * events: ['charge.succeeded', 'charge.failed'],\n * description: 'Main webhook endpoint',\n * });\n *\n * // IMPORTANT: Save this secret securely!\n * console.log(`Webhook secret: ${webhook.secret}`);\n * ```\n */\n async createWebhook(\n config: CreateWebhookParams,\n ): Promise<CreateWebhookResponse> {\n return this.request<CreateWebhookResponse>('/webhooks', {\n method: 'POST',\n body: JSON.stringify(config),\n });\n }\n\n /**\n * Lists all webhook endpoints for your business.\n *\n * @returns List of webhooks with delivery statistics\n *\n * @example\n * ```typescript\n * const { data: webhooks } = await drip.listWebhooks();\n * webhooks.forEach(wh => {\n * console.log(`${wh.url}: ${wh.stats?.successfulDeliveries} successful`);\n * });\n * ```\n */\n async listWebhooks(): Promise<ListWebhooksResponse> {\n return this.request<ListWebhooksResponse>('/webhooks');\n }\n\n /**\n * Retrieves a specific webhook by ID.\n *\n * @param webhookId - The webhook ID\n * @returns The webhook details with statistics\n * @throws {DripError} If webhook not found (404)\n *\n * @example\n * ```typescript\n * const webhook = await drip.getWebhook('wh_abc123');\n * console.log(`Events: ${webhook.events.join(', ')}`);\n * ```\n */\n async getWebhook(webhookId: string): Promise<Webhook> {\n return this.request<Webhook>(`/webhooks/${webhookId}`);\n }\n\n /**\n * Deletes a webhook endpoint.\n *\n * @param webhookId - The webhook ID to delete\n * @returns Success confirmation\n * @throws {DripError} If webhook not found (404)\n *\n * @example\n * ```typescript\n * await drip.deleteWebhook('wh_abc123');\n * console.log('Webhook deleted');\n * ```\n */\n async deleteWebhook(webhookId: string): Promise<DeleteWebhookResponse> {\n return this.request<DeleteWebhookResponse>(`/webhooks/${webhookId}`, {\n method: 'DELETE',\n });\n }\n\n /**\n * Tests a webhook by sending a test event.\n *\n * @param webhookId - The webhook ID to test\n * @returns Test result\n *\n * @example\n * ```typescript\n * const result = await drip.testWebhook('wh_abc123');\n * console.log(`Test status: ${result.status}`);\n * ```\n */\n async testWebhook(\n webhookId: string,\n ): Promise<{ message: string; deliveryId: string | null; status: string }> {\n return this.request<{\n message: string;\n deliveryId: string | null;\n status: string;\n }>(`/webhooks/${webhookId}/test`, {\n method: 'POST',\n });\n }\n\n /**\n * Rotates the signing secret for a webhook.\n *\n * After rotation, update your application to use the new secret.\n *\n * @param webhookId - The webhook ID\n * @returns The new secret\n *\n * @example\n * ```typescript\n * const { secret } = await drip.rotateWebhookSecret('wh_abc123');\n * console.log(`New secret: ${secret}`);\n * // Update your application with the new secret!\n * ```\n */\n async rotateWebhookSecret(\n webhookId: string,\n ): Promise<{ secret: string; message: string }> {\n return this.request<{ secret: string; message: string }>(\n `/webhooks/${webhookId}/rotate-secret`,\n { method: 'POST' },\n );\n }\n\n // ==========================================================================\n // Run & Event Methods (Execution Ledger)\n // ==========================================================================\n\n /**\n * Creates a new workflow definition.\n *\n * @param params - Workflow creation parameters\n * @returns The created workflow\n *\n * @example\n * ```typescript\n * const workflow = await drip.createWorkflow({\n * name: 'Prescription Intake',\n * slug: 'prescription_intake',\n * productSurface: 'AGENT',\n * });\n * ```\n */\n async createWorkflow(params: CreateWorkflowParams): Promise<Workflow> {\n return this.request<Workflow>('/workflows', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Lists all workflows for your business.\n *\n * @returns List of workflows\n */\n async listWorkflows(): Promise<{ data: Workflow[]; count: number }> {\n return this.request<{ data: Workflow[]; count: number }>('/workflows');\n }\n\n /**\n * Starts a new agent run for tracking execution.\n *\n * @param params - Run parameters\n * @returns The started run\n *\n * @example\n * ```typescript\n * const run = await drip.startRun({\n * customerId: 'cust_abc123',\n * workflowId: 'wf_xyz789',\n * correlationId: 'req_unique_123',\n * });\n *\n * // Emit events during execution...\n *\n * await drip.endRun(run.id, { status: 'COMPLETED' });\n * ```\n */\n async startRun(params: StartRunParams): Promise<RunResult> {\n return this.request<RunResult>('/runs', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Ends a run with a final status.\n *\n * @param runId - The run ID to end\n * @param params - End parameters including status\n * @returns Updated run info\n *\n * @example\n * ```typescript\n * await drip.endRun(run.id, {\n * status: 'COMPLETED',\n * });\n *\n * // Or with error:\n * await drip.endRun(run.id, {\n * status: 'FAILED',\n * errorMessage: 'Customer validation failed',\n * errorCode: 'VALIDATION_ERROR',\n * });\n * ```\n */\n async endRun(\n runId: string,\n params: EndRunParams,\n ): Promise<{\n id: string;\n status: RunStatus;\n endedAt: string | null;\n durationMs: number | null;\n eventCount: number;\n totalCostUnits: string | null;\n }> {\n return this.request(`/runs/${runId}`, {\n method: 'PATCH',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Gets a run's full timeline with events and computed totals.\n *\n * This is the key endpoint for debugging \"what happened\" in an execution.\n *\n * @param runId - The run ID\n * @returns Full timeline with events and summary\n *\n * @example\n * ```typescript\n * const { run, timeline, totals, summary } = await drip.getRunTimeline('run_abc123');\n *\n * console.log(`Status: ${run.status}`);\n * console.log(`Summary: ${summary}`);\n * console.log(`Total cost: ${totals.totalCostUnits}`);\n *\n * for (const event of timeline) {\n * console.log(`${event.eventType}: ${event.quantity} ${event.units}`);\n * }\n * ```\n */\n async getRunTimeline(runId: string): Promise<RunTimeline> {\n return this.request<RunTimeline>(`/runs/${runId}`);\n }\n\n /**\n * Emits an event to a run.\n *\n * Events can be stored idempotently when an `idempotencyKey` is provided.\n * Use `Drip.generateIdempotencyKey()` for deterministic key generation.\n * If `idempotencyKey` is omitted, repeated calls may create duplicate events.\n *\n * @param params - Event parameters\n * @returns The created event\n *\n * @example\n * ```typescript\n * await drip.emitEvent({\n * runId: run.id,\n * eventType: 'agent.validate',\n * quantity: 1,\n * description: 'Validated prescription format',\n * costUnits: 0.001,\n * });\n * ```\n */\n async emitEvent(params: EmitEventParams): Promise<EventResult> {\n return this.request<EventResult>('/events', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Emits multiple events in a single request.\n *\n * @param events - Array of events to emit\n * @returns Summary of created events\n *\n * @example\n * ```typescript\n * const result = await drip.emitEventsBatch([\n * { runId: run.id, eventType: 'agent.step1', quantity: 1 },\n * { runId: run.id, eventType: 'agent.step2', quantity: 100, units: 'tokens' },\n * ]);\n *\n * console.log(`Created: ${result.created}, Duplicates: ${result.duplicates}`);\n * ```\n */\n async emitEventsBatch(\n events: Array<Omit<EmitEventParams, 'runId'> & {\n runId?: string;\n customerId?: string;\n workflowId?: string;\n }>,\n ): Promise<{\n success: boolean;\n created: number;\n duplicates: number;\n events: Array<{ id: string; eventType: string; isDuplicate: boolean }>;\n }> {\n return this.request('/run-events/batch', {\n method: 'POST',\n body: JSON.stringify({ events }),\n });\n }\n\n // ==========================================================================\n // Simplified API Methods\n // ==========================================================================\n\n /**\n * Lists all available meters (usage types) for your business.\n *\n * Use this to discover what meter names are valid for the `charge()` method.\n * Meters are defined by your pricing plans.\n *\n * @returns List of available meters with their prices\n *\n * @example\n * ```typescript\n * const { data: meters } = await drip.listMeters();\n *\n * console.log('Available meters:');\n * for (const meter of meters) {\n * console.log(` ${meter.meter}: $${meter.unitPriceUsd}/unit`);\n * }\n *\n * // Use in charge():\n * await drip.charge({\n * customerId: 'cust_123',\n * meter: meters[0].meter, // Use a valid meter name\n * quantity: 100,\n * });\n * ```\n */\n async listMeters(): Promise<ListMetersResponse> {\n const response = await this.request<{\n data: Array<{\n id: string;\n name: string;\n unitType: string;\n unitPriceUsd: string;\n isActive: boolean;\n }>;\n count: number;\n }>('/pricing-plans');\n\n return {\n data: response.data.map((plan) => ({\n id: plan.id,\n name: plan.name,\n meter: plan.unitType,\n unitPriceUsd: plan.unitPriceUsd,\n isActive: plan.isActive,\n })),\n count: response.count,\n };\n }\n\n /**\n * Records a complete agent run in a single call.\n *\n * This is the **simplified API** that combines:\n * - Workflow creation (if needed)\n * - Run creation\n * - Event emission\n * - Run completion\n *\n * Use this instead of the individual `startRun()`, `emitEvent()`, `endRun()` calls\n * when you have all the run data available at once.\n *\n * @param params - Run parameters including events\n * @returns The created run with event summary\n *\n * @example\n * ```typescript\n * // Record a complete agent run in one call\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'prescription_intake', // Auto-creates if doesn't exist\n * events: [\n * { eventType: 'agent.start', description: 'Started processing' },\n * { eventType: 'tool.ocr', quantity: 3, units: 'pages', costUnits: 0.15 },\n * { eventType: 'tool.validate', quantity: 1, costUnits: 0.05 },\n * { eventType: 'agent.complete', description: 'Finished successfully' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * console.log(`Run ${result.run.id}: ${result.summary}`);\n * console.log(`Events: ${result.events.created} created`);\n * ```\n *\n * @example\n * ```typescript\n * // Record a failed run with error details\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'prescription_intake',\n * events: [\n * { eventType: 'agent.start', description: 'Started processing' },\n * { eventType: 'tool.ocr', quantity: 1, units: 'pages' },\n * { eventType: 'error', description: 'OCR failed: image too blurry' },\n * ],\n * status: 'FAILED',\n * errorMessage: 'OCR processing failed',\n * errorCode: 'OCR_QUALITY_ERROR',\n * });\n * ```\n */\n async recordRun(params: RecordRunParams): Promise<RecordRunResult> {\n const startTime = Date.now();\n\n // Step 1: Ensure workflow exists (get or create)\n let workflowId = params.workflow;\n let workflowName = params.workflow;\n\n // If it looks like a slug (no underscore prefix), try to find/create it\n if (!params.workflow.startsWith('wf_')) {\n try {\n // Try to find existing workflow by slug\n const workflows = await this.listWorkflows();\n const existing = workflows.data.find(\n (w) => w.slug === params.workflow || w.id === params.workflow,\n );\n\n if (existing) {\n workflowId = existing.id;\n workflowName = existing.name;\n } else {\n // Create new workflow with the slug\n const created = await this.createWorkflow({\n name: params.workflow.replace(/[_-]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()),\n slug: params.workflow,\n productSurface: 'AGENT',\n });\n workflowId = created.id;\n workflowName = created.name;\n }\n } catch {\n // If lookup fails, assume it's an ID\n workflowId = params.workflow;\n }\n }\n\n // Step 2: Create the run\n const run = await this.startRun({\n customerId: params.customerId,\n workflowId,\n externalRunId: params.externalRunId,\n correlationId: params.correlationId,\n metadata: params.metadata,\n });\n\n // Step 3: Emit all events in batch\n let eventsCreated = 0;\n let eventsDuplicates = 0;\n\n if (params.events.length > 0) {\n const batchEvents = params.events.map((event, index) => ({\n runId: run.id,\n eventType: event.eventType,\n quantity: event.quantity,\n units: event.units,\n description: event.description,\n costUnits: event.costUnits,\n metadata: event.metadata,\n idempotencyKey: params.externalRunId\n ? `${params.externalRunId}:${event.eventType}:${index}`\n : undefined,\n }));\n\n const batchResult = await this.emitEventsBatch(batchEvents);\n eventsCreated = batchResult.created;\n eventsDuplicates = batchResult.duplicates;\n }\n\n // Step 4: End the run\n const endResult = await this.endRun(run.id, {\n status: params.status,\n errorMessage: params.errorMessage,\n errorCode: params.errorCode,\n });\n\n const durationMs = Date.now() - startTime;\n\n // Build summary\n const eventSummary = params.events.length > 0\n ? `${eventsCreated} events recorded`\n : 'no events';\n const statusEmoji = params.status === 'COMPLETED' ? '✓' : params.status === 'FAILED' ? '✗' : '○';\n const summary = `${statusEmoji} ${workflowName}: ${eventSummary} (${endResult.durationMs ?? durationMs}ms)`;\n\n return {\n run: {\n id: run.id,\n workflowId,\n workflowName,\n status: params.status,\n durationMs: endResult.durationMs,\n },\n events: {\n created: eventsCreated,\n duplicates: eventsDuplicates,\n },\n totalCostUnits: endResult.totalCostUnits,\n summary,\n };\n }\n\n /**\n * Generates a deterministic idempotency key.\n *\n * Use this to ensure \"one logical action = one event\" even with retries.\n * The key is generated from customerId + runId + stepName + sequence.\n *\n * @param params - Key generation parameters\n * @returns A deterministic idempotency key\n *\n * @example\n * ```typescript\n * const key = Drip.generateIdempotencyKey({\n * customerId: 'cust_123',\n * runId: 'run_456',\n * stepName: 'validate_prescription',\n * sequence: 1,\n * });\n *\n * await drip.emitEvent({\n * runId: 'run_456',\n * eventType: 'agent.validate',\n * idempotencyKey: key,\n * });\n * ```\n */\n static generateIdempotencyKey(params: {\n customerId: string;\n runId?: string;\n stepName: string;\n sequence?: number;\n }): string {\n const components = [\n params.customerId,\n params.runId ?? 'no_run',\n params.stepName,\n String(params.sequence ?? 0),\n ];\n\n // Simple hash function for deterministic key generation\n let hash = 0;\n const str = components.join('|');\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 32bit integer\n }\n\n return `drip_${Math.abs(hash).toString(36)}_${params.stepName.slice(0, 16)}`;\n }\n\n // ==========================================================================\n // Utility Methods\n // ==========================================================================\n\n /**\n * Verifies a webhook signature using HMAC-SHA256.\n *\n * Call this when receiving webhook events to ensure they're authentic.\n * This is an async method that uses the Web Crypto API for secure verification.\n *\n * @param payload - The raw request body (string)\n * @param signature - The x-drip-signature header value\n * @param secret - Your webhook secret\n * @returns Promise resolving to whether the signature is valid\n *\n * @example\n * ```typescript\n * app.post('/webhooks/drip', async (req, res) => {\n * const isValid = await Drip.verifyWebhookSignature(\n * req.rawBody,\n * req.headers['x-drip-signature'],\n * process.env.DRIP_WEBHOOK_SECRET!,\n * );\n *\n * if (!isValid) {\n * return res.status(401).send('Invalid signature');\n * }\n *\n * // Process the webhook...\n * });\n * ```\n */\n static async verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string,\n tolerance = 300, // 5 minutes default\n ): Promise<boolean> {\n if (!payload || !signature || !secret) {\n return false;\n }\n\n try {\n // Parse signature format: t=timestamp,v1=hexsignature\n const parts = signature.split(',');\n const timestampPart = parts.find((p) => p.startsWith('t='));\n const signaturePart = parts.find((p) => p.startsWith('v1='));\n\n if (!timestampPart || !signaturePart) {\n return false;\n }\n\n const timestamp = parseInt(timestampPart.slice(2), 10);\n const providedSignature = signaturePart.slice(3);\n\n if (isNaN(timestamp)) {\n return false;\n }\n\n // Check timestamp tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > tolerance) {\n return false;\n }\n\n // Compute expected signature using timestamp.payload format\n const signaturePayload = `${timestamp}.${payload}`;\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const payloadData = encoder.encode(signaturePayload);\n\n // Import the secret as an HMAC key\n const cryptoKey = await crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n\n // Sign the payload\n const signatureBuffer = await crypto.subtle.sign(\n 'HMAC',\n cryptoKey,\n payloadData,\n );\n\n // Convert to hex string\n const expectedSignature = Array.from(new Uint8Array(signatureBuffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n\n // Constant-time comparison to prevent timing attacks\n if (providedSignature.length !== expectedSignature.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < providedSignature.length; i++) {\n result |= providedSignature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n }\n\n return result === 0;\n } catch {\n return false;\n }\n }\n\n /**\n * Synchronously verifies a webhook signature using HMAC-SHA256.\n *\n * This method uses Node.js crypto module and is only available in Node.js environments.\n * For edge runtimes or browsers, use the async `verifyWebhookSignature` method instead.\n *\n * @param payload - The raw request body (string)\n * @param signature - The x-drip-signature header value\n * @param secret - Your webhook secret\n * @returns Whether the signature is valid\n *\n * @example\n * ```typescript\n * app.post('/webhooks/drip', (req, res) => {\n * const isValid = Drip.verifyWebhookSignatureSync(\n * req.rawBody,\n * req.headers['x-drip-signature'],\n * process.env.DRIP_WEBHOOK_SECRET!,\n * );\n *\n * if (!isValid) {\n * return res.status(401).send('Invalid signature');\n * }\n *\n * // Process the webhook...\n * });\n * ```\n */\n static verifyWebhookSignatureSync(\n payload: string,\n signature: string,\n secret: string,\n tolerance = 300, // 5 minutes default\n ): boolean {\n if (!payload || !signature || !secret) {\n return false;\n }\n\n try {\n // Parse signature format: t=timestamp,v1=hexsignature\n const parts = signature.split(',');\n const timestampPart = parts.find((p) => p.startsWith('t='));\n const signaturePart = parts.find((p) => p.startsWith('v1='));\n\n if (!timestampPart || !signaturePart) {\n return false;\n }\n\n const timestamp = parseInt(timestampPart.slice(2), 10);\n const providedSignature = signaturePart.slice(3);\n\n if (isNaN(timestamp)) {\n return false;\n }\n\n // Check timestamp tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > tolerance) {\n return false;\n }\n\n // Dynamic import to avoid bundling issues in edge runtimes\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const crypto = require('crypto') as typeof import('crypto');\n\n // Compute expected signature using timestamp.payload format\n const signaturePayload = `${timestamp}.${payload}`;\n const expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(signaturePayload)\n .digest('hex');\n\n // Use timingSafeEqual for constant-time comparison\n const sigBuffer = Buffer.from(providedSignature);\n const expectedBuffer = Buffer.from(expectedSignature);\n\n if (sigBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(sigBuffer, expectedBuffer);\n } catch {\n return false;\n }\n }\n\n /**\n * Generates a webhook signature for testing purposes.\n *\n * This method creates a signature in the same format the Drip backend uses,\n * allowing you to test your webhook handling code locally.\n *\n * @param payload - The webhook payload (JSON string)\n * @param secret - The webhook secret\n * @param timestamp - Optional timestamp (defaults to current time)\n * @returns Signature in format: t=timestamp,v1=hexsignature\n *\n * @example\n * ```typescript\n * const payload = JSON.stringify({ type: 'charge.succeeded', data: {...} });\n * const signature = Drip.generateWebhookSignature(payload, 'whsec_test123');\n *\n * // Use in tests:\n * const isValid = Drip.verifyWebhookSignatureSync(payload, signature, 'whsec_test123');\n * console.log(isValid); // true\n * ```\n */\n static generateWebhookSignature(\n payload: string,\n secret: string,\n timestamp?: number,\n ): string {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const crypto = require('crypto') as typeof import('crypto');\n\n const ts = timestamp ?? Math.floor(Date.now() / 1000);\n const signaturePayload = `${ts}.${payload}`;\n const signature = crypto\n .createHmac('sha256', secret)\n .update(signaturePayload)\n .digest('hex');\n\n return `t=${ts},v1=${signature}`;\n }\n\n // ==========================================================================\n // StreamMeter Factory\n // ==========================================================================\n\n /**\n * Creates a StreamMeter for accumulating usage and charging once.\n *\n * Perfect for LLM token streaming where you want to:\n * - Accumulate tokens locally (no API call per token)\n * - Charge once at the end of the stream\n * - Handle partial failures (charge for what was delivered)\n *\n * @param options - StreamMeter configuration\n * @returns A new StreamMeter instance\n *\n * @example\n * ```typescript\n * const meter = drip.createStreamMeter({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * });\n *\n * // Accumulate tokens as they stream\n * for await (const chunk of llmStream) {\n * meter.addSync(chunk.tokens);\n * yield chunk;\n * }\n *\n * // Single charge at end\n * const result = await meter.flush();\n * console.log(`Charged ${result.charge?.amountUsdc} for ${result.quantity} tokens`);\n * ```\n *\n * @example\n * ```typescript\n * // With auto-flush threshold\n * const meter = drip.createStreamMeter({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * flushThreshold: 10000, // Charge every 10k tokens\n * });\n *\n * for await (const chunk of longStream) {\n * await meter.add(chunk.tokens); // May auto-flush\n * }\n *\n * await meter.flush(); // Final flush for remaining tokens\n * ```\n */\n createStreamMeter(options: StreamMeterOptions): StreamMeter {\n return new StreamMeter(this.charge.bind(this), options);\n }\n}\n\n// Re-export StreamMeter types\nexport { StreamMeter } from './stream-meter.js';\nexport type { StreamMeterOptions, StreamMeterFlushResult } from './stream-meter.js';\n\n// Default export for convenience\nexport default Drip;\n"]}
1
+ {"version":3,"sources":["../src/stream-meter.ts","../src/index.ts"],"names":["StreamMeter","chargeFn","options","quantity","idempotencyKey","chargeResult","result","DEFAULT_RETRY_CONFIG","defaultIsRetryable","error","DripError","retryWithBackoff","fn","maxAttempts","baseDelayMs","maxDelayMs","isRetryable","lastError","attempt","delay","resolve","_DripError","message","statusCode","code","Drip","config","path","controller","timeoutId","res","data","healthBaseUrl","start","response","latencyMs","status","timestamp","params","customerId","query","charge","chargeId","webhookId","runId","events","plan","startTime","workflowId","workflowName","existing","w","created","c","run","eventsCreated","eventsDuplicates","batchEvents","event","index","batchResult","endResult","durationMs","eventSummary","summary","components","hash","str","i","char","payload","signature","secret","tolerance","parts","timestampPart","p","signaturePart","providedSignature","now","signaturePayload","encoder","keyData","payloadData","subtle","cryptoKey","signatureBuffer","expectedSignature","b","crypto","sigBuffer","expectedBuffer","ts","index_default"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * StreamMeter - Accumulate usage locally and charge once at the end.\n *\n * Perfect for LLM token streaming and other high-frequency metering scenarios\n * where you want to avoid making an API call for every small increment.\n *\n * @example\n * ```typescript\n * const meter = drip.createStreamMeter({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * });\n *\n * for await (const chunk of llmStream) {\n * meter.add(chunk.tokens);\n * }\n *\n * const result = await meter.flush();\n * console.log(`Charged ${result.charge.amountUsdc} for ${result.quantity} tokens`);\n * ```\n */\n\nimport type { ChargeResult, ChargeParams } from './index.js';\n\n/**\n * Options for creating a StreamMeter.\n */\nexport interface StreamMeterOptions {\n /**\n * The Drip customer ID to charge.\n */\n customerId: string;\n\n /**\n * The usage meter/type to record against.\n * Must match a meter configured in your pricing plan.\n */\n meter: string;\n\n /**\n * Unique key to prevent duplicate charges.\n * If not provided, one will be generated.\n */\n idempotencyKey?: string;\n\n /**\n * Additional metadata to attach to the charge.\n */\n metadata?: Record<string, unknown>;\n\n /**\n * Auto-flush when accumulated quantity reaches this threshold.\n * Useful for long-running streams where you want periodic charges.\n */\n flushThreshold?: number;\n\n /**\n * Callback invoked on each add() call.\n * Useful for logging or progress tracking.\n */\n onAdd?: (quantity: number, total: number) => void;\n\n /**\n * Callback invoked after each successful flush.\n */\n onFlush?: (result: StreamMeterFlushResult) => void;\n}\n\n/**\n * Result of flushing a StreamMeter.\n */\nexport interface StreamMeterFlushResult {\n /** Whether the flush was successful */\n success: boolean;\n\n /** The quantity that was charged */\n quantity: number;\n\n /** The charge result from the API (if quantity > 0) */\n charge: ChargeResult['charge'] | null;\n\n /** Whether this was an idempotent replay */\n isReplay: boolean;\n}\n\n/**\n * Internal charge function type (injected by Drip class).\n */\nexport type ChargeFn = (params: ChargeParams) => Promise<ChargeResult>;\n\n/**\n * StreamMeter accumulates usage locally and charges once when flushed.\n *\n * This is ideal for:\n * - LLM token streaming (charge once at end of stream)\n * - High-frequency events (batch small increments)\n * - Partial failure handling (charge for what was delivered)\n */\nexport class StreamMeter {\n private _total: number = 0;\n private _flushed: boolean = false;\n private _flushCount: number = 0;\n private readonly _chargeFn: ChargeFn;\n private readonly _options: StreamMeterOptions;\n\n /**\n * Creates a new StreamMeter.\n *\n * @param chargeFn - The charge function from Drip client\n * @param options - StreamMeter configuration\n */\n constructor(chargeFn: ChargeFn, options: StreamMeterOptions) {\n this._chargeFn = chargeFn;\n this._options = options;\n }\n\n /**\n * Current accumulated quantity (not yet charged).\n */\n get total(): number {\n return this._total;\n }\n\n /**\n * Whether this meter has been flushed at least once.\n */\n get isFlushed(): boolean {\n return this._flushed;\n }\n\n /**\n * Number of times this meter has been flushed.\n */\n get flushCount(): number {\n return this._flushCount;\n }\n\n /**\n * Add quantity to the accumulated total.\n *\n * If a flushThreshold is set and the total exceeds it,\n * this will automatically trigger a flush.\n *\n * @param quantity - Amount to add (must be positive)\n * @returns Promise that resolves when add completes (may trigger auto-flush)\n */\n async add(quantity: number): Promise<StreamMeterFlushResult | null> {\n if (quantity <= 0) {\n return null;\n }\n\n this._total += quantity;\n\n // Invoke callback if provided\n this._options.onAdd?.(quantity, this._total);\n\n // Check for auto-flush threshold\n if (\n this._options.flushThreshold !== undefined &&\n this._total >= this._options.flushThreshold\n ) {\n return this.flush();\n }\n\n return null;\n }\n\n /**\n * Synchronously add quantity without auto-flush.\n * Use this for maximum performance when you don't need threshold-based flushing.\n *\n * @param quantity - Amount to add (must be positive)\n */\n addSync(quantity: number): void {\n if (quantity <= 0) {\n return;\n }\n\n this._total += quantity;\n\n // Invoke callback if provided\n this._options.onAdd?.(quantity, this._total);\n }\n\n /**\n * Flush accumulated usage and charge the customer.\n *\n * If total is 0, returns a success result with no charge.\n * After flush, the meter resets to 0 and can be reused.\n *\n * @returns The flush result including charge details\n */\n async flush(): Promise<StreamMeterFlushResult> {\n const quantity = this._total;\n\n // Reset total before charging to avoid double-counting on retry\n this._total = 0;\n\n // Nothing to charge\n if (quantity === 0) {\n const result: StreamMeterFlushResult = {\n success: true,\n quantity: 0,\n charge: null,\n isReplay: false,\n };\n return result;\n }\n\n // Generate idempotency key for this flush\n const idempotencyKey = this._options.idempotencyKey\n ? `${this._options.idempotencyKey}_flush_${this._flushCount}`\n : undefined;\n\n // Charge the customer\n const chargeResult = await this._chargeFn({\n customerId: this._options.customerId,\n meter: this._options.meter,\n quantity,\n idempotencyKey,\n metadata: this._options.metadata,\n });\n\n this._flushed = true;\n this._flushCount++;\n\n const result: StreamMeterFlushResult = {\n success: chargeResult.success,\n quantity,\n charge: chargeResult.charge,\n isReplay: chargeResult.isReplay,\n };\n\n // Invoke callback if provided\n this._options.onFlush?.(result);\n\n return result;\n }\n\n /**\n * Reset the meter without charging.\n * Use this to discard accumulated usage (e.g., on error before delivery).\n */\n reset(): void {\n this._total = 0;\n }\n}\n","/**\n * Drip SDK - Usage-based billing for Node.js\n *\n * The official SDK for integrating with the Drip billing platform.\n * Provides methods for managing customers, recording usage, handling charges,\n * and configuring webhooks.\n *\n * @packageDocumentation\n */\n\nimport { StreamMeter, type StreamMeterOptions } from './stream-meter.js';\n\n// ============================================================================\n// Retry Utility\n// ============================================================================\n\n/**\n * Default retry configuration.\n */\nconst DEFAULT_RETRY_CONFIG = {\n maxAttempts: 3,\n baseDelayMs: 100,\n maxDelayMs: 5000,\n} as const;\n\n/**\n * Retry options for API calls.\n */\nexport interface RetryOptions {\n /**\n * Maximum number of retry attempts.\n * @default 3\n */\n maxAttempts?: number;\n\n /**\n * Base delay between retries in milliseconds (exponential backoff).\n * @default 100\n */\n baseDelayMs?: number;\n\n /**\n * Maximum delay between retries in milliseconds.\n * @default 5000\n */\n maxDelayMs?: number;\n\n /**\n * Custom function to determine if an error is retryable.\n * By default, retries on network errors and 5xx status codes.\n */\n isRetryable?: (error: unknown) => boolean;\n}\n\n/**\n * Default function to determine if an error is retryable.\n */\nfunction defaultIsRetryable(error: unknown): boolean {\n // Retry on network errors\n if (error instanceof Error) {\n if (error.message.includes('fetch') || error.message.includes('network')) {\n return true;\n }\n }\n\n // Retry on 5xx errors and timeouts\n if (error instanceof DripError) {\n return error.statusCode >= 500 || error.statusCode === 408 || error.statusCode === 429;\n }\n\n return false;\n}\n\n/**\n * Executes a function with exponential backoff retry.\n * @internal\n */\nasync function retryWithBackoff<T>(\n fn: () => Promise<T>,\n options: RetryOptions = {},\n): Promise<T> {\n const maxAttempts = options.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts;\n const baseDelayMs = options.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs;\n const maxDelayMs = options.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs;\n const isRetryable = options.isRetryable ?? defaultIsRetryable;\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n // Don't retry if it's the last attempt or error isn't retryable\n if (attempt === maxAttempts || !isRetryable(error)) {\n throw error;\n }\n\n // Exponential backoff with jitter\n const delay = Math.min(\n baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 100,\n maxDelayMs,\n );\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n // Should never reach here, but TypeScript needs it\n throw lastError;\n}\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration options for the Drip SDK client.\n */\nexport interface DripConfig {\n /**\n * Your Drip API key. Obtain this from the Drip dashboard.\n * @example \"drip_live_abc123...\"\n */\n apiKey: string;\n\n /**\n * Base URL for the Drip API. Defaults to production API.\n * Override for staging/development environments.\n * @default \"https://api.drip.dev/v1\"\n */\n baseUrl?: string;\n\n /**\n * Request timeout in milliseconds.\n * @default 30000\n */\n timeout?: number;\n}\n\n// ============================================================================\n// Customer Types\n// ============================================================================\n\n/**\n * Parameters for creating a new customer.\n */\nexport interface CreateCustomerParams {\n /**\n * Your internal customer/user ID for reconciliation.\n * @example \"user_12345\"\n */\n externalCustomerId?: string;\n\n /**\n * The customer's Drip Smart Account address (derived from their EOA).\n * @example \"0x1234567890abcdef...\"\n */\n onchainAddress: string;\n\n /**\n * Additional metadata to store with the customer.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * A Drip customer record.\n */\nexport interface Customer {\n /** Unique customer ID in Drip */\n id: string;\n\n /** Your business ID */\n businessId: string;\n\n /** Your external customer ID (if provided) */\n externalCustomerId: string | null;\n\n /** Customer's on-chain address */\n onchainAddress: string;\n\n /** Custom metadata */\n metadata: Record<string, unknown> | null;\n\n /** ISO timestamp of creation */\n createdAt: string;\n\n /** ISO timestamp of last update */\n updatedAt: string;\n}\n\n/**\n * Options for listing customers.\n */\nexport interface ListCustomersOptions {\n /**\n * Maximum number of customers to return (1-100).\n * @default 100\n */\n limit?: number;\n\n /**\n * Filter by customer status.\n */\n status?: 'ACTIVE' | 'LOW_BALANCE' | 'PAUSED';\n}\n\n/**\n * Response from listing customers.\n */\nexport interface ListCustomersResponse {\n /** Array of customers */\n data: Customer[];\n\n /** Total count returned */\n count: number;\n}\n\n/**\n * Customer balance information.\n */\nexport interface BalanceResult {\n /** Customer ID */\n customerId: string;\n\n /** On-chain address */\n onchainAddress: string;\n\n /** Balance in USDC (6 decimals) - matches backend field name */\n balanceUsdc: string;\n\n /** Pending charges in USDC */\n pendingChargesUsdc: string;\n\n /** Available USDC (balance minus pending) */\n availableUsdc: string;\n\n /** ISO timestamp of last balance sync */\n lastSyncedAt: string | null;\n}\n\n// ============================================================================\n// Usage & Charge Types\n// ============================================================================\n\n/**\n * Parameters for recording usage and charging a customer.\n */\nexport interface ChargeParams {\n /**\n * The Drip customer ID to charge.\n */\n customerId: string;\n\n /**\n * The usage meter/type to record against.\n * Must match a meter configured in your pricing plan.\n * @example \"api_calls\", \"compute_seconds\", \"storage_gb\"\n */\n meter: string;\n\n /**\n * The quantity of usage to record.\n * Will be multiplied by the meter's unit price.\n */\n quantity: number;\n\n /**\n * Unique key to prevent duplicate charges.\n * If provided, retrying with the same key returns the original charge.\n * @example \"req_abc123\"\n */\n idempotencyKey?: string;\n\n /**\n * Additional metadata to attach to this usage event.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of a successful charge operation.\n */\nexport interface ChargeResult {\n /** Whether the charge was successful */\n success: boolean;\n\n /** The usage event ID */\n usageEventId: string;\n\n /** True if this was an idempotent replay (returned cached result from previous request) */\n isReplay: boolean;\n\n /** Details about the charge */\n charge: {\n /** Unique charge ID */\n id: string;\n\n /** Amount charged in USDC (6 decimals) */\n amountUsdc: string;\n\n /** Amount in native token */\n amountToken: string;\n\n /** Blockchain transaction hash */\n txHash: string;\n\n /** Current status of the charge */\n status: ChargeStatus;\n };\n}\n\n/**\n * Possible charge statuses.\n */\nexport type ChargeStatus =\n | 'PENDING'\n | 'SUBMITTED'\n | 'CONFIRMED'\n | 'FAILED'\n | 'REFUNDED';\n\n/**\n * Parameters for tracking usage without billing.\n * Use this for internal visibility, pilots, or pre-billing tracking.\n */\nexport interface TrackUsageParams {\n /**\n * The Drip customer ID to track usage for.\n * @example \"cust_abc123\"\n */\n customerId: string;\n\n /**\n * The meter/usage type (e.g., 'api_calls', 'tokens').\n */\n meter: string;\n\n /**\n * The quantity of usage to record.\n */\n quantity: number;\n\n /**\n * Unique key to prevent duplicate records.\n */\n idempotencyKey?: string;\n\n /**\n * Human-readable unit label (e.g., 'tokens', 'requests').\n */\n units?: string;\n\n /**\n * Human-readable description of this usage event.\n */\n description?: string;\n\n /**\n * Additional metadata to attach to this usage event.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of tracking usage (no billing).\n */\nexport interface TrackUsageResult {\n /** Whether the usage was recorded */\n success: boolean;\n\n /** The usage event ID */\n usageEventId: string;\n\n /** Customer ID */\n customerId: string;\n\n /** Usage type that was recorded */\n usageType: string;\n\n /** Quantity recorded */\n quantity: number;\n\n /** Whether this customer is internal-only */\n isInternal: boolean;\n\n /** Confirmation message */\n message: string;\n}\n\n/**\n * A detailed charge record.\n */\nexport interface Charge {\n /** Unique charge ID */\n id: string;\n\n /** Associated usage event ID */\n usageId: string;\n\n /** Customer ID */\n customerId: string;\n\n /** Customer details */\n customer: {\n id: string;\n onchainAddress: string;\n externalCustomerId: string | null;\n };\n\n /** Usage event details */\n usageEvent: {\n id: string;\n type: string;\n quantity: string;\n metadata: Record<string, unknown> | null;\n };\n\n /** Amount in USDC */\n amountUsdc: string;\n\n /** Amount in native token */\n amountToken: string;\n\n /** Transaction hash (if submitted) */\n txHash: string | null;\n\n /** Block number (if confirmed) */\n blockNumber: string | null;\n\n /** Current status */\n status: ChargeStatus;\n\n /** Failure reason (if failed) */\n failureReason: string | null;\n\n /** ISO timestamp of creation */\n createdAt: string;\n\n /** ISO timestamp of confirmation */\n confirmedAt: string | null;\n}\n\n/**\n * Options for listing charges.\n */\nexport interface ListChargesOptions {\n /**\n * Filter by customer ID.\n */\n customerId?: string;\n\n /**\n * Filter by charge status.\n */\n status?: ChargeStatus;\n\n /**\n * Maximum number of charges to return (1-100).\n * @default 100\n */\n limit?: number;\n\n /**\n * Number of charges to skip (for pagination).\n * @default 0\n */\n offset?: number;\n}\n\n/**\n * Response from listing charges.\n */\nexport interface ListChargesResponse {\n /** Array of charges */\n data: Charge[];\n\n /** Total count returned */\n count: number;\n}\n\n// ============================================================================\n// Webhook Types\n// ============================================================================\n\n/**\n * Available webhook event types.\n */\nexport type WebhookEventType =\n | 'customer.balance.low'\n | 'usage.recorded'\n | 'charge.succeeded'\n | 'charge.failed'\n | 'customer.deposit.confirmed'\n | 'customer.withdraw.confirmed'\n | 'customer.usage_cap.reached'\n | 'webhook.endpoint.unhealthy'\n | 'customer.created'\n | 'api_key.created'\n | 'pricing_plan.updated'\n | 'transaction.created'\n | 'transaction.pending'\n | 'transaction.confirmed'\n | 'transaction.failed';\n\n/**\n * Parameters for creating a webhook.\n */\nexport interface CreateWebhookParams {\n /**\n * The URL to send webhook events to.\n * Must be HTTPS in production.\n * @example \"https://api.yourapp.com/webhooks/drip\"\n */\n url: string;\n\n /**\n * Array of event types to subscribe to.\n * @example [\"charge.succeeded\", \"charge.failed\"]\n */\n events: WebhookEventType[];\n\n /**\n * Optional description for the webhook.\n */\n description?: string;\n}\n\n/**\n * A webhook configuration.\n */\nexport interface Webhook {\n /** Unique webhook ID */\n id: string;\n\n /** Webhook endpoint URL */\n url: string;\n\n /** Subscribed event types */\n events: string[];\n\n /** Description */\n description: string | null;\n\n /** Whether the webhook is active */\n isActive: boolean;\n\n /** ISO timestamp of creation */\n createdAt: string;\n\n /** ISO timestamp of last update */\n updatedAt: string;\n\n /** Delivery statistics */\n stats?: {\n totalDeliveries: number;\n successfulDeliveries: number;\n failedDeliveries: number;\n lastDeliveryAt: string | null;\n };\n}\n\n/**\n * Response from creating a webhook.\n */\nexport interface CreateWebhookResponse extends Webhook {\n /**\n * The webhook signing secret.\n * Only returned once at creation time - save it securely!\n */\n secret: string;\n\n /** Reminder to save the secret */\n message: string;\n}\n\n/**\n * Response from listing webhooks.\n */\nexport interface ListWebhooksResponse {\n /** Array of webhooks */\n data: Webhook[];\n\n /** Total count */\n count: number;\n}\n\n/**\n * Response from deleting a webhook.\n */\nexport interface DeleteWebhookResponse {\n /** Whether the deletion was successful */\n success: boolean;\n}\n\n// ============================================================================\n// Checkout Types\n// ============================================================================\n\n/**\n * Parameters for creating a checkout session.\n * This is the primary way to get money into a customer's account.\n */\nexport interface CheckoutParams {\n /**\n * Existing customer ID (optional).\n * If not provided, a new customer is created after payment.\n */\n customerId?: string;\n\n /**\n * Your internal customer/user ID for new customers.\n * Used to link the Drip customer to your system.\n */\n externalCustomerId?: string;\n\n /**\n * Amount in cents (e.g., 5000 = $50.00).\n * Minimum: 500 ($5.00)\n * Maximum: 1000000 ($10,000.00)\n */\n amount: number;\n\n /**\n * URL to redirect after successful payment.\n * Query params will be added: session_id, customer_id, status\n */\n returnUrl: string;\n\n /**\n * URL to redirect if user cancels (optional).\n */\n cancelUrl?: string;\n\n /**\n * Custom metadata to attach to this checkout.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of creating a checkout session.\n */\nexport interface CheckoutResult {\n /** Checkout session ID */\n id: string;\n\n /** URL to redirect user to for payment */\n url: string;\n\n /** ISO timestamp when session expires (30 minutes) */\n expiresAt: string;\n\n /** Amount in USD */\n amountUsd: number;\n}\n\n// ============================================================================\n// Run & Event Types (Execution Ledger)\n// ============================================================================\n\n/**\n * Parameters for creating a new workflow.\n */\nexport interface CreateWorkflowParams {\n /** Human-readable workflow name */\n name: string;\n\n /** URL-safe identifier (lowercase alphanumeric with underscores/hyphens) */\n slug: string;\n\n /** Type of workflow */\n productSurface?: 'RPC' | 'WEBHOOK' | 'AGENT' | 'PIPELINE' | 'CUSTOM';\n\n /** Optional description */\n description?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * A workflow definition.\n */\nexport interface Workflow {\n id: string;\n name: string;\n slug: string;\n productSurface: string;\n description: string | null;\n isActive: boolean;\n createdAt: string;\n}\n\n/**\n * Parameters for starting a new agent run.\n */\nexport interface StartRunParams {\n /** Customer ID this run belongs to */\n customerId: string;\n\n /** Workflow ID this run executes */\n workflowId: string;\n\n /** Your external run ID for correlation */\n externalRunId?: string;\n\n /** Correlation ID for distributed tracing */\n correlationId?: string;\n\n /** Parent run ID for nested runs */\n parentRunId?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of starting a run.\n */\nexport interface RunResult {\n id: string;\n customerId: string;\n workflowId: string;\n workflowName: string;\n status: RunStatus;\n correlationId: string | null;\n createdAt: string;\n}\n\n/**\n * Parameters for ending/updating a run.\n */\nexport interface EndRunParams {\n /** New status for the run */\n status: 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';\n\n /** Error message if failed */\n errorMessage?: string;\n\n /** Error code for categorization */\n errorCode?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Possible run statuses.\n */\nexport type RunStatus =\n | 'PENDING'\n | 'RUNNING'\n | 'COMPLETED'\n | 'FAILED'\n | 'CANCELLED'\n | 'TIMEOUT';\n\n/**\n * Parameters for emitting an event to a run.\n */\nexport interface EmitEventParams {\n /** Run ID to attach this event to */\n runId: string;\n\n /** Event type (e.g., \"agent.step\", \"rpc.request\") */\n eventType: string;\n\n /** Quantity of units consumed */\n quantity?: number;\n\n /** Human-readable unit label */\n units?: string;\n\n /** Human-readable description */\n description?: string;\n\n /** Cost in abstract units */\n costUnits?: number;\n\n /** Currency for cost */\n costCurrency?: string;\n\n /** Correlation ID for tracing */\n correlationId?: string;\n\n /** Parent event ID for trace tree */\n parentEventId?: string;\n\n /** OpenTelemetry-style span ID */\n spanId?: string;\n\n /** Idempotency key (auto-generated if not provided) */\n idempotencyKey?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of emitting an event.\n */\nexport interface EventResult {\n id: string;\n runId: string;\n eventType: string;\n quantity: number;\n costUnits: number | null;\n isDuplicate: boolean;\n timestamp: string;\n}\n\n// ============================================================================\n// Meter Types\n// ============================================================================\n\n/**\n * A meter (usage type) from a pricing plan.\n */\nexport interface Meter {\n /** Pricing plan ID */\n id: string;\n\n /** Human-readable name */\n name: string;\n\n /** The meter/usage type identifier (use this in charge() calls) */\n meter: string;\n\n /** Price per unit in USD */\n unitPriceUsd: string;\n\n /** Whether this meter is active */\n isActive: boolean;\n}\n\n/**\n * Response from listing meters.\n */\nexport interface ListMetersResponse {\n /** Array of available meters */\n data: Meter[];\n\n /** Total count */\n count: number;\n}\n\n// ============================================================================\n// Record Run Types (Simplified API)\n// ============================================================================\n\n/**\n * A single event to record in a run.\n */\nexport interface RecordRunEvent {\n /** Event type (e.g., \"agent.step\", \"tool.call\") */\n eventType: string;\n\n /** Quantity of units consumed */\n quantity?: number;\n\n /** Human-readable unit label */\n units?: string;\n\n /** Human-readable description */\n description?: string;\n\n /** Cost in abstract units */\n costUnits?: number;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for recording a complete run in one call.\n * This is the simplified API that combines workflow, run, and events.\n */\nexport interface RecordRunParams {\n /** Customer ID this run belongs to */\n customerId: string;\n\n /**\n * Workflow identifier. Can be:\n * - An existing workflow ID (e.g., \"wf_abc123\")\n * - A slug that will be auto-created if it doesn't exist (e.g., \"my_agent\")\n */\n workflow: string;\n\n /** Events that occurred during the run */\n events: RecordRunEvent[];\n\n /** Final status of the run */\n status: 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';\n\n /** Error message if status is FAILED */\n errorMessage?: string;\n\n /** Error code if status is FAILED */\n errorCode?: string;\n\n /** Your external run ID for correlation */\n externalRunId?: string;\n\n /** Correlation ID for distributed tracing */\n correlationId?: string;\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Result of recording a run.\n */\nexport interface RecordRunResult {\n /** The created run */\n run: {\n id: string;\n workflowId: string;\n workflowName: string;\n status: RunStatus;\n durationMs: number | null;\n };\n\n /** Summary of events created */\n events: {\n created: number;\n duplicates: number;\n };\n\n /** Total cost computed */\n totalCostUnits: string | null;\n\n /** Human-readable summary */\n summary: string;\n}\n\n/**\n * Full run timeline response.\n */\nexport interface RunTimeline {\n run: {\n id: string;\n customerId: string;\n customerName: string | null;\n workflowId: string;\n workflowName: string;\n status: RunStatus;\n startedAt: string | null;\n endedAt: string | null;\n durationMs: number | null;\n errorMessage: string | null;\n errorCode: string | null;\n correlationId: string | null;\n metadata: Record<string, unknown> | null;\n };\n timeline: Array<{\n id: string;\n eventType: string;\n quantity: number;\n units: string | null;\n description: string | null;\n costUnits: number | null;\n timestamp: string;\n correlationId: string | null;\n parentEventId: string | null;\n charge: {\n id: string;\n amountUsdc: string;\n status: string;\n } | null;\n }>;\n totals: {\n eventCount: number;\n totalQuantity: string;\n totalCostUnits: string;\n totalChargedUsdc: string;\n };\n summary: string;\n}\n\n// ============================================================================\n// Wrap API Call Types\n// ============================================================================\n\n/**\n * Parameters for wrapping an external API call with usage tracking.\n * This ensures usage is recorded even if there's a crash/failure after the API call.\n */\nexport interface WrapApiCallParams<T> {\n /**\n * The Drip customer ID to charge.\n */\n customerId: string;\n\n /**\n * The usage meter/type to record against.\n * Must match a meter configured in your pricing plan.\n */\n meter: string;\n\n /**\n * The async function that makes the external API call.\n * This is the call you want to track (e.g., OpenAI, Anthropic, etc.)\n */\n call: () => Promise<T>;\n\n /**\n * Function to extract the usage quantity from the API call result.\n * @example (result) => result.usage.total_tokens\n */\n extractUsage: (result: T) => number;\n\n /**\n * Custom idempotency key prefix.\n * If not provided, a unique key is generated.\n * The key ensures retries don't double-charge.\n */\n idempotencyKey?: string;\n\n /**\n * Additional metadata to attach to this usage event.\n */\n metadata?: Record<string, unknown>;\n\n /**\n * Retry configuration for the Drip charge call.\n * The external API call is NOT retried (only called once).\n */\n retryOptions?: RetryOptions;\n}\n\n/**\n * Result of a wrapped API call.\n */\nexport interface WrapApiCallResult<T> {\n /**\n * The result from the external API call.\n */\n result: T;\n\n /**\n * The charge result from Drip.\n */\n charge: ChargeResult;\n\n /**\n * The idempotency key used (useful for debugging).\n */\n idempotencyKey: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Error thrown by Drip SDK operations.\n */\nexport class DripError extends Error {\n /**\n * Creates a new DripError.\n * @param message - Human-readable error message\n * @param statusCode - HTTP status code from the API\n * @param code - Machine-readable error code\n */\n constructor(\n message: string,\n public statusCode: number,\n public code?: string,\n ) {\n super(message);\n this.name = 'DripError';\n Object.setPrototypeOf(this, DripError.prototype);\n }\n}\n\n// ============================================================================\n// Main SDK Class\n// ============================================================================\n\n/**\n * The main Drip SDK client.\n *\n * @example\n * ```typescript\n * import { Drip } from '@drip-sdk/node';\n *\n * const drip = new Drip({\n * apiKey: process.env.DRIP_API_KEY!,\n * });\n *\n * // Create a customer\n * const customer = await drip.createCustomer({\n * onchainAddress: '0x...',\n * externalCustomerId: 'user_123',\n * });\n *\n * // Record usage and charge\n * const result = await drip.charge({\n * customerId: customer.id,\n * meter: 'api_calls',\n * quantity: 100,\n * });\n *\n * console.log(`Charged ${result.charge.amountUsdc} USDC`);\n * ```\n */\nexport class Drip {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n /**\n * Creates a new Drip SDK client.\n *\n * @param config - Configuration options\n * @throws {Error} If apiKey is not provided\n *\n * @example\n * ```typescript\n * const drip = new Drip({\n * apiKey: 'drip_live_abc123...',\n * });\n * ```\n */\n constructor(config: DripConfig) {\n if (!config.apiKey) {\n throw new Error('Drip API key is required');\n }\n\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || 'https://api.drip.dev/v1';\n this.timeout = config.timeout || 30000;\n }\n\n /**\n * Makes an authenticated request to the Drip API.\n * @internal\n */\n private async request<T>(\n path: string,\n options: RequestInit = {},\n ): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}${path}`, {\n ...options,\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n });\n\n // Handle 204 No Content\n if (res.status === 204) {\n return { success: true } as T;\n }\n\n const data = await res.json();\n\n if (!res.ok) {\n throw new DripError(\n data.message || data.error || 'Request failed',\n res.status,\n data.code,\n );\n }\n\n return data as T;\n } catch (error) {\n if (error instanceof DripError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new DripError('Request timed out', 408, 'TIMEOUT');\n }\n throw new DripError(\n error instanceof Error ? error.message : 'Unknown error',\n 0,\n 'UNKNOWN',\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // ==========================================================================\n // Health Check Methods\n // ==========================================================================\n\n /**\n * Pings the Drip API to check connectivity and measure latency.\n *\n * @returns Health status with latency information\n * @throws {DripError} If the request fails or times out\n *\n * @example\n * ```typescript\n * const health = await drip.ping();\n * if (health.ok) {\n * console.log(`API healthy, latency: ${health.latencyMs}ms`);\n * }\n * ```\n */\n async ping(): Promise<{ ok: boolean; status: string; latencyMs: number; timestamp: number }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n // Safely construct health endpoint URL\n let healthBaseUrl = this.baseUrl;\n if (healthBaseUrl.endsWith('/v1/')) {\n healthBaseUrl = healthBaseUrl.slice(0, -4);\n } else if (healthBaseUrl.endsWith('/v1')) {\n healthBaseUrl = healthBaseUrl.slice(0, -3);\n }\n healthBaseUrl = healthBaseUrl.replace(/\\/+$/, '');\n\n const start = Date.now();\n\n try {\n const response = await fetch(`${healthBaseUrl}/health`, {\n signal: controller.signal,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n const latencyMs = Date.now() - start;\n\n // Try to parse JSON, but handle non-JSON responses gracefully\n let status = 'unknown';\n let timestamp = Date.now();\n\n try {\n const data = await response.json() as { status?: string; timestamp?: number };\n if (typeof data.status === 'string') {\n status = data.status;\n }\n if (typeof data.timestamp === 'number') {\n timestamp = data.timestamp;\n }\n } catch {\n // Non-JSON response, derive status from HTTP code\n status = response.ok ? 'healthy' : `error:${response.status}`;\n }\n\n // For non-OK HTTP responses, set appropriate status\n if (!response.ok && status === 'unknown') {\n status = `error:${response.status}`;\n }\n\n return {\n ok: response.ok && status === 'healthy',\n status,\n latencyMs,\n timestamp,\n };\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new DripError('Request timed out', 408, 'TIMEOUT');\n }\n throw new DripError(\n error instanceof Error ? error.message : 'Unknown error',\n 0,\n 'UNKNOWN',\n );\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // ==========================================================================\n // Customer Methods\n // ==========================================================================\n\n /**\n * Creates a new customer in your Drip account.\n *\n * @param params - Customer creation parameters\n * @returns The created customer\n * @throws {DripError} If creation fails (e.g., duplicate customer)\n *\n * @example\n * ```typescript\n * const customer = await drip.createCustomer({\n * onchainAddress: '0x1234567890abcdef...',\n * externalCustomerId: 'user_123',\n * metadata: { plan: 'pro' },\n * });\n * ```\n */\n async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n return this.request<Customer>('/customers', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Retrieves a customer by their Drip ID.\n *\n * @param customerId - The Drip customer ID\n * @returns The customer details\n * @throws {DripError} If customer not found (404)\n *\n * @example\n * ```typescript\n * const customer = await drip.getCustomer('cust_abc123');\n * console.log(customer.onchainAddress);\n * ```\n */\n async getCustomer(customerId: string): Promise<Customer> {\n return this.request<Customer>(`/customers/${customerId}`);\n }\n\n /**\n * Lists all customers for your business.\n *\n * @param options - Optional filtering and pagination\n * @returns List of customers\n *\n * @example\n * ```typescript\n * // List all customers\n * const { data: customers } = await drip.listCustomers();\n *\n * // List with filters\n * const { data: activeCustomers } = await drip.listCustomers({\n * status: 'ACTIVE',\n * limit: 50,\n * });\n * ```\n */\n async listCustomers(\n options?: ListCustomersOptions,\n ): Promise<ListCustomersResponse> {\n const params = new URLSearchParams();\n\n if (options?.limit) {\n params.set('limit', options.limit.toString());\n }\n if (options?.status) {\n params.set('status', options.status);\n }\n\n const query = params.toString();\n const path = query ? `/customers?${query}` : '/customers';\n\n return this.request<ListCustomersResponse>(path);\n }\n\n /**\n * Gets the current balance for a customer.\n *\n * @param customerId - The Drip customer ID\n * @returns Current balance in USDC and native token\n *\n * @example\n * ```typescript\n * const balance = await drip.getBalance('cust_abc123');\n * console.log(`Balance: ${balance.balanceUSDC} USDC`);\n * ```\n */\n async getBalance(customerId: string): Promise<BalanceResult> {\n return this.request<BalanceResult>(`/customers/${customerId}/balance`);\n }\n\n // ==========================================================================\n // Charge Methods\n // ==========================================================================\n\n /**\n * Records usage and charges a customer.\n *\n * This is the primary method for billing customers. It:\n * 1. Records the usage event\n * 2. Calculates the charge based on your pricing plan\n * 3. Executes the on-chain charge\n *\n * @param params - Charge parameters\n * @returns The charge result\n * @throws {DripError} If charge fails (insufficient balance, invalid customer, etc.)\n *\n * @example\n * ```typescript\n * const result = await drip.charge({\n * customerId: 'cust_abc123',\n * meter: 'api_calls',\n * quantity: 100,\n * idempotencyKey: 'req_unique_123',\n * });\n *\n * if (result.success) {\n * console.log(`Charged ${result.charge.amountUsdc} USDC`);\n * console.log(`TX: ${result.charge.txHash}`);\n * }\n * ```\n */\n async charge(params: ChargeParams): Promise<ChargeResult> {\n return this.request<ChargeResult>('/usage', {\n method: 'POST',\n body: JSON.stringify({\n customerId: params.customerId,\n usageType: params.meter,\n quantity: params.quantity,\n idempotencyKey: params.idempotencyKey,\n metadata: params.metadata,\n }),\n });\n }\n\n /**\n * Wraps an external API call with guaranteed usage recording.\n *\n * **This solves the crash-before-record problem:**\n * ```typescript\n * // DANGEROUS - usage lost if crash between lines 1 and 2:\n * const response = await openai.chat.completions.create({...}); // line 1\n * await drip.charge({ tokens: response.usage.total_tokens }); // line 2\n *\n * // SAFE - wrapApiCall guarantees recording with retry:\n * const { result } = await drip.wrapApiCall({\n * call: () => openai.chat.completions.create({...}),\n * extractUsage: (r) => r.usage.total_tokens,\n * ...\n * });\n * ```\n *\n * How it works:\n * 1. Generates idempotency key BEFORE the API call\n * 2. Makes the external API call (once, no retry)\n * 3. Records usage in Drip with retry + idempotency\n * 4. If recording fails transiently, retries are safe (no double-charge)\n *\n * @param params - Wrap parameters including the call and usage extractor\n * @returns The API result and charge details\n * @throws {DripError} If the Drip charge fails after retries\n * @throws {Error} If the external API call fails\n *\n * @example\n * ```typescript\n * // OpenAI example\n * const { result, charge } = await drip.wrapApiCall({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * call: () => openai.chat.completions.create({\n * model: 'gpt-4',\n * messages: [{ role: 'user', content: 'Hello!' }],\n * }),\n * extractUsage: (r) => r.usage?.total_tokens ?? 0,\n * });\n *\n * console.log(result.choices[0].message.content);\n * console.log(`Charged: ${charge.charge.amountUsdc} USDC`);\n * ```\n *\n * @example\n * ```typescript\n * // Anthropic example\n * const { result, charge } = await drip.wrapApiCall({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * call: () => anthropic.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * }),\n * extractUsage: (r) => r.usage.input_tokens + r.usage.output_tokens,\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With custom retry options\n * const { result } = await drip.wrapApiCall({\n * customerId: 'cust_abc123',\n * meter: 'api_calls',\n * call: () => fetch('https://api.example.com/expensive'),\n * extractUsage: () => 1, // Fixed cost per call\n * retryOptions: {\n * maxAttempts: 5,\n * baseDelayMs: 200,\n * },\n * });\n * ```\n */\n async wrapApiCall<T>(params: WrapApiCallParams<T>): Promise<WrapApiCallResult<T>> {\n // Generate idempotency key BEFORE the call - this is the key insight!\n // Even if we crash after the API call, retrying with the same key is safe.\n const idempotencyKey = params.idempotencyKey\n ?? `wrap_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n\n // Step 1: Make the external API call (no retry - we don't control this)\n const result = await params.call();\n\n // Step 2: Extract usage from the result\n const quantity = params.extractUsage(result);\n\n // Step 3: Record usage in Drip with retry (idempotency makes this safe)\n const charge = await retryWithBackoff(\n () =>\n this.charge({\n customerId: params.customerId,\n meter: params.meter,\n quantity,\n idempotencyKey,\n metadata: params.metadata,\n }),\n params.retryOptions,\n );\n\n return {\n result,\n charge,\n idempotencyKey,\n };\n }\n\n /**\n * Records usage for internal visibility WITHOUT billing.\n *\n * Use this for:\n * - Tracking internal team usage without charging\n * - Pilot programs where you want visibility before billing\n * - Pre-billing tracking before customer has on-chain wallet\n *\n * This does NOT:\n * - Create a Charge record\n * - Require customer balance\n * - Require blockchain/wallet setup\n *\n * For billing, use `charge()` instead.\n *\n * @param params - The usage tracking parameters\n * @returns The tracked usage event\n *\n * @example\n * ```typescript\n * const result = await drip.trackUsage({\n * customerId: 'cust_abc123',\n * meter: 'api_calls',\n * quantity: 100,\n * description: 'API calls during trial period',\n * });\n *\n * console.log(`Tracked: ${result.usageEventId}`);\n * ```\n */\n async trackUsage(params: TrackUsageParams): Promise<TrackUsageResult> {\n return this.request<TrackUsageResult>('/usage/internal', {\n method: 'POST',\n body: JSON.stringify({\n customerId: params.customerId,\n usageType: params.meter,\n quantity: params.quantity,\n idempotencyKey: params.idempotencyKey,\n units: params.units,\n description: params.description,\n metadata: params.metadata,\n }),\n });\n }\n\n /**\n * Retrieves a specific charge by ID.\n *\n * @param chargeId - The charge ID\n * @returns The charge details\n * @throws {DripError} If charge not found (404)\n *\n * @example\n * ```typescript\n * const charge = await drip.getCharge('chg_abc123');\n * console.log(`Status: ${charge.status}`);\n * ```\n */\n async getCharge(chargeId: string): Promise<Charge> {\n return this.request<Charge>(`/charges/${chargeId}`);\n }\n\n /**\n * Lists charges for your business.\n *\n * @param options - Optional filtering and pagination\n * @returns List of charges\n *\n * @example\n * ```typescript\n * // List all charges\n * const { data: charges } = await drip.listCharges();\n *\n * // List charges for a specific customer\n * const { data: customerCharges } = await drip.listCharges({\n * customerId: 'cust_abc123',\n * status: 'CONFIRMED',\n * });\n * ```\n */\n async listCharges(options?: ListChargesOptions): Promise<ListChargesResponse> {\n const params = new URLSearchParams();\n\n if (options?.customerId) {\n params.set('customerId', options.customerId);\n }\n if (options?.status) {\n params.set('status', options.status);\n }\n if (options?.limit) {\n params.set('limit', options.limit.toString());\n }\n if (options?.offset) {\n params.set('offset', options.offset.toString());\n }\n\n const query = params.toString();\n const path = query ? `/charges?${query}` : '/charges';\n\n return this.request<ListChargesResponse>(path);\n }\n\n /**\n * Gets the current status of a charge.\n *\n * Useful for polling charge status after async operations.\n *\n * @param chargeId - The charge ID\n * @returns Current charge status\n *\n * @example\n * ```typescript\n * const status = await drip.getChargeStatus('chg_abc123');\n * if (status.status === 'CONFIRMED') {\n * console.log('Charge confirmed!');\n * }\n * ```\n */\n async getChargeStatus(\n chargeId: string,\n ): Promise<{ status: ChargeStatus; txHash?: string }> {\n return this.request<{ status: ChargeStatus; txHash?: string }>(\n `/charges/${chargeId}/status`,\n );\n }\n\n // ==========================================================================\n // Checkout Methods (Fiat On-Ramp)\n // ==========================================================================\n\n /**\n * Creates a checkout session to add funds to a customer's account.\n *\n * This is the PRIMARY method for getting money into Drip. It returns a URL\n * to a hosted checkout page where customers can pay via:\n * - Bank transfer (ACH) - $0.50 flat fee, 1-2 business days\n * - Debit card - 1.5% fee, instant\n * - Direct USDC - no fee, instant\n *\n * After payment, the customer is redirected to your returnUrl with:\n * - session_id: The checkout session ID\n * - customer_id: The Drip customer ID\n * - status: \"success\" or \"failed\"\n *\n * @param params - Checkout parameters\n * @returns Checkout session with redirect URL\n *\n * @example\n * ```typescript\n * // Basic checkout\n * const { url } = await drip.checkout({\n * customerId: 'cust_abc123',\n * amount: 5000, // $50.00\n * returnUrl: 'https://myapp.com/dashboard',\n * });\n *\n * // Redirect user to checkout\n * res.redirect(url);\n * ```\n *\n * @example\n * ```typescript\n * // Checkout for new customer\n * const { url, id } = await drip.checkout({\n * externalCustomerId: 'user_123', // Your user ID\n * amount: 10000, // $100.00\n * returnUrl: 'https://myapp.com/welcome',\n * metadata: { plan: 'pro' },\n * });\n * ```\n */\n async checkout(params: CheckoutParams): Promise<CheckoutResult> {\n const response = await this.request<{\n id: string;\n url: string;\n expires_at: string;\n amount_usd: number;\n }>('/checkout', {\n method: 'POST',\n body: JSON.stringify({\n customer_id: params.customerId,\n external_customer_id: params.externalCustomerId,\n amount: params.amount,\n return_url: params.returnUrl,\n cancel_url: params.cancelUrl,\n metadata: params.metadata,\n }),\n });\n\n return {\n id: response.id,\n url: response.url,\n expiresAt: response.expires_at,\n amountUsd: response.amount_usd,\n };\n }\n\n // ==========================================================================\n // Webhook Methods\n // ==========================================================================\n\n /**\n * Creates a new webhook endpoint.\n *\n * The webhook secret is only returned once at creation time.\n * Store it securely for verifying webhook signatures.\n *\n * @param config - Webhook configuration\n * @returns The created webhook with its secret\n *\n * @example\n * ```typescript\n * const webhook = await drip.createWebhook({\n * url: 'https://api.yourapp.com/webhooks/drip',\n * events: ['charge.succeeded', 'charge.failed'],\n * description: 'Main webhook endpoint',\n * });\n *\n * // IMPORTANT: Save this secret securely!\n * console.log(`Webhook secret: ${webhook.secret}`);\n * ```\n */\n async createWebhook(\n config: CreateWebhookParams,\n ): Promise<CreateWebhookResponse> {\n return this.request<CreateWebhookResponse>('/webhooks', {\n method: 'POST',\n body: JSON.stringify(config),\n });\n }\n\n /**\n * Lists all webhook endpoints for your business.\n *\n * @returns List of webhooks with delivery statistics\n *\n * @example\n * ```typescript\n * const { data: webhooks } = await drip.listWebhooks();\n * webhooks.forEach(wh => {\n * console.log(`${wh.url}: ${wh.stats?.successfulDeliveries} successful`);\n * });\n * ```\n */\n async listWebhooks(): Promise<ListWebhooksResponse> {\n return this.request<ListWebhooksResponse>('/webhooks');\n }\n\n /**\n * Retrieves a specific webhook by ID.\n *\n * @param webhookId - The webhook ID\n * @returns The webhook details with statistics\n * @throws {DripError} If webhook not found (404)\n *\n * @example\n * ```typescript\n * const webhook = await drip.getWebhook('wh_abc123');\n * console.log(`Events: ${webhook.events.join(', ')}`);\n * ```\n */\n async getWebhook(webhookId: string): Promise<Webhook> {\n return this.request<Webhook>(`/webhooks/${webhookId}`);\n }\n\n /**\n * Deletes a webhook endpoint.\n *\n * @param webhookId - The webhook ID to delete\n * @returns Success confirmation\n * @throws {DripError} If webhook not found (404)\n *\n * @example\n * ```typescript\n * await drip.deleteWebhook('wh_abc123');\n * console.log('Webhook deleted');\n * ```\n */\n async deleteWebhook(webhookId: string): Promise<DeleteWebhookResponse> {\n return this.request<DeleteWebhookResponse>(`/webhooks/${webhookId}`, {\n method: 'DELETE',\n });\n }\n\n /**\n * Tests a webhook by sending a test event.\n *\n * @param webhookId - The webhook ID to test\n * @returns Test result\n *\n * @example\n * ```typescript\n * const result = await drip.testWebhook('wh_abc123');\n * console.log(`Test status: ${result.status}`);\n * ```\n */\n async testWebhook(\n webhookId: string,\n ): Promise<{ message: string; deliveryId: string | null; status: string }> {\n return this.request<{\n message: string;\n deliveryId: string | null;\n status: string;\n }>(`/webhooks/${webhookId}/test`, {\n method: 'POST',\n });\n }\n\n /**\n * Rotates the signing secret for a webhook.\n *\n * After rotation, update your application to use the new secret.\n *\n * @param webhookId - The webhook ID\n * @returns The new secret\n *\n * @example\n * ```typescript\n * const { secret } = await drip.rotateWebhookSecret('wh_abc123');\n * console.log(`New secret: ${secret}`);\n * // Update your application with the new secret!\n * ```\n */\n async rotateWebhookSecret(\n webhookId: string,\n ): Promise<{ secret: string; message: string }> {\n return this.request<{ secret: string; message: string }>(\n `/webhooks/${webhookId}/rotate-secret`,\n { method: 'POST' },\n );\n }\n\n // ==========================================================================\n // Run & Event Methods (Execution Ledger)\n // ==========================================================================\n\n /**\n * Creates a new workflow definition.\n *\n * @param params - Workflow creation parameters\n * @returns The created workflow\n *\n * @example\n * ```typescript\n * const workflow = await drip.createWorkflow({\n * name: 'Prescription Intake',\n * slug: 'prescription_intake',\n * productSurface: 'AGENT',\n * });\n * ```\n */\n async createWorkflow(params: CreateWorkflowParams): Promise<Workflow> {\n return this.request<Workflow>('/workflows', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Lists all workflows for your business.\n *\n * @returns List of workflows\n */\n async listWorkflows(): Promise<{ data: Workflow[]; count: number }> {\n return this.request<{ data: Workflow[]; count: number }>('/workflows');\n }\n\n /**\n * Starts a new agent run for tracking execution.\n *\n * @param params - Run parameters\n * @returns The started run\n *\n * @example\n * ```typescript\n * const run = await drip.startRun({\n * customerId: 'cust_abc123',\n * workflowId: 'wf_xyz789',\n * correlationId: 'req_unique_123',\n * });\n *\n * // Emit events during execution...\n *\n * await drip.endRun(run.id, { status: 'COMPLETED' });\n * ```\n */\n async startRun(params: StartRunParams): Promise<RunResult> {\n return this.request<RunResult>('/runs', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Ends a run with a final status.\n *\n * @param runId - The run ID to end\n * @param params - End parameters including status\n * @returns Updated run info\n *\n * @example\n * ```typescript\n * await drip.endRun(run.id, {\n * status: 'COMPLETED',\n * });\n *\n * // Or with error:\n * await drip.endRun(run.id, {\n * status: 'FAILED',\n * errorMessage: 'Customer validation failed',\n * errorCode: 'VALIDATION_ERROR',\n * });\n * ```\n */\n async endRun(\n runId: string,\n params: EndRunParams,\n ): Promise<{\n id: string;\n status: RunStatus;\n endedAt: string | null;\n durationMs: number | null;\n eventCount: number;\n totalCostUnits: string | null;\n }> {\n return this.request(`/runs/${runId}`, {\n method: 'PATCH',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Gets a run's full timeline with events and computed totals.\n *\n * This is the key endpoint for debugging \"what happened\" in an execution.\n *\n * @param runId - The run ID\n * @returns Full timeline with events and summary\n *\n * @example\n * ```typescript\n * const { run, timeline, totals, summary } = await drip.getRunTimeline('run_abc123');\n *\n * console.log(`Status: ${run.status}`);\n * console.log(`Summary: ${summary}`);\n * console.log(`Total cost: ${totals.totalCostUnits}`);\n *\n * for (const event of timeline) {\n * console.log(`${event.eventType}: ${event.quantity} ${event.units}`);\n * }\n * ```\n */\n async getRunTimeline(runId: string): Promise<RunTimeline> {\n return this.request<RunTimeline>(`/runs/${runId}`);\n }\n\n /**\n * Emits an event to a run.\n *\n * Events can be stored idempotently when an `idempotencyKey` is provided.\n * Use `Drip.generateIdempotencyKey()` for deterministic key generation.\n * If `idempotencyKey` is omitted, repeated calls may create duplicate events.\n *\n * @param params - Event parameters\n * @returns The created event\n *\n * @example\n * ```typescript\n * await drip.emitEvent({\n * runId: run.id,\n * eventType: 'agent.validate',\n * quantity: 1,\n * description: 'Validated prescription format',\n * costUnits: 0.001,\n * });\n * ```\n */\n async emitEvent(params: EmitEventParams): Promise<EventResult> {\n return this.request<EventResult>('/events', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Emits multiple events in a single request.\n *\n * @param events - Array of events to emit\n * @returns Summary of created events\n *\n * @example\n * ```typescript\n * const result = await drip.emitEventsBatch([\n * { runId: run.id, eventType: 'agent.step1', quantity: 1 },\n * { runId: run.id, eventType: 'agent.step2', quantity: 100, units: 'tokens' },\n * ]);\n *\n * console.log(`Created: ${result.created}, Duplicates: ${result.duplicates}`);\n * ```\n */\n async emitEventsBatch(\n events: Array<Omit<EmitEventParams, 'runId'> & {\n runId?: string;\n customerId?: string;\n workflowId?: string;\n }>,\n ): Promise<{\n success: boolean;\n created: number;\n duplicates: number;\n events: Array<{ id: string; eventType: string; isDuplicate: boolean }>;\n }> {\n return this.request('/run-events/batch', {\n method: 'POST',\n body: JSON.stringify({ events }),\n });\n }\n\n // ==========================================================================\n // Simplified API Methods\n // ==========================================================================\n\n /**\n * Lists all available meters (usage types) for your business.\n *\n * Use this to discover what meter names are valid for the `charge()` method.\n * Meters are defined by your pricing plans.\n *\n * @returns List of available meters with their prices\n *\n * @example\n * ```typescript\n * const { data: meters } = await drip.listMeters();\n *\n * console.log('Available meters:');\n * for (const meter of meters) {\n * console.log(` ${meter.meter}: $${meter.unitPriceUsd}/unit`);\n * }\n *\n * // Use in charge():\n * await drip.charge({\n * customerId: 'cust_123',\n * meter: meters[0].meter, // Use a valid meter name\n * quantity: 100,\n * });\n * ```\n */\n async listMeters(): Promise<ListMetersResponse> {\n const response = await this.request<{\n data: Array<{\n id: string;\n name: string;\n unitType: string;\n unitPriceUsd: string;\n isActive: boolean;\n }>;\n count: number;\n }>('/pricing-plans');\n\n return {\n data: response.data.map((plan) => ({\n id: plan.id,\n name: plan.name,\n meter: plan.unitType,\n unitPriceUsd: plan.unitPriceUsd,\n isActive: plan.isActive,\n })),\n count: response.count,\n };\n }\n\n /**\n * Records a complete agent run in a single call.\n *\n * This is the **simplified API** that combines:\n * - Workflow creation (if needed)\n * - Run creation\n * - Event emission\n * - Run completion\n *\n * Use this instead of the individual `startRun()`, `emitEvent()`, `endRun()` calls\n * when you have all the run data available at once.\n *\n * @param params - Run parameters including events\n * @returns The created run with event summary\n *\n * @example\n * ```typescript\n * // Record a complete agent run in one call\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'prescription_intake', // Auto-creates if doesn't exist\n * events: [\n * { eventType: 'agent.start', description: 'Started processing' },\n * { eventType: 'tool.ocr', quantity: 3, units: 'pages', costUnits: 0.15 },\n * { eventType: 'tool.validate', quantity: 1, costUnits: 0.05 },\n * { eventType: 'agent.complete', description: 'Finished successfully' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * console.log(`Run ${result.run.id}: ${result.summary}`);\n * console.log(`Events: ${result.events.created} created`);\n * ```\n *\n * @example\n * ```typescript\n * // Record a failed run with error details\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'prescription_intake',\n * events: [\n * { eventType: 'agent.start', description: 'Started processing' },\n * { eventType: 'tool.ocr', quantity: 1, units: 'pages' },\n * { eventType: 'error', description: 'OCR failed: image too blurry' },\n * ],\n * status: 'FAILED',\n * errorMessage: 'OCR processing failed',\n * errorCode: 'OCR_QUALITY_ERROR',\n * });\n * ```\n */\n async recordRun(params: RecordRunParams): Promise<RecordRunResult> {\n const startTime = Date.now();\n\n // Step 1: Ensure workflow exists (get or create)\n let workflowId = params.workflow;\n let workflowName = params.workflow;\n\n // If it looks like a slug (no underscore prefix), try to find/create it\n if (!params.workflow.startsWith('wf_')) {\n try {\n // Try to find existing workflow by slug\n const workflows = await this.listWorkflows();\n const existing = workflows.data.find(\n (w) => w.slug === params.workflow || w.id === params.workflow,\n );\n\n if (existing) {\n workflowId = existing.id;\n workflowName = existing.name;\n } else {\n // Create new workflow with the slug\n const created = await this.createWorkflow({\n name: params.workflow.replace(/[_-]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()),\n slug: params.workflow,\n productSurface: 'AGENT',\n });\n workflowId = created.id;\n workflowName = created.name;\n }\n } catch {\n // If lookup fails, assume it's an ID\n workflowId = params.workflow;\n }\n }\n\n // Step 2: Create the run\n const run = await this.startRun({\n customerId: params.customerId,\n workflowId,\n externalRunId: params.externalRunId,\n correlationId: params.correlationId,\n metadata: params.metadata,\n });\n\n // Step 3: Emit all events in batch\n let eventsCreated = 0;\n let eventsDuplicates = 0;\n\n if (params.events.length > 0) {\n const batchEvents = params.events.map((event, index) => ({\n runId: run.id,\n eventType: event.eventType,\n quantity: event.quantity,\n units: event.units,\n description: event.description,\n costUnits: event.costUnits,\n metadata: event.metadata,\n idempotencyKey: params.externalRunId\n ? `${params.externalRunId}:${event.eventType}:${index}`\n : undefined,\n }));\n\n const batchResult = await this.emitEventsBatch(batchEvents);\n eventsCreated = batchResult.created;\n eventsDuplicates = batchResult.duplicates;\n }\n\n // Step 4: End the run\n const endResult = await this.endRun(run.id, {\n status: params.status,\n errorMessage: params.errorMessage,\n errorCode: params.errorCode,\n });\n\n const durationMs = Date.now() - startTime;\n\n // Build summary\n const eventSummary = params.events.length > 0\n ? `${eventsCreated} events recorded`\n : 'no events';\n const statusEmoji = params.status === 'COMPLETED' ? '✓' : params.status === 'FAILED' ? '✗' : '○';\n const summary = `${statusEmoji} ${workflowName}: ${eventSummary} (${endResult.durationMs ?? durationMs}ms)`;\n\n return {\n run: {\n id: run.id,\n workflowId,\n workflowName,\n status: params.status,\n durationMs: endResult.durationMs,\n },\n events: {\n created: eventsCreated,\n duplicates: eventsDuplicates,\n },\n totalCostUnits: endResult.totalCostUnits,\n summary,\n };\n }\n\n /**\n * Generates a deterministic idempotency key.\n *\n * Use this to ensure \"one logical action = one event\" even with retries.\n * The key is generated from customerId + runId + stepName + sequence.\n *\n * @param params - Key generation parameters\n * @returns A deterministic idempotency key\n *\n * @example\n * ```typescript\n * const key = Drip.generateIdempotencyKey({\n * customerId: 'cust_123',\n * runId: 'run_456',\n * stepName: 'validate_prescription',\n * sequence: 1,\n * });\n *\n * await drip.emitEvent({\n * runId: 'run_456',\n * eventType: 'agent.validate',\n * idempotencyKey: key,\n * });\n * ```\n */\n static generateIdempotencyKey(params: {\n customerId: string;\n runId?: string;\n stepName: string;\n sequence?: number;\n }): string {\n const components = [\n params.customerId,\n params.runId ?? 'no_run',\n params.stepName,\n String(params.sequence ?? 0),\n ];\n\n // Simple hash function for deterministic key generation\n let hash = 0;\n const str = components.join('|');\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 32bit integer\n }\n\n return `drip_${Math.abs(hash).toString(36)}_${params.stepName.slice(0, 16)}`;\n }\n\n // ==========================================================================\n // Utility Methods\n // ==========================================================================\n\n /**\n * Verifies a webhook signature using HMAC-SHA256.\n *\n * Call this when receiving webhook events to ensure they're authentic.\n * This is an async method that uses the Web Crypto API for secure verification.\n *\n * @param payload - The raw request body (string)\n * @param signature - The x-drip-signature header value\n * @param secret - Your webhook secret\n * @returns Promise resolving to whether the signature is valid\n *\n * @example\n * ```typescript\n * app.post('/webhooks/drip', async (req, res) => {\n * const isValid = await Drip.verifyWebhookSignature(\n * req.rawBody,\n * req.headers['x-drip-signature'],\n * process.env.DRIP_WEBHOOK_SECRET!,\n * );\n *\n * if (!isValid) {\n * return res.status(401).send('Invalid signature');\n * }\n *\n * // Process the webhook...\n * });\n * ```\n */\n static async verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string,\n tolerance = 300, // 5 minutes default\n ): Promise<boolean> {\n if (!payload || !signature || !secret) {\n return false;\n }\n\n try {\n // Parse signature format: t=timestamp,v1=hexsignature\n const parts = signature.split(',');\n const timestampPart = parts.find((p) => p.startsWith('t='));\n const signaturePart = parts.find((p) => p.startsWith('v1='));\n\n if (!timestampPart || !signaturePart) {\n return false;\n }\n\n const timestamp = parseInt(timestampPart.slice(2), 10);\n const providedSignature = signaturePart.slice(3);\n\n if (isNaN(timestamp)) {\n return false;\n }\n\n // Check timestamp tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > tolerance) {\n return false;\n }\n\n // Compute expected signature using timestamp.payload format\n const signaturePayload = `${timestamp}.${payload}`;\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const payloadData = encoder.encode(signaturePayload);\n\n // Get the subtle crypto API - use globalThis.crypto for browsers/edge runtimes,\n // or fall back to Node.js webcrypto for Node.js 18+\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const subtle = globalThis.crypto?.subtle ?? (require('crypto') as typeof import('crypto')).webcrypto.subtle;\n\n // Import the secret as an HMAC key\n const cryptoKey = await subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n\n // Sign the payload\n const signatureBuffer = await subtle.sign(\n 'HMAC',\n cryptoKey,\n payloadData,\n );\n\n // Convert to hex string\n const expectedSignature = Array.from(new Uint8Array(signatureBuffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n\n // Constant-time comparison to prevent timing attacks\n if (providedSignature.length !== expectedSignature.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < providedSignature.length; i++) {\n result |= providedSignature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n }\n\n return result === 0;\n } catch {\n return false;\n }\n }\n\n /**\n * Synchronously verifies a webhook signature using HMAC-SHA256.\n *\n * This method uses Node.js crypto module and is only available in Node.js environments.\n * For edge runtimes or browsers, use the async `verifyWebhookSignature` method instead.\n *\n * @param payload - The raw request body (string)\n * @param signature - The x-drip-signature header value\n * @param secret - Your webhook secret\n * @returns Whether the signature is valid\n *\n * @example\n * ```typescript\n * app.post('/webhooks/drip', (req, res) => {\n * const isValid = Drip.verifyWebhookSignatureSync(\n * req.rawBody,\n * req.headers['x-drip-signature'],\n * process.env.DRIP_WEBHOOK_SECRET!,\n * );\n *\n * if (!isValid) {\n * return res.status(401).send('Invalid signature');\n * }\n *\n * // Process the webhook...\n * });\n * ```\n */\n static verifyWebhookSignatureSync(\n payload: string,\n signature: string,\n secret: string,\n tolerance = 300, // 5 minutes default\n ): boolean {\n if (!payload || !signature || !secret) {\n return false;\n }\n\n try {\n // Parse signature format: t=timestamp,v1=hexsignature\n const parts = signature.split(',');\n const timestampPart = parts.find((p) => p.startsWith('t='));\n const signaturePart = parts.find((p) => p.startsWith('v1='));\n\n if (!timestampPart || !signaturePart) {\n return false;\n }\n\n const timestamp = parseInt(timestampPart.slice(2), 10);\n const providedSignature = signaturePart.slice(3);\n\n if (isNaN(timestamp)) {\n return false;\n }\n\n // Check timestamp tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > tolerance) {\n return false;\n }\n\n // Dynamic import to avoid bundling issues in edge runtimes\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const crypto = require('crypto') as typeof import('crypto');\n\n // Compute expected signature using timestamp.payload format\n const signaturePayload = `${timestamp}.${payload}`;\n const expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(signaturePayload)\n .digest('hex');\n\n // Use timingSafeEqual for constant-time comparison\n const sigBuffer = Buffer.from(providedSignature);\n const expectedBuffer = Buffer.from(expectedSignature);\n\n if (sigBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(sigBuffer, expectedBuffer);\n } catch {\n return false;\n }\n }\n\n /**\n * Generates a webhook signature for testing purposes.\n *\n * This method creates a signature in the same format the Drip backend uses,\n * allowing you to test your webhook handling code locally.\n *\n * @param payload - The webhook payload (JSON string)\n * @param secret - The webhook secret\n * @param timestamp - Optional timestamp (defaults to current time)\n * @returns Signature in format: t=timestamp,v1=hexsignature\n *\n * @example\n * ```typescript\n * const payload = JSON.stringify({ type: 'charge.succeeded', data: {...} });\n * const signature = Drip.generateWebhookSignature(payload, 'whsec_test123');\n *\n * // Use in tests:\n * const isValid = Drip.verifyWebhookSignatureSync(payload, signature, 'whsec_test123');\n * console.log(isValid); // true\n * ```\n */\n static generateWebhookSignature(\n payload: string,\n secret: string,\n timestamp?: number,\n ): string {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const crypto = require('crypto') as typeof import('crypto');\n\n const ts = timestamp ?? Math.floor(Date.now() / 1000);\n const signaturePayload = `${ts}.${payload}`;\n const signature = crypto\n .createHmac('sha256', secret)\n .update(signaturePayload)\n .digest('hex');\n\n return `t=${ts},v1=${signature}`;\n }\n\n // ==========================================================================\n // StreamMeter Factory\n // ==========================================================================\n\n /**\n * Creates a StreamMeter for accumulating usage and charging once.\n *\n * Perfect for LLM token streaming where you want to:\n * - Accumulate tokens locally (no API call per token)\n * - Charge once at the end of the stream\n * - Handle partial failures (charge for what was delivered)\n *\n * @param options - StreamMeter configuration\n * @returns A new StreamMeter instance\n *\n * @example\n * ```typescript\n * const meter = drip.createStreamMeter({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * });\n *\n * // Accumulate tokens as they stream\n * for await (const chunk of llmStream) {\n * meter.addSync(chunk.tokens);\n * yield chunk;\n * }\n *\n * // Single charge at end\n * const result = await meter.flush();\n * console.log(`Charged ${result.charge?.amountUsdc} for ${result.quantity} tokens`);\n * ```\n *\n * @example\n * ```typescript\n * // With auto-flush threshold\n * const meter = drip.createStreamMeter({\n * customerId: 'cust_abc123',\n * meter: 'tokens',\n * flushThreshold: 10000, // Charge every 10k tokens\n * });\n *\n * for await (const chunk of longStream) {\n * await meter.add(chunk.tokens); // May auto-flush\n * }\n *\n * await meter.flush(); // Final flush for remaining tokens\n * ```\n */\n createStreamMeter(options: StreamMeterOptions): StreamMeter {\n return new StreamMeter(this.charge.bind(this), options);\n }\n}\n\n// Re-export StreamMeter types\nexport { StreamMeter } from './stream-meter.js';\nexport type { StreamMeterOptions, StreamMeterFlushResult } from './stream-meter.js';\n\n// Default export for convenience\nexport default Drip;\n"]}
@@ -1,3 +1,3 @@
1
- 'use strict';var crypto$1=require('crypto');var T=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var f=class t extends Error{constructor(r,s,i,n){super(r);this.code=s;this.statusCode=i;this.details=n;this.name="DripMiddlewareError",Object.setPrototypeOf(this,t.prototype);}};var b=class{_total=0;_flushed=false;_flushCount=0;_chargeFn;_options;constructor(e,r){this._chargeFn=e,this._options=r;}get total(){return this._total}get isFlushed(){return this._flushed}get flushCount(){return this._flushCount}async add(e){return e<=0?null:(this._total+=e,this._options.onAdd?.(e,this._total),this._options.flushThreshold!==void 0&&this._total>=this._options.flushThreshold?this.flush():null)}addSync(e){e<=0||(this._total+=e,this._options.onAdd?.(e,this._total));}async flush(){let e=this._total;if(this._total=0,e===0)return {success:true,quantity:0,charge:null,isReplay:false};let r=this._options.idempotencyKey?`${this._options.idempotencyKey}_flush_${this._flushCount}`:void 0,s=await this._chargeFn({customerId:this._options.customerId,meter:this._options.meter,quantity:e,idempotencyKey:r,metadata:this._options.metadata});this._flushed=true,this._flushCount++;let i={success:s.success,quantity:e,charge:s.charge,isReplay:s.isReplay};return this._options.onFlush?.(i),i}reset(){this._total=0;}};var R=class t extends Error{constructor(r,s,i){super(r);this.statusCode=s;this.code=i;this.name="DripError",Object.setPrototypeOf(this,t.prototype);}},E=class{apiKey;baseUrl;timeout;constructor(e){if(!e.apiKey)throw new Error("Drip API key is required");this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://api.drip.dev/v1",this.timeout=e.timeout||3e4;}async request(e,r={}){let s=new AbortController,i=setTimeout(()=>s.abort(),this.timeout);try{let n=await fetch(`${this.baseUrl}${e}`,{...r,signal:s.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...r.headers}});if(n.status===204)return {success:!0};let o=await n.json();if(!n.ok)throw new R(o.message||o.error||"Request failed",n.status,o.code);return o}catch(n){throw n instanceof R?n:n instanceof Error&&n.name==="AbortError"?new R("Request timed out",408,"TIMEOUT"):new R(n instanceof Error?n.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(i);}}async ping(){let e=new AbortController,r=setTimeout(()=>e.abort(),this.timeout),s=this.baseUrl;s.endsWith("/v1/")?s=s.slice(0,-4):s.endsWith("/v1")&&(s=s.slice(0,-3)),s=s.replace(/\/+$/,"");let i=Date.now();try{let n=await fetch(`${s}/health`,{signal:e.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),o=Date.now()-i,u="unknown",d=Date.now();try{let p=await n.json();typeof p.status=="string"&&(u=p.status),typeof p.timestamp=="number"&&(d=p.timestamp);}catch{u=n.ok?"healthy":`error:${n.status}`;}return !n.ok&&u==="unknown"&&(u=`error:${n.status}`),{ok:n.ok&&u==="healthy",status:u,latencyMs:o,timestamp:d}}catch(n){throw n instanceof Error&&n.name==="AbortError"?new R("Request timed out",408,"TIMEOUT"):new R(n instanceof Error?n.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(r);}}async createCustomer(e){return this.request("/customers",{method:"POST",body:JSON.stringify(e)})}async getCustomer(e){return this.request(`/customers/${e}`)}async listCustomers(e){let r=new URLSearchParams;e?.limit&&r.set("limit",e.limit.toString()),e?.status&&r.set("status",e.status);let s=r.toString(),i=s?`/customers?${s}`:"/customers";return this.request(i)}async getBalance(e){return this.request(`/customers/${e}/balance`)}async charge(e){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,metadata:e.metadata})})}async trackUsage(e){return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,units:e.units,description:e.description,metadata:e.metadata})})}async getCharge(e){return this.request(`/charges/${e}`)}async listCharges(e){let r=new URLSearchParams;e?.customerId&&r.set("customerId",e.customerId),e?.status&&r.set("status",e.status),e?.limit&&r.set("limit",e.limit.toString()),e?.offset&&r.set("offset",e.offset.toString());let s=r.toString(),i=s?`/charges?${s}`:"/charges";return this.request(i)}async getChargeStatus(e){return this.request(`/charges/${e}/status`)}async checkout(e){let r=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:e.customerId,external_customer_id:e.externalCustomerId,amount:e.amount,return_url:e.returnUrl,cancel_url:e.cancelUrl,metadata:e.metadata})});return {id:r.id,url:r.url,expiresAt:r.expires_at,amountUsd:r.amount_usd}}async createWebhook(e){return this.request("/webhooks",{method:"POST",body:JSON.stringify(e)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(e){return this.request(`/webhooks/${e}`)}async deleteWebhook(e){return this.request(`/webhooks/${e}`,{method:"DELETE"})}async testWebhook(e){return this.request(`/webhooks/${e}/test`,{method:"POST"})}async rotateWebhookSecret(e){return this.request(`/webhooks/${e}/rotate-secret`,{method:"POST"})}async createWorkflow(e){return this.request("/workflows",{method:"POST",body:JSON.stringify(e)})}async listWorkflows(){return this.request("/workflows")}async startRun(e){return this.request("/runs",{method:"POST",body:JSON.stringify(e)})}async endRun(e,r){return this.request(`/runs/${e}`,{method:"PATCH",body:JSON.stringify(r)})}async getRunTimeline(e){return this.request(`/runs/${e}`)}async emitEvent(e){return this.request("/events",{method:"POST",body:JSON.stringify(e)})}async emitEventsBatch(e){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:e})})}async listMeters(){let e=await this.request("/pricing-plans");return {data:e.data.map(r=>({id:r.id,name:r.name,meter:r.unitType,unitPriceUsd:r.unitPriceUsd,isActive:r.isActive})),count:e.count}}async recordRun(e){let r=Date.now(),s=e.workflow,i=e.workflow;if(!e.workflow.startsWith("wf_"))try{let y=(await this.listWorkflows()).data.find(l=>l.slug===e.workflow||l.id===e.workflow);if(y)s=y.id,i=y.name;else {let l=await this.createWorkflow({name:e.workflow.replace(/[_-]/g," ").replace(/\b\w/g,x=>x.toUpperCase()),slug:e.workflow,productSurface:"AGENT"});s=l.id,i=l.name;}}catch{s=e.workflow;}let n=await this.startRun({customerId:e.customerId,workflowId:s,externalRunId:e.externalRunId,correlationId:e.correlationId,metadata:e.metadata}),o=0,u=0;if(e.events.length>0){let g=e.events.map((l,x)=>({runId:n.id,eventType:l.eventType,quantity:l.quantity,units:l.units,description:l.description,costUnits:l.costUnits,metadata:l.metadata,idempotencyKey:e.externalRunId?`${e.externalRunId}:${l.eventType}:${x}`:void 0})),y=await this.emitEventsBatch(g);o=y.created,u=y.duplicates;}let d=await this.endRun(n.id,{status:e.status,errorMessage:e.errorMessage,errorCode:e.errorCode}),p=Date.now()-r,a=e.events.length>0?`${o} events recorded`:"no events",m=`${e.status==="COMPLETED"?"\u2713":e.status==="FAILED"?"\u2717":"\u25CB"} ${i}: ${a} (${d.durationMs??p}ms)`;return {run:{id:n.id,workflowId:s,workflowName:i,status:e.status,durationMs:d.durationMs},events:{created:o,duplicates:u},totalCostUnits:d.totalCostUnits,summary:m}}static generateIdempotencyKey(e){let r=[e.customerId,e.runId??"no_run",e.stepName,String(e.sequence??0)],s=0,i=r.join("|");for(let n=0;n<i.length;n++){let o=i.charCodeAt(n);s=(s<<5)-s+o,s=s&s;}return `drip_${Math.abs(s).toString(36)}_${e.stepName.slice(0,16)}`}static async verifyWebhookSignature(e,r,s,i=300){if(!e||!r||!s)return false;try{let n=r.split(","),o=n.find(P=>P.startsWith("t=")),u=n.find(P=>P.startsWith("v1="));if(!o||!u)return !1;let d=parseInt(o.slice(2),10),p=u.slice(3);if(isNaN(d))return !1;let a=Math.floor(Date.now()/1e3);if(Math.abs(a-d)>i)return !1;let c=`${d}.${e}`,m=new TextEncoder,g=m.encode(s),y=m.encode(c),l=await crypto.subtle.importKey("raw",g,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),x=await crypto.subtle.sign("HMAC",l,y),k=Array.from(new Uint8Array(x)).map(P=>P.toString(16).padStart(2,"0")).join("");if(p.length!==k.length)return !1;let v=0;for(let P=0;P<p.length;P++)v|=p.charCodeAt(P)^k.charCodeAt(P);return v===0}catch{return false}}static verifyWebhookSignatureSync(e,r,s,i=300){if(!e||!r||!s)return false;try{let n=r.split(","),o=n.find(x=>x.startsWith("t=")),u=n.find(x=>x.startsWith("v1="));if(!o||!u)return !1;let d=parseInt(o.slice(2),10),p=u.slice(3);if(isNaN(d))return !1;let a=Math.floor(Date.now()/1e3);if(Math.abs(a-d)>i)return !1;let c=T("crypto"),m=`${d}.${e}`,g=c.createHmac("sha256",s).update(m).digest("hex"),y=Buffer.from(p),l=Buffer.from(g);return y.length!==l.length?!1:c.timingSafeEqual(y,l)}catch{return false}}static generateWebhookSignature(e,r,s){let i=T("crypto"),n=s??Math.floor(Date.now()/1e3),o=`${n}.${e}`,u=i.createHmac("sha256",r).update(o).digest("hex");return `t=${n},v1=${u}`}createStreamMeter(e){return new b(this.charge.bind(this),e)}};var $=300,F=300,X=["x-payment-signature","x-payment-session-key","x-payment-smart-account","x-payment-timestamp","x-payment-amount","x-payment-recipient","x-payment-usage-id","x-payment-nonce"];function j(t){return t.toLowerCase()}function h(t,e){let r=j(e);if(t[r]!==void 0){let s=t[r];return Array.isArray(s)?s[0]:s}for(let[s,i]of Object.entries(t))if(s.toLowerCase()===r)return Array.isArray(i)?i[0]:i}function C(t){return X.every(e=>h(t,e)!==void 0)}function S(t){let e=h(t,"x-payment-signature"),r=h(t,"x-payment-session-key"),s=h(t,"x-payment-smart-account"),i=h(t,"x-payment-timestamp"),n=h(t,"x-payment-amount"),o=h(t,"x-payment-recipient"),u=h(t,"x-payment-usage-id"),d=h(t,"x-payment-nonce");if(!e||!r||!s||!i||!n||!o||!u||!d)return null;let p=parseInt(i,10);if(isNaN(p)||Math.floor(Date.now()/1e3)-p>F)return null;let c=(m,g)=>{if(!m.startsWith("0x"))return false;let y=m.slice(2);return y.length<g?false:/^[a-fA-F0-9]+$/.test(y)};return !c(e,130)||!c(r,64)||!c(s,40)?null:{signature:e,sessionKeyId:r,smartAccount:s,timestamp:p,amount:n,recipient:o,usageId:u,nonce:d}}function A(t){let e=Math.floor(Date.now()/1e3),r=e+(t.expiresInSec??$),s=`${e}-${crypto$1.randomBytes(16).toString("hex")}`,i=t.usageId;i.startsWith("0x")||(i=N(i));let n={"X-Payment-Required":"true","X-Payment-Amount":t.amount,"X-Payment-Recipient":t.recipient,"X-Payment-Usage-Id":i,"X-Payment-Description":t.description??"API usage charge","X-Payment-Expires":String(r),"X-Payment-Nonce":s,"X-Payment-Timestamp":String(e)},o={amount:t.amount,recipient:t.recipient,usageId:i,description:t.description??"API usage charge",expiresAt:r,nonce:s,timestamp:e};return {headers:n,paymentRequest:o}}function N(t){let e=5381,r=52711;for(let n=0;n<t.length;n++){let o=t.charCodeAt(n);e=(e<<5)+e^o,r=(r<<5)+r^o;}return `0x${Math.abs(e*31+r).toString(16).padStart(16,"0").slice(0,16).padEnd(64,"0")}`}async function _(t,e){let r=e.customerResolver??"header";if(typeof r=="function")return r(t);if(r==="header"){let s=h(t.headers,"x-drip-customer-id")??h(t.headers,"x-customer-id");if(!s)throw new f("Missing customer ID. Include X-Drip-Customer-Id header.","CUSTOMER_RESOLUTION_FAILED",400);return s}if(r==="query"){let s=t.query??{},i=s.drip_customer_id??s.customer_id,n=Array.isArray(i)?i[0]:i;if(!n)throw new f("Missing customer ID. Include drip_customer_id query parameter.","CUSTOMER_RESOLUTION_FAILED",400);return n}throw new f(`Invalid customer resolver: ${r}`,"CONFIGURATION_ERROR",500)}async function M(t,e){return typeof e.quantity=="function"?e.quantity(t):e.quantity}async function U(t,e,r){if(r.idempotencyKey)return r.idempotencyKey(t);let s=Date.now(),i=[t.method,t.url,e,s];return `drip_${N(i.join("|")).slice(2,18)}`}function q(t){let e=t.apiKey??process.env.DRIP_API_KEY;if(!e)throw new f("Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.","CONFIGURATION_ERROR",500);return new E({apiKey:e,baseUrl:t.baseUrl??process.env.DRIP_API_URL})}async function I(t,e){if(e.skipInDevelopment&&process.env.NODE_ENV==="development"){console.warn("[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.");let r=q(e),s={success:true,usageEventId:"dev_usage_event",isReplay:false,charge:{id:"dev_charge",amountUsdc:"0.00",amountToken:"0",txHash:"0x0",status:"CONFIRMED"}};return {success:true,state:{customerId:"dev_customer",quantity:typeof e.quantity=="number"?e.quantity:1,idempotencyKey:"dev_idempotency",hasPaymentProof:false},charge:s,drip:r,isReplay:false}}try{let r=q(e),s=await _(t,e),i=await M(t,e),n=await U(t,s,e),o=C(t.headers),u=o?S(t.headers):void 0,d={customerId:s,quantity:i,idempotencyKey:n,hasPaymentProof:o,paymentProof:u??void 0},p=typeof e.metadata=="function"?e.metadata(t):e.metadata;try{let a=await r.charge({customerId:s,meter:e.meter,quantity:i,idempotencyKey:n,metadata:p});return e.onCharge&&await e.onCharge(a,t),{success:!0,state:d,charge:a,drip:r,isReplay:a.isReplay??!1}}catch(a){if(a instanceof R){if(a.statusCode===402){let c=process.env.DRIP_RECIPIENT_ADDRESS;if(!c)throw new f("DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.","CONFIGURATION_ERROR",500);let m="0.01",g=a.message.match(/amount[:\s]+([0-9.]+)/i);g?m=g[1]:m=(i*1e-4).toFixed(6);let{headers:y,paymentRequest:l}=A({amount:m,recipient:c,usageId:n,description:`${e.meter} usage charge`});return {success:!1,error:new f("Insufficient balance. Payment required.","PAYMENT_REQUIRED",402),paymentRequired:{headers:y,paymentRequest:l}}}throw e.onError&&await e.onError(a,t),new f(a.message,"CHARGE_FAILED",a.statusCode,{code:a.code})}throw a}}catch(r){if(r instanceof f)return {success:false,error:r};let s=r instanceof Error?r.message:"Unknown error";return {success:false,error:new f(s,"INTERNAL_ERROR",500)}}}function D(t){let e={};return t.forEach((r,s)=>{e[s.toLowerCase()]=r;}),e}function G(t){let e={};return t.forEach((r,s)=>{e[s]=r;}),e}function B(t,e,r,s){return Response.json({error:t,code:e,...s&&{details:s}},{status:r})}function J(t,e){let r=new Headers;return Object.entries(t).forEach(([s,i])=>{r.set(s,i);}),r.set("Content-Type","application/json"),new Response(JSON.stringify({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:e,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}}),{status:402,headers:r})}function O(t,e){return async(r,s)=>{let i={method:r.method,url:r.url,headers:D(r.headers),query:r.nextUrl?G(r.nextUrl.searchParams):{}},n=typeof t.quantity=="function"?await t.quantity(r):t.quantity,o;if(typeof t.customerResolver=="function"){let m=t.customerResolver;o=async()=>m(r);}else o=t.customerResolver;let u;if(typeof t.idempotencyKey=="function"){let m=t.idempotencyKey;u=async()=>m(r);}let d=typeof t.metadata=="function"?t.metadata(r):t.metadata,p={meter:t.meter,quantity:n,apiKey:t.apiKey,baseUrl:t.baseUrl,customerResolver:o,idempotencyKey:u,metadata:d,skipInDevelopment:t.skipInDevelopment,onCharge:void 0,onError:void 0},a=await I(i,p);if(!a.success){if(t.errorResponse){let m=await t.errorResponse(a.error,r);if(m)return m}return a.paymentRequired?J(a.paymentRequired.headers,a.paymentRequired.paymentRequest):B(a.error.message,a.error.code,a.error.statusCode,a.error.details)}t.onCharge&&await t.onCharge(a.charge,r);let c={drip:a.drip,customerId:a.state.customerId,charge:a.charge,isReplay:a.isReplay,params:s?.params};try{return await e(r,c)}catch(m){throw m}}}function Q(t){return (e,r)=>O({...t,...e},r)}function Y(t){return C(D(t.headers))}function z(t,e){return h(D(t.headers),e)}function W(t){let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=Array.isArray(s)?s[0]:s;return e}function V(t,e,r){t.status(402).set(e).json({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:r,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}});}function Z(t,e,r,s,i){t.status(s).json({error:e,code:r,...i&&{details:i}});}function K(t){let e=t.attachToRequest??true;return async(r,s,i)=>{let n={method:r.method,url:r.originalUrl||r.url,headers:W(r.headers),query:r.query},o=typeof t.quantity=="function"?await t.quantity(r):t.quantity,u;if(typeof t.customerResolver=="function"){let g=t.customerResolver;u=async()=>g(r);}else u=t.customerResolver;let d;if(typeof t.idempotencyKey=="function"){let g=t.idempotencyKey;d=async()=>g(r);}let p=typeof t.metadata=="function"?t.metadata(r):t.metadata,a={meter:t.meter,quantity:o,apiKey:t.apiKey,baseUrl:t.baseUrl,customerResolver:u,idempotencyKey:d,metadata:p,skipInDevelopment:t.skipInDevelopment,onCharge:void 0,onError:void 0},c=await I(n,a);if(!c.success){if(t.errorHandler&&await t.errorHandler(c.error,r,s))return;if(c.paymentRequired){V(s,c.paymentRequired.headers,c.paymentRequired.paymentRequest);return}Z(s,c.error.message,c.error.code,c.error.statusCode,c.error.details);return}t.onCharge&&await t.onCharge(c.charge,r);let m={drip:c.drip,customerId:c.state.customerId,charge:c.charge,isReplay:c.isReplay};e&&(r.drip=m),i();}}function ee(t){return e=>K({...t,...e})}function te(t){return C(W(t.headers))}function L(t){return "drip"in t&&typeof t.drip=="object"}function re(t){if(!L(t))throw new Error("Drip context not found on request. Ensure dripMiddleware is applied before this route.");return t.drip}
2
- exports.Drip=E;exports.DripError=R;exports.DripMiddlewareError=f;exports.createDripClient=q;exports.createDripMiddleware=ee;exports.createWithDrip=Q;exports.dripMiddleware=K;exports.generateIdempotencyKey=U;exports.generatePaymentRequest=A;exports.getDripContext=re;exports.getDripHeader=z;exports.getHeader=h;exports.hasDripContext=L;exports.hasExpressPaymentProof=te;exports.hasNextPaymentProof=Y;exports.hasPaymentProof=C;exports.parsePaymentProof=S;exports.processRequest=I;exports.resolveCustomerId=_;exports.resolveQuantity=M;exports.withDrip=O;//# sourceMappingURL=middleware.cjs.map
1
+ 'use strict';var crypto=require('crypto');var D=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var f=class t extends Error{constructor(r,s,i,n){super(r);this.code=s;this.statusCode=i;this.details=n;this.name="DripMiddlewareError",Object.setPrototypeOf(this,t.prototype);}};var C=class{_total=0;_flushed=false;_flushCount=0;_chargeFn;_options;constructor(e,r){this._chargeFn=e,this._options=r;}get total(){return this._total}get isFlushed(){return this._flushed}get flushCount(){return this._flushCount}async add(e){return e<=0?null:(this._total+=e,this._options.onAdd?.(e,this._total),this._options.flushThreshold!==void 0&&this._total>=this._options.flushThreshold?this.flush():null)}addSync(e){e<=0||(this._total+=e,this._options.onAdd?.(e,this._total));}async flush(){let e=this._total;if(this._total=0,e===0)return {success:true,quantity:0,charge:null,isReplay:false};let r=this._options.idempotencyKey?`${this._options.idempotencyKey}_flush_${this._flushCount}`:void 0,s=await this._chargeFn({customerId:this._options.customerId,meter:this._options.meter,quantity:e,idempotencyKey:r,metadata:this._options.metadata});this._flushed=true,this._flushCount++;let i={success:s.success,quantity:e,charge:s.charge,isReplay:s.isReplay};return this._options.onFlush?.(i),i}reset(){this._total=0;}};var q={maxAttempts:3,baseDelayMs:100,maxDelayMs:5e3};function F(t){return t instanceof Error&&(t.message.includes("fetch")||t.message.includes("network"))?true:t instanceof R?t.statusCode>=500||t.statusCode===408||t.statusCode===429:false}async function X(t,e={}){let r=e.maxAttempts??q.maxAttempts,s=e.baseDelayMs??q.baseDelayMs,i=e.maxDelayMs??q.maxDelayMs,n=e.isRetryable??F,o;for(let u=1;u<=r;u++)try{return await t()}catch(d){if(o=d,u===r||!n(d))throw d;let m=Math.min(s*Math.pow(2,u-1)+Math.random()*100,i);await new Promise(a=>setTimeout(a,m));}throw o}var R=class t extends Error{constructor(r,s,i){super(r);this.statusCode=s;this.code=i;this.name="DripError",Object.setPrototypeOf(this,t.prototype);}},E=class{apiKey;baseUrl;timeout;constructor(e){if(!e.apiKey)throw new Error("Drip API key is required");this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://api.drip.dev/v1",this.timeout=e.timeout||3e4;}async request(e,r={}){let s=new AbortController,i=setTimeout(()=>s.abort(),this.timeout);try{let n=await fetch(`${this.baseUrl}${e}`,{...r,signal:s.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...r.headers}});if(n.status===204)return {success:!0};let o=await n.json();if(!n.ok)throw new R(o.message||o.error||"Request failed",n.status,o.code);return o}catch(n){throw n instanceof R?n:n instanceof Error&&n.name==="AbortError"?new R("Request timed out",408,"TIMEOUT"):new R(n instanceof Error?n.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(i);}}async ping(){let e=new AbortController,r=setTimeout(()=>e.abort(),this.timeout),s=this.baseUrl;s.endsWith("/v1/")?s=s.slice(0,-4):s.endsWith("/v1")&&(s=s.slice(0,-3)),s=s.replace(/\/+$/,"");let i=Date.now();try{let n=await fetch(`${s}/health`,{signal:e.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),o=Date.now()-i,u="unknown",d=Date.now();try{let m=await n.json();typeof m.status=="string"&&(u=m.status),typeof m.timestamp=="number"&&(d=m.timestamp);}catch{u=n.ok?"healthy":`error:${n.status}`;}return !n.ok&&u==="unknown"&&(u=`error:${n.status}`),{ok:n.ok&&u==="healthy",status:u,latencyMs:o,timestamp:d}}catch(n){throw n instanceof Error&&n.name==="AbortError"?new R("Request timed out",408,"TIMEOUT"):new R(n instanceof Error?n.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(r);}}async createCustomer(e){return this.request("/customers",{method:"POST",body:JSON.stringify(e)})}async getCustomer(e){return this.request(`/customers/${e}`)}async listCustomers(e){let r=new URLSearchParams;e?.limit&&r.set("limit",e.limit.toString()),e?.status&&r.set("status",e.status);let s=r.toString(),i=s?`/customers?${s}`:"/customers";return this.request(i)}async getBalance(e){return this.request(`/customers/${e}/balance`)}async charge(e){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,metadata:e.metadata})})}async wrapApiCall(e){let r=e.idempotencyKey??`wrap_${Date.now()}_${Math.random().toString(36).slice(2,11)}`,s=await e.call(),i=e.extractUsage(s),n=await X(()=>this.charge({customerId:e.customerId,meter:e.meter,quantity:i,idempotencyKey:r,metadata:e.metadata}),e.retryOptions);return {result:s,charge:n,idempotencyKey:r}}async trackUsage(e){return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:e.customerId,usageType:e.meter,quantity:e.quantity,idempotencyKey:e.idempotencyKey,units:e.units,description:e.description,metadata:e.metadata})})}async getCharge(e){return this.request(`/charges/${e}`)}async listCharges(e){let r=new URLSearchParams;e?.customerId&&r.set("customerId",e.customerId),e?.status&&r.set("status",e.status),e?.limit&&r.set("limit",e.limit.toString()),e?.offset&&r.set("offset",e.offset.toString());let s=r.toString(),i=s?`/charges?${s}`:"/charges";return this.request(i)}async getChargeStatus(e){return this.request(`/charges/${e}/status`)}async checkout(e){let r=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:e.customerId,external_customer_id:e.externalCustomerId,amount:e.amount,return_url:e.returnUrl,cancel_url:e.cancelUrl,metadata:e.metadata})});return {id:r.id,url:r.url,expiresAt:r.expires_at,amountUsd:r.amount_usd}}async createWebhook(e){return this.request("/webhooks",{method:"POST",body:JSON.stringify(e)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(e){return this.request(`/webhooks/${e}`)}async deleteWebhook(e){return this.request(`/webhooks/${e}`,{method:"DELETE"})}async testWebhook(e){return this.request(`/webhooks/${e}/test`,{method:"POST"})}async rotateWebhookSecret(e){return this.request(`/webhooks/${e}/rotate-secret`,{method:"POST"})}async createWorkflow(e){return this.request("/workflows",{method:"POST",body:JSON.stringify(e)})}async listWorkflows(){return this.request("/workflows")}async startRun(e){return this.request("/runs",{method:"POST",body:JSON.stringify(e)})}async endRun(e,r){return this.request(`/runs/${e}`,{method:"PATCH",body:JSON.stringify(r)})}async getRunTimeline(e){return this.request(`/runs/${e}`)}async emitEvent(e){return this.request("/events",{method:"POST",body:JSON.stringify(e)})}async emitEventsBatch(e){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:e})})}async listMeters(){let e=await this.request("/pricing-plans");return {data:e.data.map(r=>({id:r.id,name:r.name,meter:r.unitType,unitPriceUsd:r.unitPriceUsd,isActive:r.isActive})),count:e.count}}async recordRun(e){let r=Date.now(),s=e.workflow,i=e.workflow;if(!e.workflow.startsWith("wf_"))try{let y=(await this.listWorkflows()).data.find(p=>p.slug===e.workflow||p.id===e.workflow);if(y)s=y.id,i=y.name;else {let p=await this.createWorkflow({name:e.workflow.replace(/[_-]/g," ").replace(/\b\w/g,x=>x.toUpperCase()),slug:e.workflow,productSurface:"AGENT"});s=p.id,i=p.name;}}catch{s=e.workflow;}let n=await this.startRun({customerId:e.customerId,workflowId:s,externalRunId:e.externalRunId,correlationId:e.correlationId,metadata:e.metadata}),o=0,u=0;if(e.events.length>0){let g=e.events.map((p,x)=>({runId:n.id,eventType:p.eventType,quantity:p.quantity,units:p.units,description:p.description,costUnits:p.costUnits,metadata:p.metadata,idempotencyKey:e.externalRunId?`${e.externalRunId}:${p.eventType}:${x}`:void 0})),y=await this.emitEventsBatch(g);o=y.created,u=y.duplicates;}let d=await this.endRun(n.id,{status:e.status,errorMessage:e.errorMessage,errorCode:e.errorCode}),m=Date.now()-r,a=e.events.length>0?`${o} events recorded`:"no events",l=`${e.status==="COMPLETED"?"\u2713":e.status==="FAILED"?"\u2717":"\u25CB"} ${i}: ${a} (${d.durationMs??m}ms)`;return {run:{id:n.id,workflowId:s,workflowName:i,status:e.status,durationMs:d.durationMs},events:{created:o,duplicates:u},totalCostUnits:d.totalCostUnits,summary:l}}static generateIdempotencyKey(e){let r=[e.customerId,e.runId??"no_run",e.stepName,String(e.sequence??0)],s=0,i=r.join("|");for(let n=0;n<i.length;n++){let o=i.charCodeAt(n);s=(s<<5)-s+o,s=s&s;}return `drip_${Math.abs(s).toString(36)}_${e.stepName.slice(0,16)}`}static async verifyWebhookSignature(e,r,s,i=300){if(!e||!r||!s)return false;try{let n=r.split(","),o=n.find(w=>w.startsWith("t=")),u=n.find(w=>w.startsWith("v1="));if(!o||!u)return !1;let d=parseInt(o.slice(2),10),m=u.slice(3);if(isNaN(d))return !1;let a=Math.floor(Date.now()/1e3);if(Math.abs(a-d)>i)return !1;let c=`${d}.${e}`,l=new TextEncoder,g=l.encode(s),y=l.encode(c),p=globalThis.crypto?.subtle??D("crypto").webcrypto.subtle,x=await p.importKey("raw",g,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),$=await p.sign("HMAC",x,y),v=Array.from(new Uint8Array($)).map(w=>w.toString(16).padStart(2,"0")).join("");if(m.length!==v.length)return !1;let A=0;for(let w=0;w<m.length;w++)A|=m.charCodeAt(w)^v.charCodeAt(w);return A===0}catch{return false}}static verifyWebhookSignatureSync(e,r,s,i=300){if(!e||!r||!s)return false;try{let n=r.split(","),o=n.find(x=>x.startsWith("t=")),u=n.find(x=>x.startsWith("v1="));if(!o||!u)return !1;let d=parseInt(o.slice(2),10),m=u.slice(3);if(isNaN(d))return !1;let a=Math.floor(Date.now()/1e3);if(Math.abs(a-d)>i)return !1;let c=D("crypto"),l=`${d}.${e}`,g=c.createHmac("sha256",s).update(l).digest("hex"),y=Buffer.from(m),p=Buffer.from(g);return y.length!==p.length?!1:c.timingSafeEqual(y,p)}catch{return false}}static generateWebhookSignature(e,r,s){let i=D("crypto"),n=s??Math.floor(Date.now()/1e3),o=`${n}.${e}`,u=i.createHmac("sha256",r).update(o).digest("hex");return `t=${n},v1=${u}`}createStreamMeter(e){return new C(this.charge.bind(this),e)}};var G=300,B=300,J=["x-payment-signature","x-payment-session-key","x-payment-smart-account","x-payment-timestamp","x-payment-amount","x-payment-recipient","x-payment-usage-id","x-payment-nonce"];function Y(t){return t.toLowerCase()}function h(t,e){let r=Y(e);if(t[r]!==void 0){let s=t[r];return Array.isArray(s)?s[0]:s}for(let[s,i]of Object.entries(t))if(s.toLowerCase()===r)return Array.isArray(i)?i[0]:i}function b(t){return J.every(e=>h(t,e)!==void 0)}function S(t){let e=h(t,"x-payment-signature"),r=h(t,"x-payment-session-key"),s=h(t,"x-payment-smart-account"),i=h(t,"x-payment-timestamp"),n=h(t,"x-payment-amount"),o=h(t,"x-payment-recipient"),u=h(t,"x-payment-usage-id"),d=h(t,"x-payment-nonce");if(!e||!r||!s||!i||!n||!o||!u||!d)return null;let m=parseInt(i,10);if(isNaN(m)||Math.floor(Date.now()/1e3)-m>B)return null;let c=(l,g)=>{if(!l.startsWith("0x"))return false;let y=l.slice(2);return y.length<g?false:/^[a-fA-F0-9]+$/.test(y)};return !c(e,130)||!c(r,64)||!c(s,40)?null:{signature:e,sessionKeyId:r,smartAccount:s,timestamp:m,amount:n,recipient:o,usageId:u,nonce:d}}function M(t){let e=Math.floor(Date.now()/1e3),r=e+(t.expiresInSec??G),s=`${e}-${crypto.randomBytes(16).toString("hex")}`,i=t.usageId;i.startsWith("0x")||(i=N(i));let n={"X-Payment-Required":"true","X-Payment-Amount":t.amount,"X-Payment-Recipient":t.recipient,"X-Payment-Usage-Id":i,"X-Payment-Description":t.description??"API usage charge","X-Payment-Expires":String(r),"X-Payment-Nonce":s,"X-Payment-Timestamp":String(e)},o={amount:t.amount,recipient:t.recipient,usageId:i,description:t.description??"API usage charge",expiresAt:r,nonce:s,timestamp:e};return {headers:n,paymentRequest:o}}function N(t){let e=5381,r=52711;for(let n=0;n<t.length;n++){let o=t.charCodeAt(n);e=(e<<5)+e^o,r=(r<<5)+r^o;}return `0x${Math.abs(e*31+r).toString(16).padStart(16,"0").slice(0,16).padEnd(64,"0")}`}async function _(t,e){let r=e.customerResolver??"header";if(typeof r=="function")return r(t);if(r==="header"){let s=h(t.headers,"x-drip-customer-id")??h(t.headers,"x-customer-id");if(!s)throw new f("Missing customer ID. Include X-Drip-Customer-Id header.","CUSTOMER_RESOLUTION_FAILED",400);return s}if(r==="query"){let s=t.query??{},i=s.drip_customer_id??s.customer_id,n=Array.isArray(i)?i[0]:i;if(!n)throw new f("Missing customer ID. Include drip_customer_id query parameter.","CUSTOMER_RESOLUTION_FAILED",400);return n}throw new f(`Invalid customer resolver: ${r}`,"CONFIGURATION_ERROR",500)}async function U(t,e){return typeof e.quantity=="function"?e.quantity(t):e.quantity}async function O(t,e,r){if(r.idempotencyKey)return r.idempotencyKey(t);let s=Date.now(),i=[t.method,t.url,e,s];return `drip_${N(i.join("|")).slice(2,18)}`}function k(t){let e=t.apiKey??process.env.DRIP_API_KEY;if(!e)throw new f("Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.","CONFIGURATION_ERROR",500);return new E({apiKey:e,baseUrl:t.baseUrl??process.env.DRIP_API_URL})}async function I(t,e){if(e.skipInDevelopment&&process.env.NODE_ENV==="development"){console.warn("[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.");let r=k(e),s={success:true,usageEventId:"dev_usage_event",isReplay:false,charge:{id:"dev_charge",amountUsdc:"0.00",amountToken:"0",txHash:"0x0",status:"CONFIRMED"}};return {success:true,state:{customerId:"dev_customer",quantity:typeof e.quantity=="number"?e.quantity:1,idempotencyKey:"dev_idempotency",hasPaymentProof:false},charge:s,drip:r,isReplay:false}}try{let r=k(e),s=await _(t,e),i=await U(t,e),n=await O(t,s,e),o=b(t.headers),u=o?S(t.headers):void 0,d={customerId:s,quantity:i,idempotencyKey:n,hasPaymentProof:o,paymentProof:u??void 0},m=typeof e.metadata=="function"?e.metadata(t):e.metadata;try{let a=await r.charge({customerId:s,meter:e.meter,quantity:i,idempotencyKey:n,metadata:m});return e.onCharge&&await e.onCharge(a,t),{success:!0,state:d,charge:a,drip:r,isReplay:a.isReplay??!1}}catch(a){if(a instanceof R){if(a.statusCode===402){let c=process.env.DRIP_RECIPIENT_ADDRESS;if(!c)throw new f("DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.","CONFIGURATION_ERROR",500);let l="0.01",g=a.message.match(/amount[:\s]+([0-9.]+)/i);g?l=g[1]:l=(i*1e-4).toFixed(6);let{headers:y,paymentRequest:p}=M({amount:l,recipient:c,usageId:n,description:`${e.meter} usage charge`});return {success:!1,error:new f("Insufficient balance. Payment required.","PAYMENT_REQUIRED",402),paymentRequired:{headers:y,paymentRequest:p}}}throw e.onError&&await e.onError(a,t),new f(a.message,"CHARGE_FAILED",a.statusCode,{code:a.code})}throw a}}catch(r){if(r instanceof f)return {success:false,error:r};let s=r instanceof Error?r.message:"Unknown error";return {success:false,error:new f(s,"INTERNAL_ERROR",500)}}}function T(t){let e={};return t.forEach((r,s)=>{e[s.toLowerCase()]=r;}),e}function Q(t){let e={};return t.forEach((r,s)=>{e[s]=r;}),e}function z(t,e,r,s){return Response.json({error:t,code:e,...s&&{details:s}},{status:r})}function V(t,e){let r=new Headers;return Object.entries(t).forEach(([s,i])=>{r.set(s,i);}),r.set("Content-Type","application/json"),new Response(JSON.stringify({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:e,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}}),{status:402,headers:r})}function W(t,e){return async(r,s)=>{let i={method:r.method,url:r.url,headers:T(r.headers),query:r.nextUrl?Q(r.nextUrl.searchParams):{}},n=typeof t.quantity=="function"?await t.quantity(r):t.quantity,o;if(typeof t.customerResolver=="function"){let l=t.customerResolver;o=async()=>l(r);}else o=t.customerResolver;let u;if(typeof t.idempotencyKey=="function"){let l=t.idempotencyKey;u=async()=>l(r);}let d=typeof t.metadata=="function"?t.metadata(r):t.metadata,m={meter:t.meter,quantity:n,apiKey:t.apiKey,baseUrl:t.baseUrl,customerResolver:o,idempotencyKey:u,metadata:d,skipInDevelopment:t.skipInDevelopment,onCharge:void 0,onError:void 0},a=await I(i,m);if(!a.success){if(t.errorResponse){let l=await t.errorResponse(a.error,r);if(l)return l}return a.paymentRequired?V(a.paymentRequired.headers,a.paymentRequired.paymentRequest):z(a.error.message,a.error.code,a.error.statusCode,a.error.details)}t.onCharge&&await t.onCharge(a.charge,r);let c={drip:a.drip,customerId:a.state.customerId,charge:a.charge,isReplay:a.isReplay,params:s?.params};try{return await e(r,c)}catch(l){throw l}}}function Z(t){return (e,r)=>W({...t,...e},r)}function ee(t){return b(T(t.headers))}function te(t,e){return h(T(t.headers),e)}function K(t){let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=Array.isArray(s)?s[0]:s;return e}function re(t,e,r){t.status(402).set(e).json({error:"Payment required",code:"PAYMENT_REQUIRED",paymentRequest:r,instructions:{step1:"Sign the payment request with your session key using EIP-712",step2:"Retry the request with X-Payment-* headers",documentation:"https://docs.drip.dev/x402"}});}function se(t,e,r,s,i){t.status(s).json({error:e,code:r,...i&&{details:i}});}function L(t){let e=t.attachToRequest??true;return async(r,s,i)=>{let n={method:r.method,url:r.originalUrl||r.url,headers:K(r.headers),query:r.query},o=typeof t.quantity=="function"?await t.quantity(r):t.quantity,u;if(typeof t.customerResolver=="function"){let g=t.customerResolver;u=async()=>g(r);}else u=t.customerResolver;let d;if(typeof t.idempotencyKey=="function"){let g=t.idempotencyKey;d=async()=>g(r);}let m=typeof t.metadata=="function"?t.metadata(r):t.metadata,a={meter:t.meter,quantity:o,apiKey:t.apiKey,baseUrl:t.baseUrl,customerResolver:u,idempotencyKey:d,metadata:m,skipInDevelopment:t.skipInDevelopment,onCharge:void 0,onError:void 0},c=await I(n,a);if(!c.success){if(t.errorHandler&&await t.errorHandler(c.error,r,s))return;if(c.paymentRequired){re(s,c.paymentRequired.headers,c.paymentRequired.paymentRequest);return}se(s,c.error.message,c.error.code,c.error.statusCode,c.error.details);return}t.onCharge&&await t.onCharge(c.charge,r);let l={drip:c.drip,customerId:c.state.customerId,charge:c.charge,isReplay:c.isReplay};e&&(r.drip=l),i();}}function ne(t){return e=>L({...t,...e})}function ie(t){return b(K(t.headers))}function H(t){return "drip"in t&&typeof t.drip=="object"}function oe(t){if(!H(t))throw new Error("Drip context not found on request. Ensure dripMiddleware is applied before this route.");return t.drip}
2
+ exports.Drip=E;exports.DripError=R;exports.DripMiddlewareError=f;exports.createDripClient=k;exports.createDripMiddleware=ne;exports.createWithDrip=Z;exports.dripMiddleware=L;exports.generateIdempotencyKey=O;exports.generatePaymentRequest=M;exports.getDripContext=oe;exports.getDripHeader=te;exports.getHeader=h;exports.hasDripContext=H;exports.hasExpressPaymentProof=ie;exports.hasNextPaymentProof=ee;exports.hasPaymentProof=b;exports.parsePaymentProof=S;exports.processRequest=I;exports.resolveCustomerId=_;exports.resolveQuantity=U;exports.withDrip=W;//# sourceMappingURL=middleware.cjs.map
3
3
  //# sourceMappingURL=middleware.cjs.map