@drip-sdk/node 1.0.8 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stream-meter.ts","../src/index.ts","../src/middleware/types.ts","../src/middleware/core.ts","../src/middleware/express.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","DripMiddlewareError","_DripMiddlewareError","details","DEFAULT_PAYMENT_EXPIRY_SEC","MAX_TIMESTAMP_AGE_SEC","REQUIRED_PAYMENT_HEADERS","normalizeHeaderName","name","getHeader","headers","normalized","value","key","hasPaymentProof","header","parsePaymentProof","sessionKeyId","smartAccount","timestampStr","amount","recipient","usageId","nonce","isValidHex","minLength","hex","generatePaymentRequest","expiresAt","randomBytes","hashString","paymentRequest","input","primaryHash","secondaryHash","resolveCustomerId","request","resolver","id","resolveQuantity","generateIdempotencyKey","createDripClient","apiKey","processRequest","drip","mockCharge","paymentProofPresent","paymentProof","state","metadata","amountMatch","normalizeHeaders","sendPaymentRequired","sendError","dripMiddleware","attachToRequest","req","next","genericRequest","resolvedQuantity","resolvedCustomerResolver","originalResolver","resolvedIdempotencyKey","originalIdempotencyKey","resolvedMetadata","genericConfig","dripContext","createDripMiddleware","defaults","hasPaymentProofHeaders","hasDripContext","getDripContext"],"mappings":"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","file":"express.cjs","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","/**\n * Drip Middleware Types\n *\n * Shared type definitions for the withDrip wrapper and framework-specific adapters.\n * These types ensure consistent behavior across Next.js, Express, and other frameworks.\n */\n\nimport type { Drip, Customer, ChargeResult, DripError } from '../index.js';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for the withDrip middleware.\n */\nexport interface WithDripConfig<TRequest = unknown> {\n /**\n * The usage meter to charge against.\n * Must match a meter configured in your Drip pricing plan.\n * @example \"api_calls\", \"compute_seconds\", \"tokens\"\n */\n meter: string;\n\n /**\n * The quantity to charge. Can be a static number or a function\n * that calculates quantity based on the request.\n * @example 1\n * @example (req) => req.body.tokens.length\n */\n quantity: number | ((request: TRequest) => number | Promise<number>);\n\n /**\n * API key for Drip. Defaults to DRIP_API_KEY environment variable.\n */\n apiKey?: string;\n\n /**\n * Base URL for Drip API. Defaults to DRIP_API_URL or production.\n */\n baseUrl?: string;\n\n /**\n * How to identify the customer from the request.\n * - 'header': Look for X-Drip-Customer-Id header\n * - 'query': Look for drip_customer_id query parameter\n * - function: Custom extraction logic\n * @default 'header'\n */\n customerResolver?:\n | 'header'\n | 'query'\n | ((request: TRequest) => string | Promise<string>);\n\n /**\n * Custom idempotency key generator.\n * By default, generates from request method, path, and customer ID.\n */\n idempotencyKey?: (request: TRequest) => string | Promise<string>;\n\n /**\n * Custom error handler for Drip errors.\n * Return a response to override default error handling.\n */\n onError?: (error: DripError, request: TRequest) => unknown | Promise<unknown>;\n\n /**\n * Called after successful charge. Useful for logging/metrics.\n */\n onCharge?: (charge: ChargeResult, request: TRequest) => void | Promise<void>;\n\n /**\n * Whether to skip charging in development/test environments.\n * @default false\n */\n skipInDevelopment?: boolean;\n\n /**\n * Custom metadata to attach to each charge.\n */\n metadata?: Record<string, unknown> | ((request: TRequest) => Record<string, unknown>);\n}\n\n// ============================================================================\n// Context Types\n// ============================================================================\n\n/**\n * Context passed to the wrapped handler after payment verification.\n */\nexport interface DripContext {\n /**\n * The Drip SDK client instance.\n */\n drip: Drip;\n\n /**\n * The resolved customer ID.\n */\n customerId: string;\n\n /**\n * The charge result from this request.\n */\n charge: ChargeResult;\n\n /**\n * Whether this was a replayed request (idempotency key matched).\n */\n isReplay: boolean;\n}\n\n// ============================================================================\n// x402 Payment Types\n// ============================================================================\n\n/**\n * x402 payment proof extracted from request headers.\n */\nexport interface X402PaymentProof {\n signature: string;\n sessionKeyId: string;\n smartAccount: string;\n timestamp: number;\n amount: string;\n recipient: string;\n usageId: string;\n nonce: string;\n}\n\n/**\n * x402 payment request returned in 402 responses.\n */\nexport interface X402PaymentRequest {\n amount: string;\n recipient: string;\n usageId: string;\n description: string;\n expiresAt: number;\n nonce: string;\n timestamp: number;\n}\n\n/**\n * Headers to include in 402 Payment Required responses.\n */\nexport type X402ResponseHeaders = {\n 'X-Payment-Required': 'true';\n 'X-Payment-Amount': string;\n 'X-Payment-Recipient': string;\n 'X-Payment-Usage-Id': string;\n 'X-Payment-Description': string;\n 'X-Payment-Expires': string;\n 'X-Payment-Nonce': string;\n 'X-Payment-Timestamp': string;\n} & Record<string, string>;\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\n/**\n * Result of customer resolution.\n */\nexport interface CustomerResolutionResult {\n success: boolean;\n customerId?: string;\n error?: string;\n}\n\n/**\n * Result of balance check.\n */\nexport interface BalanceCheckResult {\n sufficient: boolean;\n balance?: string;\n required?: string;\n shortfall?: string;\n}\n\n/**\n * Internal state for middleware processing.\n */\nexport interface MiddlewareState {\n customerId: string;\n quantity: number;\n idempotencyKey: string;\n hasPaymentProof: boolean;\n paymentProof?: X402PaymentProof;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Error codes specific to the withDrip middleware.\n */\nexport type DripMiddlewareErrorCode =\n | 'CUSTOMER_NOT_FOUND'\n | 'CUSTOMER_RESOLUTION_FAILED'\n | 'PAYMENT_REQUIRED'\n | 'PAYMENT_VERIFICATION_FAILED'\n | 'CHARGE_FAILED'\n | 'CONFIGURATION_ERROR'\n | 'INTERNAL_ERROR';\n\n/**\n * Error thrown by the withDrip middleware.\n */\nexport class DripMiddlewareError extends Error {\n constructor(\n message: string,\n public readonly code: DripMiddlewareErrorCode,\n public readonly statusCode: number,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'DripMiddlewareError';\n Object.setPrototypeOf(this, DripMiddlewareError.prototype);\n }\n}\n\n// ============================================================================\n// Framework Adapter Types\n// ============================================================================\n\n/**\n * Generic request interface that frameworks must implement.\n */\nexport interface GenericRequest {\n method: string;\n url: string;\n headers: Record<string, string | string[] | undefined>;\n query?: Record<string, string | string[] | undefined>;\n}\n\n/**\n * Generic response builder for framework adapters.\n */\nexport interface ResponseBuilder {\n status(code: number): this;\n header(name: string, value: string): this;\n json(body: unknown): unknown;\n}\n","/**\n * Drip Middleware Core\n *\n * Framework-agnostic core logic for the withDrip wrapper.\n * This module handles customer resolution, balance checks, charging,\n * and x402 payment flow orchestration.\n */\n\nimport { randomBytes } from 'crypto';\nimport { Drip, DripError, type ChargeResult } from '../index.js';\nimport type {\n WithDripConfig,\n X402PaymentProof,\n X402PaymentRequest,\n X402ResponseHeaders,\n MiddlewareState,\n GenericRequest,\n} from './types.js';\nimport { DripMiddlewareError } from './types.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_PAYMENT_EXPIRY_SEC = 5 * 60; // 5 minutes\nconst MAX_TIMESTAMP_AGE_SEC = 5 * 60; // 5 minutes max age for payment proof timestamps\n\n// Required headers for x402 payment proof\nconst REQUIRED_PAYMENT_HEADERS = [\n 'x-payment-signature',\n 'x-payment-session-key',\n 'x-payment-smart-account',\n 'x-payment-timestamp',\n 'x-payment-amount',\n 'x-payment-recipient',\n 'x-payment-usage-id',\n 'x-payment-nonce',\n] as const;\n\n// ============================================================================\n// Header Utilities\n// ============================================================================\n\n/**\n * Normalize header name to lowercase.\n */\nfunction normalizeHeaderName(name: string): string {\n return name.toLowerCase();\n}\n\n/**\n * Get a header value from a request, handling case-insensitivity.\n */\nexport function getHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string,\n): string | undefined {\n const normalized = normalizeHeaderName(name);\n\n // Try exact match first\n if (headers[normalized] !== undefined) {\n const value = headers[normalized];\n return Array.isArray(value) ? value[0] : value;\n }\n\n // Fall back to case-insensitive search\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === normalized) {\n return Array.isArray(value) ? value[0] : value;\n }\n }\n\n return undefined;\n}\n\n// ============================================================================\n// x402 Payment Proof Parsing\n// ============================================================================\n\n/**\n * Check if a request contains x402 payment proof headers.\n */\nexport function hasPaymentProof(\n headers: Record<string, string | string[] | undefined>,\n): boolean {\n return REQUIRED_PAYMENT_HEADERS.every(\n (header) => getHeader(headers, header) !== undefined,\n );\n}\n\n/**\n * Parse x402 payment proof from request headers.\n * Returns null if headers are missing or invalid.\n */\nexport function parsePaymentProof(\n headers: Record<string, string | string[] | undefined>,\n): X402PaymentProof | null {\n const signature = getHeader(headers, 'x-payment-signature');\n const sessionKeyId = getHeader(headers, 'x-payment-session-key');\n const smartAccount = getHeader(headers, 'x-payment-smart-account');\n const timestampStr = getHeader(headers, 'x-payment-timestamp');\n const amount = getHeader(headers, 'x-payment-amount');\n const recipient = getHeader(headers, 'x-payment-recipient');\n const usageId = getHeader(headers, 'x-payment-usage-id');\n const nonce = getHeader(headers, 'x-payment-nonce');\n\n // All headers required\n if (\n !signature ||\n !sessionKeyId ||\n !smartAccount ||\n !timestampStr ||\n !amount ||\n !recipient ||\n !usageId ||\n !nonce\n ) {\n return null;\n }\n\n const timestamp = parseInt(timestampStr, 10);\n if (isNaN(timestamp)) {\n return null;\n }\n\n // Validate timestamp freshness (prevent replay attacks)\n const now = Math.floor(Date.now() / 1000);\n if (now - timestamp > MAX_TIMESTAMP_AGE_SEC) {\n return null; // Timestamp too old\n }\n\n // Validate hex format for blockchain addresses/signatures\n // Must start with 0x and contain only valid hex characters\n const isValidHex = (value: string, minLength: number): boolean => {\n if (!value.startsWith('0x')) return false;\n const hex = value.slice(2);\n if (hex.length < minLength) return false;\n return /^[a-fA-F0-9]+$/.test(hex);\n };\n\n // Signature must be at least 65 bytes (130 hex chars) for ECDSA\n // Session key and smart account are 32 and 20 bytes respectively\n if (\n !isValidHex(signature, 130) ||\n !isValidHex(sessionKeyId, 64) ||\n !isValidHex(smartAccount, 40)\n ) {\n return null;\n }\n\n return {\n signature,\n sessionKeyId,\n smartAccount,\n timestamp,\n amount,\n recipient,\n usageId,\n nonce,\n };\n}\n\n// ============================================================================\n// x402 Response Generation\n// ============================================================================\n\n/**\n * Generate x402 payment request headers for 402 responses.\n */\nexport function generatePaymentRequest(params: {\n amount: string;\n recipient: string;\n usageId: string;\n description?: string;\n expiresInSec?: number;\n}): {\n headers: X402ResponseHeaders;\n paymentRequest: X402PaymentRequest;\n} {\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (params.expiresInSec ?? DEFAULT_PAYMENT_EXPIRY_SEC);\n // Use cryptographically secure random bytes for nonce generation\n const nonce = `${now}-${randomBytes(16).toString('hex')}`;\n\n // Ensure usageId is properly formatted\n let usageId = params.usageId;\n if (!usageId.startsWith('0x')) {\n // Hash the string to get a bytes32\n usageId = hashString(usageId);\n }\n\n const headers: X402ResponseHeaders = {\n 'X-Payment-Required': 'true',\n 'X-Payment-Amount': params.amount,\n 'X-Payment-Recipient': params.recipient,\n 'X-Payment-Usage-Id': usageId,\n 'X-Payment-Description': params.description ?? 'API usage charge',\n 'X-Payment-Expires': String(expiresAt),\n 'X-Payment-Nonce': nonce,\n 'X-Payment-Timestamp': String(now),\n };\n\n const paymentRequest: X402PaymentRequest = {\n amount: params.amount,\n recipient: params.recipient,\n usageId,\n description: params.description ?? 'API usage charge',\n expiresAt,\n nonce,\n timestamp: now,\n };\n\n return { headers, paymentRequest };\n}\n\n/**\n * Simple string hash for usageId generation.\n * Uses djb2 hash algorithm for better distribution.\n * In production, the server will use keccak256.\n */\nfunction hashString(input: string): string {\n let primaryHash = 5381;\n let secondaryHash = 52711;\n\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n primaryHash = ((primaryHash << 5) + primaryHash) ^ char;\n secondaryHash = ((secondaryHash << 5) + secondaryHash) ^ char;\n }\n\n // Combine both hashes for more uniqueness\n const combined = Math.abs(primaryHash * 31 + secondaryHash);\n // Convert to hex and pad to 64 chars (for bytes32)\n const hex = combined.toString(16).padStart(16, '0').slice(0, 16);\n return `0x${hex.padEnd(64, '0')}`;\n}\n\n// ============================================================================\n// Customer Resolution\n// ============================================================================\n\n/**\n * Resolve customer ID from a request based on configuration.\n */\nexport async function resolveCustomerId<TRequest extends GenericRequest>(\n request: TRequest,\n config: WithDripConfig<TRequest>,\n): Promise<string> {\n const resolver = config.customerResolver ?? 'header';\n\n if (typeof resolver === 'function') {\n return resolver(request);\n }\n\n if (resolver === 'header') {\n const customerId =\n getHeader(request.headers, 'x-drip-customer-id') ??\n getHeader(request.headers, 'x-customer-id');\n\n if (!customerId) {\n throw new DripMiddlewareError(\n 'Missing customer ID. Include X-Drip-Customer-Id header.',\n 'CUSTOMER_RESOLUTION_FAILED',\n 400,\n );\n }\n\n return customerId;\n }\n\n if (resolver === 'query') {\n const query = request.query ?? {};\n const customerId = query['drip_customer_id'] ?? query['customer_id'];\n const id = Array.isArray(customerId) ? customerId[0] : customerId;\n\n if (!id) {\n throw new DripMiddlewareError(\n 'Missing customer ID. Include drip_customer_id query parameter.',\n 'CUSTOMER_RESOLUTION_FAILED',\n 400,\n );\n }\n\n return id;\n }\n\n throw new DripMiddlewareError(\n `Invalid customer resolver: ${resolver as string}`,\n 'CONFIGURATION_ERROR',\n 500,\n );\n}\n\n/**\n * Resolve quantity from configuration.\n */\nexport async function resolveQuantity<TRequest>(\n request: TRequest,\n config: WithDripConfig<TRequest>,\n): Promise<number> {\n if (typeof config.quantity === 'function') {\n return config.quantity(request);\n }\n return config.quantity;\n}\n\n/**\n * Generate idempotency key for a request.\n */\nexport async function generateIdempotencyKey<TRequest extends GenericRequest>(\n request: TRequest,\n customerId: string,\n config: WithDripConfig<TRequest>,\n): Promise<string> {\n if (config.idempotencyKey) {\n return config.idempotencyKey(request);\n }\n\n // Default: hash of method + url + customer + timestamp (millisecond precision)\n // Using milliseconds ensures each request gets a unique key by default\n const timestamp = Date.now();\n const components = [request.method, request.url, customerId, timestamp];\n return `drip_${hashString(components.join('|')).slice(2, 18)}`;\n}\n\n// ============================================================================\n// Drip Client Factory\n// ============================================================================\n\n/**\n * Create a Drip client from configuration.\n */\nexport function createDripClient<TRequest>(\n config: WithDripConfig<TRequest>,\n): Drip {\n const apiKey = config.apiKey ?? process.env.DRIP_API_KEY;\n\n if (!apiKey) {\n throw new DripMiddlewareError(\n 'Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.',\n 'CONFIGURATION_ERROR',\n 500,\n );\n }\n\n return new Drip({\n apiKey,\n baseUrl: config.baseUrl ?? process.env.DRIP_API_URL,\n });\n}\n\n// ============================================================================\n// Core Middleware Logic\n// ============================================================================\n\n/**\n * Process a request through the Drip billing flow.\n *\n * This is the core logic used by all framework adapters.\n * It handles:\n * 1. Customer resolution\n * 2. Balance checking (if no payment proof)\n * 3. Charging the customer\n * 4. x402 payment flow orchestration\n */\n/**\n * Success result from processRequest.\n */\nexport interface ProcessRequestSuccess {\n success: true;\n state: MiddlewareState;\n charge: ChargeResult;\n drip: Drip;\n isReplay: boolean;\n}\n\n/**\n * Failure result from processRequest.\n */\nexport interface ProcessRequestFailure {\n success: false;\n error: DripMiddlewareError;\n paymentRequired?: {\n headers: X402ResponseHeaders;\n paymentRequest: X402PaymentRequest;\n };\n}\n\nexport type ProcessRequestResult = ProcessRequestSuccess | ProcessRequestFailure;\n\nexport async function processRequest<TRequest extends GenericRequest>(\n request: TRequest,\n config: WithDripConfig<TRequest>,\n): Promise<ProcessRequestResult> {\n // Check if we should skip in development\n if (config.skipInDevelopment && process.env.NODE_ENV === 'development') {\n // Warn that billing is being skipped in development\n console.warn(\n '[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.',\n );\n // Return a mock successful charge for development\n const drip = createDripClient(config);\n const mockCharge: ChargeResult = {\n success: true,\n usageEventId: 'dev_usage_event',\n isReplay: false,\n charge: {\n id: 'dev_charge',\n amountUsdc: '0.00',\n amountToken: '0',\n txHash: '0x0',\n status: 'CONFIRMED' as const,\n },\n };\n return {\n success: true,\n state: {\n customerId: 'dev_customer',\n quantity: typeof config.quantity === 'number' ? config.quantity : 1,\n idempotencyKey: 'dev_idempotency',\n hasPaymentProof: false,\n },\n charge: mockCharge,\n drip,\n isReplay: false,\n };\n }\n\n try {\n // Create Drip client\n const drip = createDripClient(config);\n\n // Resolve customer and quantity\n const customerId = await resolveCustomerId(request, config);\n const quantity = await resolveQuantity(request, config);\n const idempotencyKey = await generateIdempotencyKey(request, customerId, config);\n\n // Check for payment proof\n const paymentProofPresent = hasPaymentProof(request.headers);\n const paymentProof = paymentProofPresent\n ? parsePaymentProof(request.headers)\n : undefined;\n\n const state: MiddlewareState = {\n customerId,\n quantity,\n idempotencyKey,\n hasPaymentProof: paymentProofPresent,\n paymentProof: paymentProof ?? undefined,\n };\n\n // Resolve metadata\n const metadata = typeof config.metadata === 'function'\n ? config.metadata(request)\n : config.metadata;\n\n // Attempt to charge\n try {\n const chargeResult = await drip.charge({\n customerId,\n meter: config.meter,\n quantity,\n idempotencyKey,\n metadata,\n });\n\n // Call onCharge callback if provided\n if (config.onCharge) {\n await config.onCharge(chargeResult, request);\n }\n\n return {\n success: true,\n state,\n charge: chargeResult,\n drip,\n isReplay: chargeResult.isReplay ?? false,\n };\n } catch (error) {\n if (error instanceof DripError) {\n // Handle 402 Payment Required\n if (error.statusCode === 402) {\n // Require DRIP_RECIPIENT_ADDRESS to be configured\n const recipient = process.env.DRIP_RECIPIENT_ADDRESS;\n if (!recipient) {\n throw new DripMiddlewareError(\n 'DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.',\n 'CONFIGURATION_ERROR',\n 500,\n );\n }\n\n // Try to extract amount from error message (format: \"... amount: X.XX USDC\")\n // or use quantity as fallback (1 unit = 0.01 USDC default)\n let amount = '0.01';\n const amountMatch = error.message.match(/amount[:\\s]+([0-9.]+)/i);\n if (amountMatch) {\n amount = amountMatch[1];\n } else {\n // Calculate based on quantity with default rate of 0.0001 USDC per unit\n const calculatedAmount = (quantity * 0.0001).toFixed(6);\n amount = calculatedAmount;\n }\n\n // Generate payment request for x402 flow\n const { headers, paymentRequest } = generatePaymentRequest({\n amount,\n recipient,\n usageId: idempotencyKey,\n description: `${config.meter} usage charge`,\n });\n\n return {\n success: false,\n error: new DripMiddlewareError(\n 'Insufficient balance. Payment required.',\n 'PAYMENT_REQUIRED',\n 402,\n ),\n paymentRequired: { headers, paymentRequest },\n };\n }\n\n // Handle other Drip errors\n if (config.onError) {\n await config.onError(error, request);\n }\n\n throw new DripMiddlewareError(\n error.message,\n 'CHARGE_FAILED',\n error.statusCode,\n { code: error.code },\n );\n }\n\n throw error;\n }\n } catch (error) {\n if (error instanceof DripMiddlewareError) {\n return { success: false, error };\n }\n\n // Wrap unexpected errors\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n error: new DripMiddlewareError(message, 'INTERNAL_ERROR', 500),\n };\n }\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport type { WithDripConfig, X402PaymentProof, X402PaymentRequest };\n","/**\n * Drip Express Adapter\n *\n * Provides the `dripMiddleware` for Express.js applications.\n * Handles the complete x402 payment flow automatically.\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { dripMiddleware } from '@drip-sdk/node/express';\n *\n * const app = express();\n *\n * // Apply to specific routes\n * app.use('/api/paid', dripMiddleware({\n * meter: 'api_calls',\n * quantity: 1,\n * }));\n *\n * app.post('/api/paid/generate', (req, res) => {\n * // Payment already verified - req.drip contains context\n * console.log(`Charged: ${req.drip.charge.charge.amountUsdc} USDC`);\n * res.json({ success: true });\n * });\n * ```\n */\n\nimport type { Drip } from '../index.js';\nimport type {\n WithDripConfig,\n DripContext,\n X402ResponseHeaders,\n GenericRequest,\n} from './types.js';\nimport { DripMiddlewareError } from './types.js';\nimport {\n processRequest,\n hasPaymentProof,\n} from './core.js';\n\n// ============================================================================\n// Express Types\n// ============================================================================\n\n/**\n * Express request type.\n * We use a minimal interface to avoid requiring express as a dependency.\n */\nexport interface ExpressRequest {\n method: string;\n url: string;\n originalUrl: string;\n path: string;\n headers: Record<string, string | string[] | undefined>;\n query: Record<string, string | string[] | undefined>;\n params: Record<string, string>;\n body?: unknown;\n}\n\n/**\n * Express response type.\n */\nexport interface ExpressResponse {\n status(code: number): ExpressResponse;\n set(headers: Record<string, string>): ExpressResponse;\n json(body: unknown): void;\n send(body: unknown): void;\n}\n\n/**\n * Express next function.\n */\nexport type ExpressNextFunction = (error?: unknown) => void;\n\n/**\n * Express middleware type.\n */\nexport type ExpressMiddleware = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: ExpressNextFunction,\n) => void | Promise<void>;\n\n/**\n * Extended Express request with Drip context.\n */\nexport interface DripExpressRequest extends ExpressRequest {\n drip: DripContext;\n}\n\n/**\n * Configuration specific to Express adapter.\n */\nexport interface ExpressDripConfig extends WithDripConfig<ExpressRequest> {\n /**\n * Custom error handler.\n * Return true to indicate the error was handled.\n */\n errorHandler?: (\n error: DripMiddlewareError,\n req: ExpressRequest,\n res: ExpressResponse,\n ) => boolean | Promise<boolean>;\n\n /**\n * Whether to attach the Drip context to the request object.\n * @default true\n */\n attachToRequest?: boolean;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Normalize Express headers to a consistent format.\n */\nfunction normalizeHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n return result;\n}\n\n/**\n * Send a 402 Payment Required response.\n */\nfunction sendPaymentRequired(\n res: ExpressResponse,\n headers: X402ResponseHeaders,\n paymentRequest: {\n amount: string;\n recipient: string;\n usageId: string;\n description: string;\n expiresAt: number;\n nonce: string;\n timestamp: number;\n },\n): void {\n res.status(402).set(headers as Record<string, string>).json({\n error: 'Payment required',\n code: 'PAYMENT_REQUIRED',\n paymentRequest,\n instructions: {\n step1: 'Sign the payment request with your session key using EIP-712',\n step2: 'Retry the request with X-Payment-* headers',\n documentation: 'https://docs.drip.dev/x402',\n },\n });\n}\n\n/**\n * Send an error response.\n */\nfunction sendError(\n res: ExpressResponse,\n message: string,\n code: string,\n status: number,\n details?: Record<string, unknown>,\n): void {\n res.status(status).json({\n error: message,\n code,\n ...(details && { details }),\n });\n}\n\n// ============================================================================\n// Main Middleware\n// ============================================================================\n\n/**\n * Express middleware for Drip billing.\n *\n * This middleware:\n * 1. Resolves the customer ID from headers or query\n * 2. Checks customer balance\n * 3. If insufficient, returns 402 with x402 payment headers\n * 4. If payment proof provided, verifies and processes\n * 5. Charges the customer\n * 6. Attaches Drip context to req.drip\n * 7. Calls next() on success\n *\n * @param config - Configuration for billing\n * @returns Express middleware\n *\n * @example\n * ```typescript\n * // Apply to all routes under /api/paid\n * app.use('/api/paid', dripMiddleware({\n * meter: 'api_calls',\n * quantity: 1,\n * }));\n *\n * // Or with dynamic quantity\n * app.use('/api/ai', dripMiddleware({\n * meter: 'tokens',\n * quantity: (req) => req.body?.maxTokens ?? 100,\n * }));\n * ```\n */\nexport function dripMiddleware(config: ExpressDripConfig): ExpressMiddleware {\n const attachToRequest = config.attachToRequest ?? true;\n\n return async (req, res, next) => {\n // Convert Express request to generic format\n const genericRequest = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: normalizeHeaders(req.headers),\n query: req.query as Record<string, string | undefined>,\n };\n\n // Resolve quantity if it's a function (needs access to original request)\n const resolvedQuantity = typeof config.quantity === 'function'\n ? await config.quantity(req)\n : config.quantity;\n\n // Resolve customer ID if it's a function - wrap to use generic request\n let resolvedCustomerResolver: 'header' | 'query' | ((r: GenericRequest) => string | Promise<string>) | undefined;\n if (typeof config.customerResolver === 'function') {\n // Capture the original resolver and call it with the original request\n const originalResolver = config.customerResolver;\n resolvedCustomerResolver = async () => originalResolver(req);\n } else {\n resolvedCustomerResolver = config.customerResolver;\n }\n\n // Resolve idempotencyKey if it's a function\n let resolvedIdempotencyKey: ((r: GenericRequest) => string | Promise<string>) | undefined;\n if (typeof config.idempotencyKey === 'function') {\n const originalIdempotencyKey = config.idempotencyKey;\n resolvedIdempotencyKey = async () => originalIdempotencyKey(req);\n }\n\n // Resolve metadata if it's a function\n const resolvedMetadata = typeof config.metadata === 'function'\n ? config.metadata(req)\n : config.metadata;\n\n // Create a generic config for processRequest\n const genericConfig: WithDripConfig<typeof genericRequest> = {\n meter: config.meter,\n quantity: resolvedQuantity,\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n customerResolver: resolvedCustomerResolver,\n idempotencyKey: resolvedIdempotencyKey,\n metadata: resolvedMetadata,\n skipInDevelopment: config.skipInDevelopment,\n // Clear callbacks that need the original request type\n onCharge: undefined,\n onError: undefined,\n };\n\n // Process the request through Drip billing\n const result = await processRequest(genericRequest, genericConfig);\n\n if (!result.success) {\n // Handle custom error handler\n if (config.errorHandler) {\n const handled = await config.errorHandler(result.error, req, res);\n if (handled) {\n return;\n }\n }\n\n // Handle 402 Payment Required\n if (result.paymentRequired) {\n sendPaymentRequired(\n res,\n result.paymentRequired.headers,\n result.paymentRequired.paymentRequest,\n );\n return;\n }\n\n // Send error response\n sendError(\n res,\n result.error.message,\n result.error.code,\n result.error.statusCode,\n result.error.details,\n );\n return;\n }\n\n // Call original onCharge callback if provided\n if (config.onCharge) {\n await config.onCharge(result.charge, req);\n }\n\n // Build context\n const dripContext: DripContext = {\n drip: result.drip,\n customerId: result.state.customerId,\n charge: result.charge,\n isReplay: result.isReplay,\n };\n\n // Attach to request if configured\n if (attachToRequest) {\n (req as DripExpressRequest).drip = dripContext;\n }\n\n // Continue to next middleware/handler\n next();\n };\n}\n\n// ============================================================================\n// Convenience Exports\n// ============================================================================\n\n/**\n * Create a dripMiddleware factory with default configuration.\n * Useful for consistent settings across multiple route groups.\n *\n * @example\n * ```typescript\n * // lib/drip.ts\n * import { createDripMiddleware } from '@drip-sdk/node/express';\n *\n * export const drip = createDripMiddleware({\n * apiKey: process.env.DRIP_API_KEY,\n * baseUrl: process.env.DRIP_API_URL,\n * });\n *\n * // routes/api.ts\n * import { drip } from '../lib/drip';\n *\n * router.use('/paid', drip({ meter: 'api_calls', quantity: 1 }));\n * ```\n */\nexport function createDripMiddleware(\n defaults: Partial<Omit<ExpressDripConfig, 'meter' | 'quantity'>>,\n): (\n config: Pick<ExpressDripConfig, 'meter' | 'quantity'> & Partial<Omit<ExpressDripConfig, 'meter' | 'quantity'>>,\n) => ExpressMiddleware {\n return (config) => {\n return dripMiddleware({ ...defaults, ...config } as ExpressDripConfig);\n };\n}\n\n/**\n * Check if an Express request has x402 payment proof headers.\n * Useful for conditional logic in routes.\n */\nexport function hasPaymentProofHeaders(req: ExpressRequest): boolean {\n return hasPaymentProof(normalizeHeaders(req.headers));\n}\n\n/**\n * Type guard to check if request has Drip context attached.\n */\nexport function hasDripContext(\n req: ExpressRequest,\n): req is DripExpressRequest {\n return 'drip' in req && typeof (req as DripExpressRequest).drip === 'object';\n}\n\n/**\n * Get Drip context from request, throwing if not present.\n */\nexport function getDripContext(req: ExpressRequest): DripContext {\n if (!hasDripContext(req)) {\n throw new Error(\n 'Drip context not found on request. Ensure dripMiddleware is applied before this route.',\n );\n }\n return req.drip;\n}\n"]}
1
+ {"version":3,"sources":["../src/stream-meter.ts","../src/index.ts","../src/middleware/types.ts","../src/middleware/core.ts","../src/middleware/express.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","crypto","sigBuffer","expectedBuffer","ts","DripMiddlewareError","_DripMiddlewareError","details","DEFAULT_PAYMENT_EXPIRY_SEC","MAX_TIMESTAMP_AGE_SEC","REQUIRED_PAYMENT_HEADERS","normalizeHeaderName","name","getHeader","headers","normalized","value","key","hasPaymentProof","header","parsePaymentProof","sessionKeyId","smartAccount","timestampStr","amount","recipient","usageId","nonce","isValidHex","minLength","hex","generatePaymentRequest","expiresAt","randomBytes","hashString","paymentRequest","input","primaryHash","secondaryHash","resolveCustomerId","request","resolver","id","resolveQuantity","generateIdempotencyKey","createDripClient","apiKey","processRequest","drip","mockCharge","paymentProofPresent","paymentProof","state","metadata","amountMatch","normalizeHeaders","sendPaymentRequired","sendError","dripMiddleware","attachToRequest","req","next","genericRequest","resolvedQuantity","resolvedCustomerResolver","originalResolver","resolvedIdempotencyKey","originalIdempotencyKey","resolvedMetadata","genericConfig","dripContext","createDripMiddleware","defaults","hasPaymentProofHeaders","hasDripContext","getDripContext"],"mappings":"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","file":"express.cjs","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","/**\n * Drip Middleware Types\n *\n * Shared type definitions for the withDrip wrapper and framework-specific adapters.\n * These types ensure consistent behavior across Next.js, Express, and other frameworks.\n */\n\nimport type { Drip, Customer, ChargeResult, DripError } from '../index.js';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration for the withDrip middleware.\n */\nexport interface WithDripConfig<TRequest = unknown> {\n /**\n * The usage meter to charge against.\n * Must match a meter configured in your Drip pricing plan.\n * @example \"api_calls\", \"compute_seconds\", \"tokens\"\n */\n meter: string;\n\n /**\n * The quantity to charge. Can be a static number or a function\n * that calculates quantity based on the request.\n * @example 1\n * @example (req) => req.body.tokens.length\n */\n quantity: number | ((request: TRequest) => number | Promise<number>);\n\n /**\n * API key for Drip. Defaults to DRIP_API_KEY environment variable.\n */\n apiKey?: string;\n\n /**\n * Base URL for Drip API. Defaults to DRIP_API_URL or production.\n */\n baseUrl?: string;\n\n /**\n * How to identify the customer from the request.\n * - 'header': Look for X-Drip-Customer-Id header\n * - 'query': Look for drip_customer_id query parameter\n * - function: Custom extraction logic\n * @default 'header'\n */\n customerResolver?:\n | 'header'\n | 'query'\n | ((request: TRequest) => string | Promise<string>);\n\n /**\n * Custom idempotency key generator.\n * By default, generates from request method, path, and customer ID.\n */\n idempotencyKey?: (request: TRequest) => string | Promise<string>;\n\n /**\n * Custom error handler for Drip errors.\n * Return a response to override default error handling.\n */\n onError?: (error: DripError, request: TRequest) => unknown | Promise<unknown>;\n\n /**\n * Called after successful charge. Useful for logging/metrics.\n */\n onCharge?: (charge: ChargeResult, request: TRequest) => void | Promise<void>;\n\n /**\n * Whether to skip charging in development/test environments.\n * @default false\n */\n skipInDevelopment?: boolean;\n\n /**\n * Custom metadata to attach to each charge.\n */\n metadata?: Record<string, unknown> | ((request: TRequest) => Record<string, unknown>);\n}\n\n// ============================================================================\n// Context Types\n// ============================================================================\n\n/**\n * Context passed to the wrapped handler after payment verification.\n */\nexport interface DripContext {\n /**\n * The Drip SDK client instance.\n */\n drip: Drip;\n\n /**\n * The resolved customer ID.\n */\n customerId: string;\n\n /**\n * The charge result from this request.\n */\n charge: ChargeResult;\n\n /**\n * Whether this was a replayed request (idempotency key matched).\n */\n isReplay: boolean;\n}\n\n// ============================================================================\n// x402 Payment Types\n// ============================================================================\n\n/**\n * x402 payment proof extracted from request headers.\n */\nexport interface X402PaymentProof {\n signature: string;\n sessionKeyId: string;\n smartAccount: string;\n timestamp: number;\n amount: string;\n recipient: string;\n usageId: string;\n nonce: string;\n}\n\n/**\n * x402 payment request returned in 402 responses.\n */\nexport interface X402PaymentRequest {\n amount: string;\n recipient: string;\n usageId: string;\n description: string;\n expiresAt: number;\n nonce: string;\n timestamp: number;\n}\n\n/**\n * Headers to include in 402 Payment Required responses.\n */\nexport type X402ResponseHeaders = {\n 'X-Payment-Required': 'true';\n 'X-Payment-Amount': string;\n 'X-Payment-Recipient': string;\n 'X-Payment-Usage-Id': string;\n 'X-Payment-Description': string;\n 'X-Payment-Expires': string;\n 'X-Payment-Nonce': string;\n 'X-Payment-Timestamp': string;\n} & Record<string, string>;\n\n// ============================================================================\n// Result Types\n// ============================================================================\n\n/**\n * Result of customer resolution.\n */\nexport interface CustomerResolutionResult {\n success: boolean;\n customerId?: string;\n error?: string;\n}\n\n/**\n * Result of balance check.\n */\nexport interface BalanceCheckResult {\n sufficient: boolean;\n balance?: string;\n required?: string;\n shortfall?: string;\n}\n\n/**\n * Internal state for middleware processing.\n */\nexport interface MiddlewareState {\n customerId: string;\n quantity: number;\n idempotencyKey: string;\n hasPaymentProof: boolean;\n paymentProof?: X402PaymentProof;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Error codes specific to the withDrip middleware.\n */\nexport type DripMiddlewareErrorCode =\n | 'CUSTOMER_NOT_FOUND'\n | 'CUSTOMER_RESOLUTION_FAILED'\n | 'PAYMENT_REQUIRED'\n | 'PAYMENT_VERIFICATION_FAILED'\n | 'CHARGE_FAILED'\n | 'CONFIGURATION_ERROR'\n | 'INTERNAL_ERROR';\n\n/**\n * Error thrown by the withDrip middleware.\n */\nexport class DripMiddlewareError extends Error {\n constructor(\n message: string,\n public readonly code: DripMiddlewareErrorCode,\n public readonly statusCode: number,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'DripMiddlewareError';\n Object.setPrototypeOf(this, DripMiddlewareError.prototype);\n }\n}\n\n// ============================================================================\n// Framework Adapter Types\n// ============================================================================\n\n/**\n * Generic request interface that frameworks must implement.\n */\nexport interface GenericRequest {\n method: string;\n url: string;\n headers: Record<string, string | string[] | undefined>;\n query?: Record<string, string | string[] | undefined>;\n}\n\n/**\n * Generic response builder for framework adapters.\n */\nexport interface ResponseBuilder {\n status(code: number): this;\n header(name: string, value: string): this;\n json(body: unknown): unknown;\n}\n","/**\n * Drip Middleware Core\n *\n * Framework-agnostic core logic for the withDrip wrapper.\n * This module handles customer resolution, balance checks, charging,\n * and x402 payment flow orchestration.\n */\n\nimport { randomBytes } from 'crypto';\nimport { Drip, DripError, type ChargeResult } from '../index.js';\nimport type {\n WithDripConfig,\n X402PaymentProof,\n X402PaymentRequest,\n X402ResponseHeaders,\n MiddlewareState,\n GenericRequest,\n} from './types.js';\nimport { DripMiddlewareError } from './types.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_PAYMENT_EXPIRY_SEC = 5 * 60; // 5 minutes\nconst MAX_TIMESTAMP_AGE_SEC = 5 * 60; // 5 minutes max age for payment proof timestamps\n\n// Required headers for x402 payment proof\nconst REQUIRED_PAYMENT_HEADERS = [\n 'x-payment-signature',\n 'x-payment-session-key',\n 'x-payment-smart-account',\n 'x-payment-timestamp',\n 'x-payment-amount',\n 'x-payment-recipient',\n 'x-payment-usage-id',\n 'x-payment-nonce',\n] as const;\n\n// ============================================================================\n// Header Utilities\n// ============================================================================\n\n/**\n * Normalize header name to lowercase.\n */\nfunction normalizeHeaderName(name: string): string {\n return name.toLowerCase();\n}\n\n/**\n * Get a header value from a request, handling case-insensitivity.\n */\nexport function getHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string,\n): string | undefined {\n const normalized = normalizeHeaderName(name);\n\n // Try exact match first\n if (headers[normalized] !== undefined) {\n const value = headers[normalized];\n return Array.isArray(value) ? value[0] : value;\n }\n\n // Fall back to case-insensitive search\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === normalized) {\n return Array.isArray(value) ? value[0] : value;\n }\n }\n\n return undefined;\n}\n\n// ============================================================================\n// x402 Payment Proof Parsing\n// ============================================================================\n\n/**\n * Check if a request contains x402 payment proof headers.\n */\nexport function hasPaymentProof(\n headers: Record<string, string | string[] | undefined>,\n): boolean {\n return REQUIRED_PAYMENT_HEADERS.every(\n (header) => getHeader(headers, header) !== undefined,\n );\n}\n\n/**\n * Parse x402 payment proof from request headers.\n * Returns null if headers are missing or invalid.\n */\nexport function parsePaymentProof(\n headers: Record<string, string | string[] | undefined>,\n): X402PaymentProof | null {\n const signature = getHeader(headers, 'x-payment-signature');\n const sessionKeyId = getHeader(headers, 'x-payment-session-key');\n const smartAccount = getHeader(headers, 'x-payment-smart-account');\n const timestampStr = getHeader(headers, 'x-payment-timestamp');\n const amount = getHeader(headers, 'x-payment-amount');\n const recipient = getHeader(headers, 'x-payment-recipient');\n const usageId = getHeader(headers, 'x-payment-usage-id');\n const nonce = getHeader(headers, 'x-payment-nonce');\n\n // All headers required\n if (\n !signature ||\n !sessionKeyId ||\n !smartAccount ||\n !timestampStr ||\n !amount ||\n !recipient ||\n !usageId ||\n !nonce\n ) {\n return null;\n }\n\n const timestamp = parseInt(timestampStr, 10);\n if (isNaN(timestamp)) {\n return null;\n }\n\n // Validate timestamp freshness (prevent replay attacks)\n const now = Math.floor(Date.now() / 1000);\n if (now - timestamp > MAX_TIMESTAMP_AGE_SEC) {\n return null; // Timestamp too old\n }\n\n // Validate hex format for blockchain addresses/signatures\n // Must start with 0x and contain only valid hex characters\n const isValidHex = (value: string, minLength: number): boolean => {\n if (!value.startsWith('0x')) return false;\n const hex = value.slice(2);\n if (hex.length < minLength) return false;\n return /^[a-fA-F0-9]+$/.test(hex);\n };\n\n // Signature must be at least 65 bytes (130 hex chars) for ECDSA\n // Session key and smart account are 32 and 20 bytes respectively\n if (\n !isValidHex(signature, 130) ||\n !isValidHex(sessionKeyId, 64) ||\n !isValidHex(smartAccount, 40)\n ) {\n return null;\n }\n\n return {\n signature,\n sessionKeyId,\n smartAccount,\n timestamp,\n amount,\n recipient,\n usageId,\n nonce,\n };\n}\n\n// ============================================================================\n// x402 Response Generation\n// ============================================================================\n\n/**\n * Generate x402 payment request headers for 402 responses.\n */\nexport function generatePaymentRequest(params: {\n amount: string;\n recipient: string;\n usageId: string;\n description?: string;\n expiresInSec?: number;\n}): {\n headers: X402ResponseHeaders;\n paymentRequest: X402PaymentRequest;\n} {\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (params.expiresInSec ?? DEFAULT_PAYMENT_EXPIRY_SEC);\n // Use cryptographically secure random bytes for nonce generation\n const nonce = `${now}-${randomBytes(16).toString('hex')}`;\n\n // Ensure usageId is properly formatted\n let usageId = params.usageId;\n if (!usageId.startsWith('0x')) {\n // Hash the string to get a bytes32\n usageId = hashString(usageId);\n }\n\n const headers: X402ResponseHeaders = {\n 'X-Payment-Required': 'true',\n 'X-Payment-Amount': params.amount,\n 'X-Payment-Recipient': params.recipient,\n 'X-Payment-Usage-Id': usageId,\n 'X-Payment-Description': params.description ?? 'API usage charge',\n 'X-Payment-Expires': String(expiresAt),\n 'X-Payment-Nonce': nonce,\n 'X-Payment-Timestamp': String(now),\n };\n\n const paymentRequest: X402PaymentRequest = {\n amount: params.amount,\n recipient: params.recipient,\n usageId,\n description: params.description ?? 'API usage charge',\n expiresAt,\n nonce,\n timestamp: now,\n };\n\n return { headers, paymentRequest };\n}\n\n/**\n * Simple string hash for usageId generation.\n * Uses djb2 hash algorithm for better distribution.\n * In production, the server will use keccak256.\n */\nfunction hashString(input: string): string {\n let primaryHash = 5381;\n let secondaryHash = 52711;\n\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n primaryHash = ((primaryHash << 5) + primaryHash) ^ char;\n secondaryHash = ((secondaryHash << 5) + secondaryHash) ^ char;\n }\n\n // Combine both hashes for more uniqueness\n const combined = Math.abs(primaryHash * 31 + secondaryHash);\n // Convert to hex and pad to 64 chars (for bytes32)\n const hex = combined.toString(16).padStart(16, '0').slice(0, 16);\n return `0x${hex.padEnd(64, '0')}`;\n}\n\n// ============================================================================\n// Customer Resolution\n// ============================================================================\n\n/**\n * Resolve customer ID from a request based on configuration.\n */\nexport async function resolveCustomerId<TRequest extends GenericRequest>(\n request: TRequest,\n config: WithDripConfig<TRequest>,\n): Promise<string> {\n const resolver = config.customerResolver ?? 'header';\n\n if (typeof resolver === 'function') {\n return resolver(request);\n }\n\n if (resolver === 'header') {\n const customerId =\n getHeader(request.headers, 'x-drip-customer-id') ??\n getHeader(request.headers, 'x-customer-id');\n\n if (!customerId) {\n throw new DripMiddlewareError(\n 'Missing customer ID. Include X-Drip-Customer-Id header.',\n 'CUSTOMER_RESOLUTION_FAILED',\n 400,\n );\n }\n\n return customerId;\n }\n\n if (resolver === 'query') {\n const query = request.query ?? {};\n const customerId = query['drip_customer_id'] ?? query['customer_id'];\n const id = Array.isArray(customerId) ? customerId[0] : customerId;\n\n if (!id) {\n throw new DripMiddlewareError(\n 'Missing customer ID. Include drip_customer_id query parameter.',\n 'CUSTOMER_RESOLUTION_FAILED',\n 400,\n );\n }\n\n return id;\n }\n\n throw new DripMiddlewareError(\n `Invalid customer resolver: ${resolver as string}`,\n 'CONFIGURATION_ERROR',\n 500,\n );\n}\n\n/**\n * Resolve quantity from configuration.\n */\nexport async function resolveQuantity<TRequest>(\n request: TRequest,\n config: WithDripConfig<TRequest>,\n): Promise<number> {\n if (typeof config.quantity === 'function') {\n return config.quantity(request);\n }\n return config.quantity;\n}\n\n/**\n * Generate idempotency key for a request.\n */\nexport async function generateIdempotencyKey<TRequest extends GenericRequest>(\n request: TRequest,\n customerId: string,\n config: WithDripConfig<TRequest>,\n): Promise<string> {\n if (config.idempotencyKey) {\n return config.idempotencyKey(request);\n }\n\n // Default: hash of method + url + customer + timestamp (millisecond precision)\n // Using milliseconds ensures each request gets a unique key by default\n const timestamp = Date.now();\n const components = [request.method, request.url, customerId, timestamp];\n return `drip_${hashString(components.join('|')).slice(2, 18)}`;\n}\n\n// ============================================================================\n// Drip Client Factory\n// ============================================================================\n\n/**\n * Create a Drip client from configuration.\n */\nexport function createDripClient<TRequest>(\n config: WithDripConfig<TRequest>,\n): Drip {\n const apiKey = config.apiKey ?? process.env.DRIP_API_KEY;\n\n if (!apiKey) {\n throw new DripMiddlewareError(\n 'Missing Drip API key. Set DRIP_API_KEY environment variable or pass apiKey in config.',\n 'CONFIGURATION_ERROR',\n 500,\n );\n }\n\n return new Drip({\n apiKey,\n baseUrl: config.baseUrl ?? process.env.DRIP_API_URL,\n });\n}\n\n// ============================================================================\n// Core Middleware Logic\n// ============================================================================\n\n/**\n * Process a request through the Drip billing flow.\n *\n * This is the core logic used by all framework adapters.\n * It handles:\n * 1. Customer resolution\n * 2. Balance checking (if no payment proof)\n * 3. Charging the customer\n * 4. x402 payment flow orchestration\n */\n/**\n * Success result from processRequest.\n */\nexport interface ProcessRequestSuccess {\n success: true;\n state: MiddlewareState;\n charge: ChargeResult;\n drip: Drip;\n isReplay: boolean;\n}\n\n/**\n * Failure result from processRequest.\n */\nexport interface ProcessRequestFailure {\n success: false;\n error: DripMiddlewareError;\n paymentRequired?: {\n headers: X402ResponseHeaders;\n paymentRequest: X402PaymentRequest;\n };\n}\n\nexport type ProcessRequestResult = ProcessRequestSuccess | ProcessRequestFailure;\n\nexport async function processRequest<TRequest extends GenericRequest>(\n request: TRequest,\n config: WithDripConfig<TRequest>,\n): Promise<ProcessRequestResult> {\n // Check if we should skip in development\n if (config.skipInDevelopment && process.env.NODE_ENV === 'development') {\n // Warn that billing is being skipped in development\n console.warn(\n '[Drip] Skipping billing in development mode. Set skipInDevelopment: false or NODE_ENV to production to enable billing.',\n );\n // Return a mock successful charge for development\n const drip = createDripClient(config);\n const mockCharge: ChargeResult = {\n success: true,\n usageEventId: 'dev_usage_event',\n isReplay: false,\n charge: {\n id: 'dev_charge',\n amountUsdc: '0.00',\n amountToken: '0',\n txHash: '0x0',\n status: 'CONFIRMED' as const,\n },\n };\n return {\n success: true,\n state: {\n customerId: 'dev_customer',\n quantity: typeof config.quantity === 'number' ? config.quantity : 1,\n idempotencyKey: 'dev_idempotency',\n hasPaymentProof: false,\n },\n charge: mockCharge,\n drip,\n isReplay: false,\n };\n }\n\n try {\n // Create Drip client\n const drip = createDripClient(config);\n\n // Resolve customer and quantity\n const customerId = await resolveCustomerId(request, config);\n const quantity = await resolveQuantity(request, config);\n const idempotencyKey = await generateIdempotencyKey(request, customerId, config);\n\n // Check for payment proof\n const paymentProofPresent = hasPaymentProof(request.headers);\n const paymentProof = paymentProofPresent\n ? parsePaymentProof(request.headers)\n : undefined;\n\n const state: MiddlewareState = {\n customerId,\n quantity,\n idempotencyKey,\n hasPaymentProof: paymentProofPresent,\n paymentProof: paymentProof ?? undefined,\n };\n\n // Resolve metadata\n const metadata = typeof config.metadata === 'function'\n ? config.metadata(request)\n : config.metadata;\n\n // Attempt to charge\n try {\n const chargeResult = await drip.charge({\n customerId,\n meter: config.meter,\n quantity,\n idempotencyKey,\n metadata,\n });\n\n // Call onCharge callback if provided\n if (config.onCharge) {\n await config.onCharge(chargeResult, request);\n }\n\n return {\n success: true,\n state,\n charge: chargeResult,\n drip,\n isReplay: chargeResult.isReplay ?? false,\n };\n } catch (error) {\n if (error instanceof DripError) {\n // Handle 402 Payment Required\n if (error.statusCode === 402) {\n // Require DRIP_RECIPIENT_ADDRESS to be configured\n const recipient = process.env.DRIP_RECIPIENT_ADDRESS;\n if (!recipient) {\n throw new DripMiddlewareError(\n 'DRIP_RECIPIENT_ADDRESS environment variable must be configured for x402 payment flow.',\n 'CONFIGURATION_ERROR',\n 500,\n );\n }\n\n // Try to extract amount from error message (format: \"... amount: X.XX USDC\")\n // or use quantity as fallback (1 unit = 0.01 USDC default)\n let amount = '0.01';\n const amountMatch = error.message.match(/amount[:\\s]+([0-9.]+)/i);\n if (amountMatch) {\n amount = amountMatch[1];\n } else {\n // Calculate based on quantity with default rate of 0.0001 USDC per unit\n const calculatedAmount = (quantity * 0.0001).toFixed(6);\n amount = calculatedAmount;\n }\n\n // Generate payment request for x402 flow\n const { headers, paymentRequest } = generatePaymentRequest({\n amount,\n recipient,\n usageId: idempotencyKey,\n description: `${config.meter} usage charge`,\n });\n\n return {\n success: false,\n error: new DripMiddlewareError(\n 'Insufficient balance. Payment required.',\n 'PAYMENT_REQUIRED',\n 402,\n ),\n paymentRequired: { headers, paymentRequest },\n };\n }\n\n // Handle other Drip errors\n if (config.onError) {\n await config.onError(error, request);\n }\n\n throw new DripMiddlewareError(\n error.message,\n 'CHARGE_FAILED',\n error.statusCode,\n { code: error.code },\n );\n }\n\n throw error;\n }\n } catch (error) {\n if (error instanceof DripMiddlewareError) {\n return { success: false, error };\n }\n\n // Wrap unexpected errors\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n error: new DripMiddlewareError(message, 'INTERNAL_ERROR', 500),\n };\n }\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport type { WithDripConfig, X402PaymentProof, X402PaymentRequest };\n","/**\n * Drip Express Adapter\n *\n * Provides the `dripMiddleware` for Express.js applications.\n * Handles the complete x402 payment flow automatically.\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { dripMiddleware } from '@drip-sdk/node/express';\n *\n * const app = express();\n *\n * // Apply to specific routes\n * app.use('/api/paid', dripMiddleware({\n * meter: 'api_calls',\n * quantity: 1,\n * }));\n *\n * app.post('/api/paid/generate', (req, res) => {\n * // Payment already verified - req.drip contains context\n * console.log(`Charged: ${req.drip.charge.charge.amountUsdc} USDC`);\n * res.json({ success: true });\n * });\n * ```\n */\n\nimport type { Drip } from '../index.js';\nimport type {\n WithDripConfig,\n DripContext,\n X402ResponseHeaders,\n GenericRequest,\n} from './types.js';\nimport { DripMiddlewareError } from './types.js';\nimport {\n processRequest,\n hasPaymentProof,\n} from './core.js';\n\n// ============================================================================\n// Express Types\n// ============================================================================\n\n/**\n * Express request type.\n * We use a minimal interface to avoid requiring express as a dependency.\n */\nexport interface ExpressRequest {\n method: string;\n url: string;\n originalUrl: string;\n path: string;\n headers: Record<string, string | string[] | undefined>;\n query: Record<string, string | string[] | undefined>;\n params: Record<string, string>;\n body?: unknown;\n}\n\n/**\n * Express response type.\n */\nexport interface ExpressResponse {\n status(code: number): ExpressResponse;\n set(headers: Record<string, string>): ExpressResponse;\n json(body: unknown): void;\n send(body: unknown): void;\n}\n\n/**\n * Express next function.\n */\nexport type ExpressNextFunction = (error?: unknown) => void;\n\n/**\n * Express middleware type.\n */\nexport type ExpressMiddleware = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: ExpressNextFunction,\n) => void | Promise<void>;\n\n/**\n * Extended Express request with Drip context.\n */\nexport interface DripExpressRequest extends ExpressRequest {\n drip: DripContext;\n}\n\n/**\n * Configuration specific to Express adapter.\n */\nexport interface ExpressDripConfig extends WithDripConfig<ExpressRequest> {\n /**\n * Custom error handler.\n * Return true to indicate the error was handled.\n */\n errorHandler?: (\n error: DripMiddlewareError,\n req: ExpressRequest,\n res: ExpressResponse,\n ) => boolean | Promise<boolean>;\n\n /**\n * Whether to attach the Drip context to the request object.\n * @default true\n */\n attachToRequest?: boolean;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Normalize Express headers to a consistent format.\n */\nfunction normalizeHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n for (const [key, value] of Object.entries(headers)) {\n result[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;\n }\n return result;\n}\n\n/**\n * Send a 402 Payment Required response.\n */\nfunction sendPaymentRequired(\n res: ExpressResponse,\n headers: X402ResponseHeaders,\n paymentRequest: {\n amount: string;\n recipient: string;\n usageId: string;\n description: string;\n expiresAt: number;\n nonce: string;\n timestamp: number;\n },\n): void {\n res.status(402).set(headers as Record<string, string>).json({\n error: 'Payment required',\n code: 'PAYMENT_REQUIRED',\n paymentRequest,\n instructions: {\n step1: 'Sign the payment request with your session key using EIP-712',\n step2: 'Retry the request with X-Payment-* headers',\n documentation: 'https://docs.drip.dev/x402',\n },\n });\n}\n\n/**\n * Send an error response.\n */\nfunction sendError(\n res: ExpressResponse,\n message: string,\n code: string,\n status: number,\n details?: Record<string, unknown>,\n): void {\n res.status(status).json({\n error: message,\n code,\n ...(details && { details }),\n });\n}\n\n// ============================================================================\n// Main Middleware\n// ============================================================================\n\n/**\n * Express middleware for Drip billing.\n *\n * This middleware:\n * 1. Resolves the customer ID from headers or query\n * 2. Checks customer balance\n * 3. If insufficient, returns 402 with x402 payment headers\n * 4. If payment proof provided, verifies and processes\n * 5. Charges the customer\n * 6. Attaches Drip context to req.drip\n * 7. Calls next() on success\n *\n * @param config - Configuration for billing\n * @returns Express middleware\n *\n * @example\n * ```typescript\n * // Apply to all routes under /api/paid\n * app.use('/api/paid', dripMiddleware({\n * meter: 'api_calls',\n * quantity: 1,\n * }));\n *\n * // Or with dynamic quantity\n * app.use('/api/ai', dripMiddleware({\n * meter: 'tokens',\n * quantity: (req) => req.body?.maxTokens ?? 100,\n * }));\n * ```\n */\nexport function dripMiddleware(config: ExpressDripConfig): ExpressMiddleware {\n const attachToRequest = config.attachToRequest ?? true;\n\n return async (req, res, next) => {\n // Convert Express request to generic format\n const genericRequest = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: normalizeHeaders(req.headers),\n query: req.query as Record<string, string | undefined>,\n };\n\n // Resolve quantity if it's a function (needs access to original request)\n const resolvedQuantity = typeof config.quantity === 'function'\n ? await config.quantity(req)\n : config.quantity;\n\n // Resolve customer ID if it's a function - wrap to use generic request\n let resolvedCustomerResolver: 'header' | 'query' | ((r: GenericRequest) => string | Promise<string>) | undefined;\n if (typeof config.customerResolver === 'function') {\n // Capture the original resolver and call it with the original request\n const originalResolver = config.customerResolver;\n resolvedCustomerResolver = async () => originalResolver(req);\n } else {\n resolvedCustomerResolver = config.customerResolver;\n }\n\n // Resolve idempotencyKey if it's a function\n let resolvedIdempotencyKey: ((r: GenericRequest) => string | Promise<string>) | undefined;\n if (typeof config.idempotencyKey === 'function') {\n const originalIdempotencyKey = config.idempotencyKey;\n resolvedIdempotencyKey = async () => originalIdempotencyKey(req);\n }\n\n // Resolve metadata if it's a function\n const resolvedMetadata = typeof config.metadata === 'function'\n ? config.metadata(req)\n : config.metadata;\n\n // Create a generic config for processRequest\n const genericConfig: WithDripConfig<typeof genericRequest> = {\n meter: config.meter,\n quantity: resolvedQuantity,\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n customerResolver: resolvedCustomerResolver,\n idempotencyKey: resolvedIdempotencyKey,\n metadata: resolvedMetadata,\n skipInDevelopment: config.skipInDevelopment,\n // Clear callbacks that need the original request type\n onCharge: undefined,\n onError: undefined,\n };\n\n // Process the request through Drip billing\n const result = await processRequest(genericRequest, genericConfig);\n\n if (!result.success) {\n // Handle custom error handler\n if (config.errorHandler) {\n const handled = await config.errorHandler(result.error, req, res);\n if (handled) {\n return;\n }\n }\n\n // Handle 402 Payment Required\n if (result.paymentRequired) {\n sendPaymentRequired(\n res,\n result.paymentRequired.headers,\n result.paymentRequired.paymentRequest,\n );\n return;\n }\n\n // Send error response\n sendError(\n res,\n result.error.message,\n result.error.code,\n result.error.statusCode,\n result.error.details,\n );\n return;\n }\n\n // Call original onCharge callback if provided\n if (config.onCharge) {\n await config.onCharge(result.charge, req);\n }\n\n // Build context\n const dripContext: DripContext = {\n drip: result.drip,\n customerId: result.state.customerId,\n charge: result.charge,\n isReplay: result.isReplay,\n };\n\n // Attach to request if configured\n if (attachToRequest) {\n (req as DripExpressRequest).drip = dripContext;\n }\n\n // Continue to next middleware/handler\n next();\n };\n}\n\n// ============================================================================\n// Convenience Exports\n// ============================================================================\n\n/**\n * Create a dripMiddleware factory with default configuration.\n * Useful for consistent settings across multiple route groups.\n *\n * @example\n * ```typescript\n * // lib/drip.ts\n * import { createDripMiddleware } from '@drip-sdk/node/express';\n *\n * export const drip = createDripMiddleware({\n * apiKey: process.env.DRIP_API_KEY,\n * baseUrl: process.env.DRIP_API_URL,\n * });\n *\n * // routes/api.ts\n * import { drip } from '../lib/drip';\n *\n * router.use('/paid', drip({ meter: 'api_calls', quantity: 1 }));\n * ```\n */\nexport function createDripMiddleware(\n defaults: Partial<Omit<ExpressDripConfig, 'meter' | 'quantity'>>,\n): (\n config: Pick<ExpressDripConfig, 'meter' | 'quantity'> & Partial<Omit<ExpressDripConfig, 'meter' | 'quantity'>>,\n) => ExpressMiddleware {\n return (config) => {\n return dripMiddleware({ ...defaults, ...config } as ExpressDripConfig);\n };\n}\n\n/**\n * Check if an Express request has x402 payment proof headers.\n * Useful for conditional logic in routes.\n */\nexport function hasPaymentProofHeaders(req: ExpressRequest): boolean {\n return hasPaymentProof(normalizeHeaders(req.headers));\n}\n\n/**\n * Type guard to check if request has Drip context attached.\n */\nexport function hasDripContext(\n req: ExpressRequest,\n): req is DripExpressRequest {\n return 'drip' in req && typeof (req as DripExpressRequest).drip === 'object';\n}\n\n/**\n * Get Drip context from request, throwing if not present.\n */\nexport function getDripContext(req: ExpressRequest): DripContext {\n if (!hasDripContext(req)) {\n throw new Error(\n 'Drip context not found on request. Ensure dripMiddleware is applied before this route.',\n );\n }\n return req.drip;\n}\n"]}