@drip-sdk/node 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,13 +29,16 @@ export DRIP_API_KEY=sk_test_...
29
29
  export DRIP_API_KEY=pk_test_...
30
30
  ```
31
31
 
32
- ### 3. Track usage (one line)
32
+ ### 3. Create a customer and track usage
33
33
 
34
34
  ```typescript
35
35
  import { drip } from '@drip-sdk/node';
36
36
 
37
- // Track usage - that's it
38
- await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
37
+ // Create a customer first
38
+ const customer = await drip.createCustomer({ externalCustomerId: 'user_123' });
39
+
40
+ // Track usage — that's it
41
+ await drip.trackUsage({ customerId: customer.id, meter: 'api_calls', quantity: 1 });
39
42
  ```
40
43
 
41
44
  The `drip` singleton reads `DRIP_API_KEY` from your environment automatically.
@@ -64,9 +67,12 @@ async function main() {
64
67
  // Verify connectivity
65
68
  await drip.ping();
66
69
 
70
+ // Create a customer (at least one of externalCustomerId or onchainAddress required)
71
+ const customer = await drip.createCustomer({ externalCustomerId: 'user_123' });
72
+
67
73
  // Record usage
68
74
  await drip.trackUsage({
69
- customerId: 'customer_123',
75
+ customerId: customer.id,
70
76
  meter: 'llm_tokens',
71
77
  quantity: 842,
72
78
  metadata: { model: 'gpt-4o-mini' },
@@ -74,16 +80,16 @@ async function main() {
74
80
 
75
81
  // Record an execution lifecycle
76
82
  await drip.recordRun({
77
- customerId: 'customer_123',
83
+ customerId: customer.id,
78
84
  workflow: 'research-agent',
79
85
  events: [
80
- { eventType: 'llm.call', model: 'gpt-4', inputTokens: 500, outputTokens: 1200 },
81
- { eventType: 'tool.call', name: 'web-search', duration: 1500 },
86
+ { eventType: 'llm.call', quantity: 1700, units: 'tokens' },
87
+ { eventType: 'tool.call', quantity: 1 },
82
88
  ],
83
89
  status: 'COMPLETED',
84
90
  });
85
91
 
86
- console.log('Usage + run recorded');
92
+ console.log(`Customer ${customer.id}: usage + run recorded`);
87
93
  }
88
94
 
89
95
  main();
@@ -103,6 +109,7 @@ main();
103
109
  | `meter` | What you're measuring (tokens, requests, seconds, rows, etc.) |
104
110
  | `quantity` | Numeric usage for that meter |
105
111
  | `run` | A single execution or request lifecycle (success / failure / duration) |
112
+ | `correlationId` | Optional. Your trace/request ID for linking Drip data with your APM (OpenTelemetry, Datadog, etc.) |
106
113
 
107
114
  **Status values:** `PENDING` | `RUNNING` | `COMPLETED` | `FAILED`
108
115
 
@@ -110,6 +117,8 @@ main();
110
117
 
111
118
  Drip is append-only and idempotent-friendly. You can safely retry events.
112
119
 
120
+ > **Distributed tracing:** Pass `correlationId` to `startRun()`, `recordRun()`, or `emitEvent()` to cross-reference Drip billing with your observability stack. See [FULL_SDK.md](./FULL_SDK.md#distributed-tracing-correlationid) for details.
121
+
113
122
  ---
114
123
 
115
124
  ## Idempotency Keys
@@ -135,8 +144,10 @@ This means you get **free retry safety** with zero configuration.
135
144
  Pass your own `idempotencyKey` when you need **application-level deduplication** — e.g., to guarantee that a specific business operation is billed exactly once, even across process restarts:
136
145
 
137
146
  ```typescript
147
+ const customer = await drip.createCustomer({ externalCustomerId: 'user_123' });
148
+
138
149
  await drip.charge({
139
- customerId: 'cust_123',
150
+ customerId: customer.id,
140
151
  meter: 'api_calls',
141
152
  quantity: 1,
142
153
  idempotencyKey: `order_${orderId}_charge`, // your business-level key
@@ -180,8 +191,9 @@ The SDK detects your key type automatically and will throw a `DripError` with co
180
191
  const drip = new Drip({ apiKey: 'pk_test_...' });
181
192
  console.log(drip.keyType); // 'public'
182
193
 
183
- // This works fine
184
- await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
194
+ // Create a customer first, then track usage
195
+ const customer = await drip.createCustomer({ externalCustomerId: 'user_123' });
196
+ await drip.trackUsage({ customerId: customer.id, meter: 'api_calls', quantity: 1 });
185
197
 
186
198
  // This throws DripError(403, 'PUBLIC_KEY_NOT_ALLOWED')
187
199
  await drip.createWebhook({ url: '...', events: ['charge.succeeded'] });
@@ -212,8 +224,39 @@ await drip.createWebhook({ url: '...', events: ['charge.succeeded'] });
212
224
  | `emitEvent(params)` | Log event within run |
213
225
  | `emitEventsBatch(params)` | Batch log events |
214
226
  | `endRun(runId, params)` | Complete execution trace |
227
+ | `getRun(runId)` | Get run details and summary |
215
228
  | `getRunTimeline(runId)` | Get execution timeline |
216
229
 
230
+ ### Creating Customers
231
+
232
+ All parameters are optional, but at least one of `externalCustomerId` or `onchainAddress` must be provided:
233
+
234
+ ```typescript
235
+ // Simplest — just your internal user ID
236
+ const customer = await drip.createCustomer({ externalCustomerId: 'user_123' });
237
+
238
+ // With an on-chain address (for on-chain billing)
239
+ const customer = await drip.createCustomer({
240
+ onchainAddress: '0x1234...',
241
+ externalCustomerId: 'user_123',
242
+ });
243
+
244
+ // Internal/non-billing customer (for tracking only)
245
+ const customer = await drip.createCustomer({
246
+ externalCustomerId: 'internal-team',
247
+ isInternal: true,
248
+ });
249
+ ```
250
+
251
+ | Parameter | Type | Required | Description |
252
+ |-----------|------|----------|-------------|
253
+ | `externalCustomerId` | `string` | No* | Your internal user/account ID |
254
+ | `onchainAddress` | `string` | No* | Customer's Ethereum address |
255
+ | `isInternal` | `boolean` | No | Mark as internal (non-billing). Default: `false` |
256
+ | `metadata` | `object` | No | Arbitrary key-value metadata |
257
+
258
+ \*At least one of `externalCustomerId` or `onchainAddress` is required.
259
+
217
260
  ---
218
261
 
219
262
  ## Who This Is For
package/dist/core.cjs CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var crypto=require('crypto');var I=0;function y(m,...t){let e=++I,n=t.filter(r=>r!==void 0).map(String);n.push(String(e));let s=crypto.createHash("sha256").update(n.join("|")).digest("hex").slice(0,24);return `${m}_${s}`}var u=class m extends Error{constructor(e,n,s){super(e);this.statusCode=n;this.code=s;this.name="DripError",Object.setPrototypeOf(this,m.prototype);}},f=class{apiKey;baseUrl;timeout;keyType;constructor(t={}){let e=t.apiKey??(typeof process<"u"?process.env.DRIP_API_KEY:void 0),n=t.baseUrl??(typeof process<"u"?process.env.DRIP_BASE_URL:void 0);if(!e)throw new Error("Drip API key is required. Either pass { apiKey } to constructor or set DRIP_API_KEY environment variable.");this.apiKey=e,this.baseUrl=n||"https://drip-app-hlunj.ondigitalocean.app/v1",this.timeout=t.timeout||3e4,e.startsWith("sk_")?this.keyType="secret":e.startsWith("pk_")?this.keyType="public":this.keyType="unknown";}async request(t,e={}){let n=new AbortController,s=setTimeout(()=>n.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${t}`,{...e,signal:n.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...e.headers}});if(r.status===204)return {success:!0};let o=await r.json();if(!r.ok)throw new u(o.message||o.error||"Request failed",r.status,o.code);return o}catch(r){throw r instanceof u?r:r instanceof Error&&r.name==="AbortError"?new u("Request timed out",408,"TIMEOUT"):new u(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(s);}}async ping(){let t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),n=this.baseUrl;n.endsWith("/v1/")?n=n.slice(0,-4):n.endsWith("/v1")&&(n=n.slice(0,-3)),n=n.replace(/\/+$/,"");let s=Date.now();try{let r=await fetch(`${n}/health`,{signal:t.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),o=Date.now()-s,a="unknown",l=Date.now();try{let c=await r.json();typeof c.status=="string"&&(a=c.status),typeof c.timestamp=="number"&&(l=c.timestamp);}catch{a=r.ok?"healthy":`error:${r.status}`;}return !r.ok&&a==="unknown"&&(a=`error:${r.status}`),{ok:r.ok&&a==="healthy",status:a,latencyMs:o,timestamp:l}}catch(r){throw r instanceof Error&&r.name==="AbortError"?new u("Request timed out",408,"TIMEOUT"):new u(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(e);}}async createCustomer(t){return this.request("/customers",{method:"POST",body:JSON.stringify(t)})}async getCustomer(t){return this.request(`/customers/${t}`)}async listCustomers(t){let e=new URLSearchParams;t?.limit&&e.set("limit",t.limit.toString()),t?.status&&e.set("status",t.status);let n=e.toString(),s=n?`/customers?${n}`:"/customers";return this.request(s)}async trackUsage(t){let e=t.idempotencyKey??y("track",t.customerId,t.meter,t.quantity);return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:e,units:t.units,description:t.description,metadata:t.metadata})})}async createWorkflow(t){return this.request("/workflows",{method:"POST",body:JSON.stringify(t)})}async listWorkflows(){return this.request("/workflows")}async startRun(t){return this.request("/runs",{method:"POST",body:JSON.stringify(t)})}async endRun(t,e){return this.request(`/runs/${t}`,{method:"PATCH",body:JSON.stringify(e)})}async getRun(t){return this.request(`/runs/${t}`)}async getRunTimeline(t,e){let n=new URLSearchParams;e?.limit&&n.set("limit",e.limit.toString()),e?.cursor&&n.set("cursor",e.cursor),e?.includeAnomalies!==void 0&&n.set("includeAnomalies",String(e.includeAnomalies)),e?.collapseRetries!==void 0&&n.set("collapseRetries",String(e.collapseRetries));let s=n.toString(),r=s?`/runs/${t}/timeline?${s}`:`/runs/${t}/timeline`;return this.request(r)}async emitEvent(t){let e=t.idempotencyKey??y("evt",t.runId,t.eventType,t.quantity);return this.request("/run-events",{method:"POST",body:JSON.stringify({...t,idempotencyKey:e})})}async emitEventsBatch(t){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:t})})}async recordRun(t){let e=Date.now(),n=t.workflow,s=t.workflow;if(!t.workflow.startsWith("wf_"))try{let d=(await this.listWorkflows()).data.find(i=>i.slug===t.workflow||i.id===t.workflow);if(d)n=d.id,s=d.name;else {let i=await this.createWorkflow({name:t.workflow.replace(/[_-]/g," ").replace(/\b\w/g,g=>g.toUpperCase()),slug:t.workflow,productSurface:"CUSTOM"});n=i.id,s=i.name;}}catch{n=t.workflow;}let r=await this.startRun({customerId:t.customerId,workflowId:n,externalRunId:t.externalRunId,correlationId:t.correlationId,metadata:t.metadata}),o=0,a=0;if(t.events.length>0){let w=t.events.map((i,g)=>({runId:r.id,eventType:i.eventType,quantity:i.quantity,units:i.units,description:i.description,costUnits:i.costUnits,metadata:i.metadata,idempotencyKey:t.externalRunId?`${t.externalRunId}:${i.eventType}:${g}`:y("run",r.id,i.eventType,g)})),d=await this.emitEventsBatch(w);o=d.created,a=d.duplicates;}let l=await this.endRun(r.id,{status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode}),c=Date.now()-e,R=t.events.length>0?`${o} events recorded`:"no events",h=`${t.status==="COMPLETED"?"\u2713":t.status==="FAILED"?"\u2717":"\u25CB"} ${s}: ${R} (${l.durationMs??c}ms)`;return {run:{id:r.id,workflowId:n,workflowName:s,status:t.status,durationMs:l.durationMs},events:{created:o,duplicates:a},totalCostUnits:l.totalCostUnits,summary:h}}},S=f,p=null;function k(){return p||(p=new f),p}var U=new Proxy({},{get(m,t){let e=k(),n=e[t];return typeof n=="function"?n.bind(e):n}});
2
- exports.Drip=f;exports.DripError=u;exports.default=S;exports.drip=U;//# sourceMappingURL=core.cjs.map
1
+ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var crypto=require('crypto');var k=0;function p(m,...t){let e=++k,n=t.filter(r=>r!==void 0).map(String);n.push(String(e));let s=crypto.createHash("sha256").update(n.join("|")).digest("hex").slice(0,24);return `${m}_${s}`}var a=class m extends Error{constructor(e,n,s){super(e);this.statusCode=n;this.code=s;this.name="DripError",Object.setPrototypeOf(this,m.prototype);}},f=class{apiKey;baseUrl;timeout;keyType;constructor(t={}){let e=t.apiKey??(typeof process<"u"?process.env.DRIP_API_KEY:void 0),n=t.baseUrl??(typeof process<"u"?process.env.DRIP_BASE_URL:void 0);if(!e)throw new Error("Drip API key is required. Either pass { apiKey } to constructor or set DRIP_API_KEY environment variable.");this.apiKey=e,this.baseUrl=n||"https://drip-app-hlunj.ondigitalocean.app/v1",this.timeout=t.timeout||3e4,e.startsWith("sk_")?this.keyType="secret":e.startsWith("pk_")?this.keyType="public":this.keyType="unknown";}async request(t,e={}){let n=new AbortController,s=setTimeout(()=>n.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${t}`,{...e,signal:n.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...e.headers}});if(r.status===204)return {success:!0};let i=await r.json();if(!r.ok)throw new a(i.message||i.error||"Request failed",r.status,i.code);return i}catch(r){throw r instanceof a?r:r instanceof Error&&r.name==="AbortError"?new a("Request timed out",408,"TIMEOUT"):new a(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(s);}}async ping(){let t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),n=this.baseUrl;n.endsWith("/v1/")?n=n.slice(0,-4):n.endsWith("/v1")&&(n=n.slice(0,-3)),n=n.replace(/\/+$/,"");let s=Date.now();try{let r=await fetch(`${n}/health`,{signal:t.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),i=Date.now()-s,o="unknown",d=Date.now();try{let u=await r.json();typeof u.status=="string"&&(o=u.status),typeof u.timestamp=="number"&&(d=u.timestamp);}catch{o=r.ok?"healthy":`error:${r.status}`;}return !r.ok&&o==="unknown"&&(o=`error:${r.status}`),{ok:r.ok&&o==="healthy",status:o,latencyMs:i,timestamp:d}}catch(r){throw r instanceof Error&&r.name==="AbortError"?new a("Request timed out",408,"TIMEOUT"):new a(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(e);}}async createCustomer(t){return this.request("/customers",{method:"POST",body:JSON.stringify(t)})}async getCustomer(t){return this.request(`/customers/${t}`)}async listCustomers(t){let e=new URLSearchParams;t?.limit&&e.set("limit",t.limit.toString()),t?.status&&e.set("status",t.status);let n=e.toString(),s=n?`/customers?${n}`:"/customers";return this.request(s)}async trackUsage(t){let e=t.idempotencyKey??p("track",t.customerId,t.meter,t.quantity);return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:e,units:t.units,description:t.description,metadata:t.metadata})})}async createWorkflow(t){return this.request("/workflows",{method:"POST",body:JSON.stringify(t)})}async listWorkflows(){return this.request("/workflows")}async startRun(t){return this.request("/runs",{method:"POST",body:JSON.stringify(t)})}async endRun(t,e){return this.request(`/runs/${t}`,{method:"PATCH",body:JSON.stringify(e)})}async getRun(t){return this.request(`/runs/${t}`)}async getRunTimeline(t,e){let n=new URLSearchParams;e?.limit&&n.set("limit",e.limit.toString()),e?.cursor&&n.set("cursor",e.cursor),e?.includeAnomalies!==void 0&&n.set("includeAnomalies",String(e.includeAnomalies)),e?.collapseRetries!==void 0&&n.set("collapseRetries",String(e.collapseRetries));let s=n.toString(),r=s?`/runs/${t}/timeline?${s}`:`/runs/${t}/timeline`;return this.request(r)}async emitEvent(t){let e=t.idempotencyKey??p("evt",t.runId,t.eventType,t.quantity);return this.request("/run-events",{method:"POST",body:JSON.stringify({...t,idempotencyKey:e})})}async emitEventsBatch(t){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:t})})}async recordRun(t){try{return await this.request("/runs/record",{method:"POST",body:JSON.stringify({customerId:t.customerId,workflow:t.workflow,events:t.events,status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode,externalRunId:t.externalRunId,correlationId:t.correlationId,metadata:t.metadata})})}catch(e){if(e instanceof a&&e.statusCode===404)return this._recordRunFallback(t);throw e}}async _recordRunFallback(t){let e=Date.now(),n=t.workflow,s=t.workflow,{data:r}=await this.listWorkflows(),i=r.find(l=>l.slug===t.workflow||l.id===t.workflow);if(i)n=i.id,s=i.name;else {let l=await this.request("/workflows",{method:"POST",body:JSON.stringify({name:t.workflow.replace(/[_-]/g," ").replace(/\b\w/g,y=>y.toUpperCase()),slug:t.workflow,productSurface:"CUSTOM"})});n=l.id,s=l.name;}let o=await this.startRun({customerId:t.customerId,workflowId:n,correlationId:t.correlationId,externalRunId:t.externalRunId,metadata:t.metadata}),d=0,u=0;if(t.events.length>0){let l=t.events.map((c,I)=>({runId:o.id,eventType:c.eventType,quantity:c.quantity??1,units:c.units,description:c.description,costUnits:c.costUnits,metadata:c.metadata,idempotencyKey:t.externalRunId?`${t.externalRunId}:${c.eventType}:${I}`:void 0})),y=await this.emitEventsBatch(l);d=y.created,u=y.duplicates;}let g=await this.endRun(o.id,{status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode}),w=Date.now()-e,h=t.status==="COMPLETED"?"\u2713":t.status==="FAILED"?"\u2717":"\u25CB";return {run:{id:o.id,workflowId:n,workflowName:s,status:g.status,durationMs:g.durationMs??w},events:{created:d,duplicates:u},totalCostUnits:g.totalCostUnits??null,summary:`${h} ${s}: ${d} events recorded (${g.durationMs??w}ms)`}}},S=f,R=null;function C(){return R||(R=new f),R}var O=new Proxy({},{get(m,t){let e=C(),n=e[t];return typeof n=="function"?n.bind(e):n}});
2
+ exports.Drip=f;exports.DripError=a;exports.default=S;exports.drip=O;//# sourceMappingURL=core.cjs.map
3
3
  //# sourceMappingURL=core.cjs.map
package/dist/core.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/idempotency.ts","../src/core.ts"],"names":["_callCounter","deterministicIdempotencyKey","prefix","components","seq","parts","c","hash","createHash","DripError","_DripError","message","statusCode","code","Drip","config","apiKey","baseUrl","path","options","controller","timeoutId","res","data","error","healthBaseUrl","start","response","latencyMs","status","timestamp","params","customerId","query","idempotencyKey","runId","events","startTime","workflowId","workflowName","existing","w","created","run","eventsCreated","eventsDuplicates","batchEvents","event","index","batchResult","endResult","durationMs","eventSummary","summary","core_default","_singleton","getSingleton","drip","_target","prop","instance","value"],"mappings":"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","file":"core.cjs","sourcesContent":["/**\n * Deterministic idempotency key generation for SDK calls.\n *\n * Keys are:\n * - **Unique per call** — a monotonic counter ensures two rapid calls with\n * identical parameters produce different keys.\n * - **Stable across retries** — the key is generated once per SDK method\n * invocation and reused for every retry attempt.\n * - **Deterministic** — no randomness; keys are reproducible given the same\n * counter state.\n *\n * @internal\n */\nimport { createHash } from 'crypto';\n\nlet _callCounter = 0;\n\n/**\n * Generate a deterministic, unique idempotency key.\n *\n * @param prefix - Short prefix for the key type (e.g. `chg`, `track`, `evt`, `run`, `stream`)\n * @param components - Call-specific values (customerId, meter, quantity, etc.)\n * @returns A key like `chg_<24-char hex hash>`\n */\nexport function deterministicIdempotencyKey(\n prefix: string,\n ...components: Array<string | number | undefined>\n): string {\n const seq = ++_callCounter;\n const parts = components.filter((c) => c !== undefined).map(String);\n parts.push(String(seq));\n const hash = createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 24);\n return `${prefix}_${hash}`;\n}\n\n/**\n * Reset counter — only for tests.\n * @internal\n */\nexport function _resetCallCounter(): void {\n _callCounter = 0;\n}\n","/**\n * Drip SDK Core - Essential API for pilots and new integrations\n *\n * This SDK focuses on two core concepts:\n * - **Usage tracking**: trackUsage() for recording usage without billing\n * - **Execution logging**: recordRun() and related methods for tracking runs/events\n *\n * For billing, webhooks, cost estimation, and advanced features:\n * `import { Drip } from '@drip-sdk/node'`\n *\n * @packageDocumentation\n */\n\nimport { deterministicIdempotencyKey } from './idempotency.js';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration options for the Drip SDK client.\n *\n * All fields are optional - the SDK will read from environment variables:\n * - `DRIP_API_KEY` - Your Drip API key\n * - `DRIP_BASE_URL` - Override API base URL (optional)\n */\nexport interface DripConfig {\n /**\n * Your Drip API key. Obtain this from the Drip dashboard.\n * Falls back to `DRIP_API_KEY` environment variable if not provided.\n *\n * Supports both key types:\n * - **Secret keys** (`sk_live_...` / `sk_test_...`): Full access to all endpoints\n * - **Public keys** (`pk_live_...` / `pk_test_...`): Safe for client-side use.\n * Can access usage tracking, customers, runs, and events.\n *\n * @example \"sk_live_abc123...\" or \"pk_live_abc123...\"\n */\n apiKey?: string;\n\n /**\n * Base URL for the Drip API. Defaults to production API.\n * Falls back to `DRIP_BASE_URL` environment variable if not provided.\n * @default \"https://drip-app-hlunj.ondigitalocean.app/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 (optional - may not be returned by all endpoints) */\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// Usage Tracking Types\n// ============================================================================\n\n/**\n * Parameters for tracking usage without billing.\n */\nexport interface TrackUsageParams {\n /**\n * The Drip customer ID to track usage for.\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 * Auto-generated if not provided, ensuring every call is individually trackable.\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// Run & Event Types (Execution Ledger)\n// ============================================================================\n\n/**\n * Parameters for starting a new 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 * Possible run statuses.\n */\nexport type RunStatus =\n | 'PENDING'\n | 'RUNNING'\n | 'COMPLETED'\n | 'FAILED'\n | 'CANCELLED'\n | 'TIMEOUT';\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 * Result of ending a run.\n */\nexport interface EndRunResult {\n id: string;\n status: RunStatus;\n endedAt: string | null;\n durationMs: number | null;\n eventCount: number;\n totalCostUnits: string | null;\n}\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., \"request.start\", \"llm.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 /** 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 */\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 * A single event to record in a run.\n */\nexport interface RecordRunEvent {\n /** Event type (e.g., \"request.start\", \"llm.call\", \"request.end\") */\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 */\nexport interface RecordRunParams {\n /** Customer ID this run belongs to */\n customerId: string;\n\n /**\n * Workflow/request type identifier. Examples:\n * - \"rpc-request\" for RPC providers\n * - \"api-request\" for API providers\n * - \"agent-run\" for AI agents\n *\n * Auto-creates if it doesn't exist.\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 from GET /runs/:id/timeline.\n */\nexport interface RunTimeline {\n runId: string;\n workflowId: string | null;\n customerId: string;\n status: RunStatus;\n startedAt: string | null;\n endedAt: string | null;\n durationMs: number | null;\n events: Array<{\n id: string;\n eventType: string;\n actionName: string | null;\n outcome: 'SUCCESS' | 'FAILED' | 'PENDING' | 'TIMEOUT' | 'RETRYING';\n explanation: string | null;\n description: string | null;\n timestamp: string;\n durationMs: number | null;\n parentEventId: string | null;\n retryOfEventId: string | null;\n attemptNumber: number;\n retriedByEventId: string | null;\n costUsdc: string | null;\n isRetry: boolean;\n retryChain: {\n totalAttempts: number;\n finalOutcome: string;\n events: string[];\n } | null;\n metadata: {\n usageType: string;\n quantity: number;\n units: string | null;\n } | null;\n }>;\n anomalies: Array<{\n id: string;\n type: string;\n severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';\n title: string;\n explanation: string;\n relatedEventIds: string[];\n detectedAt: string;\n status: 'OPEN' | 'INVESTIGATING' | 'RESOLVED' | 'FALSE_POSITIVE' | 'IGNORED';\n }>;\n summary: {\n totalEvents: number;\n byType: Record<string, number>;\n byOutcome: Record<string, number>;\n retriedEvents: number;\n failedEvents: number;\n totalCostUsdc: string | null;\n };\n hasMore: boolean;\n nextCursor: string | null;\n}\n\n/**\n * Run details response from GET /runs/:id.\n */\nexport interface RunDetails {\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 totals: {\n eventCount: number;\n totalQuantity: string;\n totalCostUnits: string;\n };\n _links: {\n timeline: string;\n };\n}\n\n// ============================================================================\n// Internal Types (used by recordRun)\n// ============================================================================\n\ninterface 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\ninterface CreateWorkflowParams {\n name: string;\n slug: string;\n productSurface?: 'API' | 'RPC' | 'WEBHOOK' | 'AGENT' | 'PIPELINE' | 'CUSTOM';\n description?: string;\n metadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Error thrown by Drip SDK operations.\n */\nexport class DripError extends Error {\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// Core SDK Class\n// ============================================================================\n\n/**\n * Drip SDK Core - Essential API for pilots and new integrations.\n *\n * Two core concepts:\n * - **Usage tracking**: `trackUsage()` - record usage without billing\n * - **Execution logging**: `recordRun()` - track request/run lifecycle with events\n *\n * For billing (`charge()`), webhooks, and advanced features:\n * ```typescript\n * import { Drip } from '@drip-sdk/node';\n * ```\n *\n * @example\n * ```typescript\n * import { Drip } from '@drip-sdk/node/core';\n *\n * const drip = new Drip({ apiKey: process.env.DRIP_API_KEY! });\n *\n * // Verify connection\n * const health = await drip.ping();\n * console.log(`API healthy: ${health.ok}`);\n *\n * // Track usage (no billing)\n * await drip.trackUsage({\n * customerId: 'cust_123',\n * meter: 'api_calls',\n * quantity: 1,\n * });\n *\n * // Record a complete request/run with events\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'rpc-request', // or 'api-request', 'agent-run'\n * events: [\n * { eventType: 'request.start' },\n * { eventType: 'llm.call', quantity: 1500, units: 'tokens' },\n * { eventType: 'request.end' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * console.log(result.summary);\n * ```\n */\nexport class Drip {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n /**\n * The type of API key being used.\n *\n * - `'secret'` — Full access (sk_live_... / sk_test_...)\n * - `'public'` — Client-safe, restricted access (pk_live_... / pk_test_...)\n * - `'unknown'` — Key format not recognized (legacy or custom)\n */\n readonly keyType: 'secret' | 'public' | 'unknown';\n\n /**\n * Creates a new Drip SDK client.\n *\n * @param config - Configuration options (all optional, reads from env vars)\n * @throws {Error} If apiKey is not provided and DRIP_API_KEY env var is not set\n *\n * @example\n * ```typescript\n * // Option 1: Explicit config\n * const drip = new Drip({ apiKey: 'your-api-key' });\n *\n * // Option 2: Auto-config from environment (recommended)\n * // Set DRIP_API_KEY env var, then:\n * const drip = new Drip();\n *\n * // Option 3: Use pre-initialized singleton\n * import { drip } from '@drip-sdk/node';\n * ```\n */\n constructor(config: DripConfig = {}) {\n // Read from config or fall back to environment variables\n const apiKey = config.apiKey ?? (typeof process !== 'undefined' ? process.env.DRIP_API_KEY : undefined);\n const baseUrl = config.baseUrl ?? (typeof process !== 'undefined' ? process.env.DRIP_BASE_URL : undefined);\n\n if (!apiKey) {\n throw new Error(\n 'Drip API key is required. Either pass { apiKey } to constructor or set DRIP_API_KEY environment variable.'\n );\n }\n\n this.apiKey = apiKey;\n this.baseUrl = baseUrl || 'https://drip-app-hlunj.ondigitalocean.app/v1';\n this.timeout = config.timeout || 30000;\n\n // Detect key type from prefix\n if (apiKey.startsWith('sk_')) {\n this.keyType = 'secret';\n } else if (apiKey.startsWith('pk_')) {\n this.keyType = 'public';\n } else {\n this.keyType = 'unknown';\n }\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 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\n // ==========================================================================\n\n /**\n * Pings the Drip API to check connectivity and measure latency.\n *\n * Use this to verify:\n * - API key is valid\n * - Base URL is correct\n * - Network connectivity works\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 * } else {\n * console.error(`API unhealthy: ${health.status}`);\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 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 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 status = response.ok ? 'healthy' : `error:${response.status}`;\n }\n\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 * });\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 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 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 // Usage Tracking (No Billing)\n // ==========================================================================\n\n /**\n * Records usage for tracking WITHOUT billing.\n *\n * Use this for:\n * - Pilot programs (track before billing)\n * - Internal team usage\n * - Pre-billing tracking before customer setup\n *\n * For actual billing, use `charge()` from the full SDK.\n *\n * @param params - 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 pilot',\n * });\n *\n * console.log(`Tracked: ${result.usageEventId}`);\n * ```\n */\n async trackUsage(params: TrackUsageParams): Promise<TrackUsageResult> {\n const idempotencyKey = params.idempotencyKey\n ?? deterministicIdempotencyKey('track', params.customerId, params.meter, params.quantity);\n\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,\n units: params.units,\n description: params.description,\n metadata: params.metadata,\n }),\n });\n }\n\n // ==========================================================================\n // Private Workflow Methods (used by recordRun)\n // ==========================================================================\n\n private async createWorkflow(params: CreateWorkflowParams): Promise<Workflow> {\n return this.request<Workflow>('/workflows', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n private async listWorkflows(): Promise<{ data: Workflow[]; count: number }> {\n return this.request<{ data: Workflow[]; count: number }>('/workflows');\n }\n\n // ==========================================================================\n // Run & Event Methods (Execution Ledger)\n // ==========================================================================\n\n /**\n * Starts a new 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 * });\n *\n * // Emit events during execution...\n * await drip.emitEvent({ runId: run.id, eventType: 'llm.call', quantity: 1000 });\n *\n * // End the run\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 async endRun(runId: string, params: EndRunParams): Promise<EndRunResult> {\n return this.request<EndRunResult>(`/runs/${runId}`, {\n method: 'PATCH',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Gets run details with summary totals.\n *\n * For full event history with retry chains and anomalies, use `getRunTimeline()`.\n *\n * @param runId - The run ID\n * @returns Run details with totals\n *\n * @example\n * ```typescript\n * const run = await drip.getRun('run_abc123');\n * console.log(`Status: ${run.status}, Events: ${run.totals.eventCount}`);\n * ```\n */\n async getRun(runId: string): Promise<RunDetails> {\n return this.request<RunDetails>(`/runs/${runId}`);\n }\n\n /**\n * Gets a run's full timeline with events, anomalies, and analytics.\n *\n * @param runId - The run ID\n * @param options - Pagination and filtering options\n * @returns Full timeline with events, anomalies, and summary\n *\n * @example\n * ```typescript\n * const timeline = await drip.getRunTimeline('run_abc123');\n *\n * console.log(`Status: ${timeline.status}`);\n * console.log(`Events: ${timeline.summary.totalEvents}`);\n *\n * for (const event of timeline.events) {\n * console.log(`${event.eventType}: ${event.outcome}`);\n * }\n * ```\n */\n async getRunTimeline(\n runId: string,\n options?: { limit?: number; cursor?: string; includeAnomalies?: boolean; collapseRetries?: boolean },\n ): Promise<RunTimeline> {\n const params = new URLSearchParams();\n if (options?.limit) params.set('limit', options.limit.toString());\n if (options?.cursor) params.set('cursor', options.cursor);\n if (options?.includeAnomalies !== undefined) params.set('includeAnomalies', String(options.includeAnomalies));\n if (options?.collapseRetries !== undefined) params.set('collapseRetries', String(options.collapseRetries));\n\n const query = params.toString();\n const path = query ? `/runs/${runId}/timeline?${query}` : `/runs/${runId}/timeline`;\n\n return this.request<RunTimeline>(path);\n }\n\n /**\n * Emits an event to a run.\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: 'llm.call',\n * quantity: 1500,\n * units: 'tokens',\n * description: 'GPT-4 completion',\n * });\n * ```\n */\n async emitEvent(params: EmitEventParams): Promise<EventResult> {\n const idempotencyKey = params.idempotencyKey\n ?? deterministicIdempotencyKey('evt', params.runId, params.eventType, params.quantity);\n\n return this.request<EventResult>('/run-events', {\n method: 'POST',\n body: JSON.stringify({ ...params, idempotencyKey }),\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 async emitEventsBatch(\n events: Array<EmitEventParams>,\n ): Promise<{\n success: boolean;\n created: number;\n duplicates: number;\n skipped: number;\n events: Array<{ id: string; eventType: string; isDuplicate: boolean; skipped?: boolean; reason?: string }>;\n }> {\n return this.request('/run-events/batch', {\n method: 'POST',\n body: JSON.stringify({ events }),\n });\n }\n\n /**\n * Records a complete request/run in a single call.\n *\n * This is the **hero method** for tracking execution. It combines:\n * - Workflow creation (auto-creates if needed)\n * - Run creation\n * - Event emission\n * - Run completion\n *\n * @param params - Run parameters including events\n * @returns The created run with event summary\n *\n * @example\n * ```typescript\n * // RPC provider example\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'rpc-request',\n * events: [\n * { eventType: 'request.start' },\n * { eventType: 'eth_call', quantity: 1 },\n * { eventType: 'request.end' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * // API provider example\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'api-request',\n * events: [\n * { eventType: 'request.start' },\n * { eventType: 'llm.call', quantity: 2000, units: 'tokens' },\n * { eventType: 'request.end' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * console.log(result.summary);\n * // Output: \"✓ Rpc Request: 3 events recorded (152ms)\"\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 (!params.workflow.startsWith('wf_')) {\n try {\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 const created = await this.createWorkflow({\n name: params.workflow.replace(/[_-]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()),\n slug: params.workflow,\n productSurface: 'CUSTOM',\n });\n workflowId = created.id;\n workflowName = created.name;\n }\n } catch {\n // Workflow resolution failed — fall back to using the slug directly.\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 : deterministicIdempotencyKey('run', run.id, event.eventType, index),\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// Default export for convenience\nexport default Drip;\n\n// ============================================================================\n// Pre-initialized Singleton\n// ============================================================================\n\n/**\n * Pre-initialized Drip client singleton.\n *\n * Reads configuration from environment variables:\n * - `DRIP_API_KEY` (required)\n * - `DRIP_BASE_URL` (optional)\n *\n * @example\n * ```typescript\n * import { drip } from '@drip-sdk/node';\n *\n * // One line to track usage\n * await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });\n * ```\n *\n * @throws {Error} on first use if DRIP_API_KEY is not set\n */\nlet _singleton: Drip | null = null;\n\nfunction getSingleton(): Drip {\n if (!_singleton) {\n _singleton = new Drip();\n }\n return _singleton;\n}\n\n/**\n * Pre-initialized Drip client singleton.\n *\n * Uses lazy initialization - only creates the client when first accessed.\n * Reads `DRIP_API_KEY` from environment variables.\n *\n * @example\n * ```typescript\n * import { drip } from '@drip-sdk/node';\n *\n * // Track usage with one line\n * await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });\n *\n * // Record a run\n * await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'agent-run',\n * events: [{ eventType: 'llm.call', quantity: 1000, units: 'tokens' }],\n * status: 'COMPLETED',\n * });\n * ```\n */\nexport const drip: Drip = new Proxy({} as Drip, {\n get(_target, prop) {\n const instance = getSingleton();\n const value = instance[prop as keyof Drip];\n if (typeof value === 'function') {\n return value.bind(instance);\n }\n return value;\n },\n});\n"]}
1
+ {"version":3,"sources":["../src/idempotency.ts","../src/core.ts"],"names":["_callCounter","deterministicIdempotencyKey","prefix","components","seq","parts","c","hash","createHash","DripError","_DripError","message","statusCode","code","Drip","config","apiKey","baseUrl","path","options","controller","timeoutId","res","data","error","healthBaseUrl","start","response","latencyMs","status","timestamp","params","customerId","query","idempotencyKey","runId","events","err","startTime","workflowId","workflowName","workflows","match","w","created","run","eventsCreated","eventsDuplicates","batchEvents","evt","i","batchResult","endResult","durationMs","statusIcon","core_default","_singleton","getSingleton","drip","_target","prop","instance","value"],"mappings":"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","file":"core.cjs","sourcesContent":["/**\n * Deterministic idempotency key generation for SDK calls.\n *\n * Keys are:\n * - **Unique per call** — a monotonic counter ensures two rapid calls with\n * identical parameters produce different keys.\n * - **Stable across retries** — the key is generated once per SDK method\n * invocation and reused for every retry attempt.\n * - **Deterministic** — no randomness; keys are reproducible given the same\n * counter state.\n *\n * @internal\n */\nimport { createHash } from 'crypto';\n\nlet _callCounter = 0;\n\n/**\n * Generate a deterministic, unique idempotency key.\n *\n * @param prefix - Short prefix for the key type (e.g. `chg`, `track`, `evt`, `run`, `stream`)\n * @param components - Call-specific values (customerId, meter, quantity, etc.)\n * @returns A key like `chg_<24-char hex hash>`\n */\nexport function deterministicIdempotencyKey(\n prefix: string,\n ...components: Array<string | number | undefined>\n): string {\n const seq = ++_callCounter;\n const parts = components.filter((c) => c !== undefined).map(String);\n parts.push(String(seq));\n const hash = createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 24);\n return `${prefix}_${hash}`;\n}\n\n/**\n * Reset counter — only for tests.\n * @internal\n */\nexport function _resetCallCounter(): void {\n _callCounter = 0;\n}\n","/**\n * Drip SDK Core - Essential API for pilots and new integrations\n *\n * This SDK focuses on two core concepts:\n * - **Usage tracking**: trackUsage() for recording usage without billing\n * - **Execution logging**: recordRun() and related methods for tracking runs/events\n *\n * For billing, webhooks, cost estimation, and advanced features:\n * `import { Drip } from '@drip-sdk/node'`\n *\n * @packageDocumentation\n */\n\nimport { deterministicIdempotencyKey } from './idempotency.js';\n\n// ============================================================================\n// Configuration Types\n// ============================================================================\n\n/**\n * Configuration options for the Drip SDK client.\n *\n * All fields are optional - the SDK will read from environment variables:\n * - `DRIP_API_KEY` - Your Drip API key\n * - `DRIP_BASE_URL` - Override API base URL (optional)\n */\nexport interface DripConfig {\n /**\n * Your Drip API key. Obtain this from the Drip dashboard.\n * Falls back to `DRIP_API_KEY` environment variable if not provided.\n *\n * Supports both key types:\n * - **Secret keys** (`sk_live_...` / `sk_test_...`): Full access to all endpoints\n * - **Public keys** (`pk_live_...` / `pk_test_...`): Safe for client-side use.\n * Can access usage tracking, customers, runs, and events.\n *\n * @example \"sk_live_abc123...\" or \"pk_live_abc123...\"\n */\n apiKey?: string;\n\n /**\n * Base URL for the Drip API. Defaults to production API.\n * Falls back to `DRIP_BASE_URL` environment variable if not provided.\n * @default \"https://drip-app-hlunj.ondigitalocean.app/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 * At least one of `externalCustomerId` or `onchainAddress` is required.\n * @example \"user_12345\"\n */\n externalCustomerId?: string;\n\n /**\n * The customer's Drip Smart Account address (derived from their EOA).\n * At least one of `externalCustomerId` or `onchainAddress` is required.\n * @example \"0x1234567890abcdef...\"\n */\n onchainAddress?: string;\n\n /**\n * Whether this customer is internal-only (usage tracked but not billed).\n * @default false\n */\n isInternal?: boolean;\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 (optional - may not be returned by all endpoints) */\n businessId?: string;\n\n /** Your external customer ID (if provided) */\n externalCustomerId: string | null;\n\n /** Customer's on-chain address (null for internal-only customers) */\n onchainAddress: string | null;\n\n /** Whether this customer is internal-only (usage tracked but not billed) */\n isInternal?: boolean;\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// Usage Tracking Types\n// ============================================================================\n\n/**\n * Parameters for tracking usage without billing.\n */\nexport interface TrackUsageParams {\n /**\n * The Drip customer ID to track usage for.\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 * Auto-generated if not provided, ensuring every call is individually trackable.\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// Run & Event Types (Execution Ledger)\n// ============================================================================\n\n/**\n * Parameters for starting a new 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 * Possible run statuses.\n */\nexport type RunStatus =\n | 'PENDING'\n | 'RUNNING'\n | 'COMPLETED'\n | 'FAILED'\n | 'CANCELLED'\n | 'TIMEOUT';\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 * Result of ending a run.\n */\nexport interface EndRunResult {\n id: string;\n status: RunStatus;\n endedAt: string | null;\n durationMs: number | null;\n eventCount: number;\n totalCostUnits: string | null;\n}\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., \"request.start\", \"llm.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 /** 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 */\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 * A single event to record in a run.\n */\nexport interface RecordRunEvent {\n /** Event type (e.g., \"request.start\", \"llm.call\", \"request.end\") */\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 */\nexport interface RecordRunParams {\n /** Customer ID this run belongs to */\n customerId: string;\n\n /**\n * Workflow/request type identifier. Examples:\n * - \"rpc-request\" for RPC providers\n * - \"api-request\" for API providers\n * - \"agent-run\" for AI agents\n *\n * Auto-creates if it doesn't exist.\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 from GET /runs/:id/timeline.\n */\nexport interface RunTimeline {\n runId: string;\n workflowId: string | null;\n customerId: string;\n status: RunStatus;\n startedAt: string | null;\n endedAt: string | null;\n durationMs: number | null;\n events: Array<{\n id: string;\n eventType: string;\n actionName: string | null;\n outcome: 'SUCCESS' | 'FAILED' | 'PENDING' | 'TIMEOUT' | 'RETRYING';\n explanation: string | null;\n description: string | null;\n timestamp: string;\n durationMs: number | null;\n parentEventId: string | null;\n retryOfEventId: string | null;\n attemptNumber: number;\n retriedByEventId: string | null;\n costUsdc: string | null;\n isRetry: boolean;\n retryChain: {\n totalAttempts: number;\n finalOutcome: string;\n events: string[];\n } | null;\n metadata: {\n usageType: string;\n quantity: number;\n units: string | null;\n } | null;\n }>;\n anomalies: Array<{\n id: string;\n type: string;\n severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';\n title: string;\n explanation: string;\n relatedEventIds: string[];\n detectedAt: string;\n status: 'OPEN' | 'INVESTIGATING' | 'RESOLVED' | 'FALSE_POSITIVE' | 'IGNORED';\n }>;\n summary: {\n totalEvents: number;\n byType: Record<string, number>;\n byOutcome: Record<string, number>;\n retriedEvents: number;\n failedEvents: number;\n totalCostUsdc: string | null;\n };\n hasMore: boolean;\n nextCursor: string | null;\n}\n\n/**\n * Run details response from GET /runs/:id.\n */\nexport interface RunDetails {\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 totals: {\n eventCount: number;\n totalQuantity: string;\n totalCostUnits: string;\n };\n _links: {\n timeline: string;\n };\n}\n\n// ============================================================================\n// Internal Types (used by recordRun)\n// ============================================================================\n\ninterface 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\ninterface CreateWorkflowParams {\n name: string;\n slug: string;\n productSurface?: 'API' | 'RPC' | 'WEBHOOK' | 'AGENT' | 'PIPELINE' | 'CUSTOM';\n description?: string;\n metadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * Error thrown by Drip SDK operations.\n */\nexport class DripError extends Error {\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// Core SDK Class\n// ============================================================================\n\n/**\n * Drip SDK Core - Essential API for pilots and new integrations.\n *\n * Two core concepts:\n * - **Usage tracking**: `trackUsage()` - record usage without billing\n * - **Execution logging**: `recordRun()` - track request/run lifecycle with events\n *\n * For billing (`charge()`), webhooks, and advanced features:\n * ```typescript\n * import { Drip } from '@drip-sdk/node';\n * ```\n *\n * @example\n * ```typescript\n * import { Drip } from '@drip-sdk/node/core';\n *\n * const drip = new Drip({ apiKey: process.env.DRIP_API_KEY! });\n *\n * // Verify connection\n * const health = await drip.ping();\n * console.log(`API healthy: ${health.ok}`);\n *\n * // Track usage (no billing)\n * await drip.trackUsage({\n * customerId: 'cust_123',\n * meter: 'api_calls',\n * quantity: 1,\n * });\n *\n * // Record a complete request/run with events\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'rpc-request', // or 'api-request', 'agent-run'\n * events: [\n * { eventType: 'request.start' },\n * { eventType: 'llm.call', quantity: 1500, units: 'tokens' },\n * { eventType: 'request.end' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * console.log(result.summary);\n * ```\n */\nexport class Drip {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n /**\n * The type of API key being used.\n *\n * - `'secret'` — Full access (sk_live_... / sk_test_...)\n * - `'public'` — Client-safe, restricted access (pk_live_... / pk_test_...)\n * - `'unknown'` — Key format not recognized (legacy or custom)\n */\n readonly keyType: 'secret' | 'public' | 'unknown';\n\n /**\n * Creates a new Drip SDK client.\n *\n * @param config - Configuration options (all optional, reads from env vars)\n * @throws {Error} If apiKey is not provided and DRIP_API_KEY env var is not set\n *\n * @example\n * ```typescript\n * // Option 1: Explicit config\n * const drip = new Drip({ apiKey: 'your-api-key' });\n *\n * // Option 2: Auto-config from environment (recommended)\n * // Set DRIP_API_KEY env var, then:\n * const drip = new Drip();\n *\n * // Option 3: Use pre-initialized singleton\n * import { drip } from '@drip-sdk/node';\n * ```\n */\n constructor(config: DripConfig = {}) {\n // Read from config or fall back to environment variables\n const apiKey = config.apiKey ?? (typeof process !== 'undefined' ? process.env.DRIP_API_KEY : undefined);\n const baseUrl = config.baseUrl ?? (typeof process !== 'undefined' ? process.env.DRIP_BASE_URL : undefined);\n\n if (!apiKey) {\n throw new Error(\n 'Drip API key is required. Either pass { apiKey } to constructor or set DRIP_API_KEY environment variable.'\n );\n }\n\n this.apiKey = apiKey;\n this.baseUrl = baseUrl || 'https://drip-app-hlunj.ondigitalocean.app/v1';\n this.timeout = config.timeout || 30000;\n\n // Detect key type from prefix\n if (apiKey.startsWith('sk_')) {\n this.keyType = 'secret';\n } else if (apiKey.startsWith('pk_')) {\n this.keyType = 'public';\n } else {\n this.keyType = 'unknown';\n }\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 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\n // ==========================================================================\n\n /**\n * Pings the Drip API to check connectivity and measure latency.\n *\n * Use this to verify:\n * - API key is valid\n * - Base URL is correct\n * - Network connectivity works\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 * } else {\n * console.error(`API unhealthy: ${health.status}`);\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 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 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 status = response.ok ? 'healthy' : `error:${response.status}`;\n }\n\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 * });\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 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 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 // Usage Tracking (No Billing)\n // ==========================================================================\n\n /**\n * Records usage for tracking WITHOUT billing.\n *\n * Use this for:\n * - Pilot programs (track before billing)\n * - Internal team usage\n * - Pre-billing tracking before customer setup\n *\n * For actual billing, use `charge()` from the full SDK.\n *\n * @param params - 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 pilot',\n * });\n *\n * console.log(`Tracked: ${result.usageEventId}`);\n * ```\n */\n async trackUsage(params: TrackUsageParams): Promise<TrackUsageResult> {\n const idempotencyKey = params.idempotencyKey\n ?? deterministicIdempotencyKey('track', params.customerId, params.meter, params.quantity);\n\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,\n units: params.units,\n description: params.description,\n metadata: params.metadata,\n }),\n });\n }\n\n // ==========================================================================\n // Private Workflow Methods (used by recordRun)\n // ==========================================================================\n\n private async createWorkflow(params: CreateWorkflowParams): Promise<Workflow> {\n return this.request<Workflow>('/workflows', {\n method: 'POST',\n body: JSON.stringify(params),\n });\n }\n\n private async listWorkflows(): Promise<{ data: Workflow[]; count: number }> {\n return this.request<{ data: Workflow[]; count: number }>('/workflows');\n }\n\n // ==========================================================================\n // Run & Event Methods (Execution Ledger)\n // ==========================================================================\n\n /**\n * Starts a new 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 * });\n *\n * // Emit events during execution...\n * await drip.emitEvent({ runId: run.id, eventType: 'llm.call', quantity: 1000 });\n *\n * // End the run\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 async endRun(runId: string, params: EndRunParams): Promise<EndRunResult> {\n return this.request<EndRunResult>(`/runs/${runId}`, {\n method: 'PATCH',\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Gets run details with summary totals.\n *\n * For full event history with retry chains and anomalies, use `getRunTimeline()`.\n *\n * @param runId - The run ID\n * @returns Run details with totals\n *\n * @example\n * ```typescript\n * const run = await drip.getRun('run_abc123');\n * console.log(`Status: ${run.status}, Events: ${run.totals.eventCount}`);\n * ```\n */\n async getRun(runId: string): Promise<RunDetails> {\n return this.request<RunDetails>(`/runs/${runId}`);\n }\n\n /**\n * Gets a run's full timeline with events, anomalies, and analytics.\n *\n * @param runId - The run ID\n * @param options - Pagination and filtering options\n * @returns Full timeline with events, anomalies, and summary\n *\n * @example\n * ```typescript\n * const timeline = await drip.getRunTimeline('run_abc123');\n *\n * console.log(`Status: ${timeline.status}`);\n * console.log(`Events: ${timeline.summary.totalEvents}`);\n *\n * for (const event of timeline.events) {\n * console.log(`${event.eventType}: ${event.outcome}`);\n * }\n * ```\n */\n async getRunTimeline(\n runId: string,\n options?: { limit?: number; cursor?: string; includeAnomalies?: boolean; collapseRetries?: boolean },\n ): Promise<RunTimeline> {\n const params = new URLSearchParams();\n if (options?.limit) params.set('limit', options.limit.toString());\n if (options?.cursor) params.set('cursor', options.cursor);\n if (options?.includeAnomalies !== undefined) params.set('includeAnomalies', String(options.includeAnomalies));\n if (options?.collapseRetries !== undefined) params.set('collapseRetries', String(options.collapseRetries));\n\n const query = params.toString();\n const path = query ? `/runs/${runId}/timeline?${query}` : `/runs/${runId}/timeline`;\n\n return this.request<RunTimeline>(path);\n }\n\n /**\n * Emits an event to a run.\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: 'llm.call',\n * quantity: 1500,\n * units: 'tokens',\n * description: 'GPT-4 completion',\n * });\n * ```\n */\n async emitEvent(params: EmitEventParams): Promise<EventResult> {\n const idempotencyKey = params.idempotencyKey\n ?? deterministicIdempotencyKey('evt', params.runId, params.eventType, params.quantity);\n\n return this.request<EventResult>('/run-events', {\n method: 'POST',\n body: JSON.stringify({ ...params, idempotencyKey }),\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 async emitEventsBatch(\n events: Array<EmitEventParams>,\n ): Promise<{\n success: boolean;\n created: number;\n duplicates: number;\n skipped: number;\n events: Array<{ id: string; eventType: string; isDuplicate: boolean; skipped?: boolean; reason?: string }>;\n }> {\n return this.request('/run-events/batch', {\n method: 'POST',\n body: JSON.stringify({ events }),\n });\n }\n\n /**\n * Records a complete request/run in a single call.\n *\n * This is the **hero method** for tracking execution. It combines:\n * - Workflow creation (auto-creates if needed)\n * - Run creation\n * - Event emission\n * - Run completion\n *\n * @param params - Run parameters including events\n * @returns The created run with event summary\n *\n * @example\n * ```typescript\n * // RPC provider example\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'rpc-request',\n * events: [\n * { eventType: 'request.start' },\n * { eventType: 'eth_call', quantity: 1 },\n * { eventType: 'request.end' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * // API provider example\n * const result = await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'api-request',\n * events: [\n * { eventType: 'request.start' },\n * { eventType: 'llm.call', quantity: 2000, units: 'tokens' },\n * { eventType: 'request.end' },\n * ],\n * status: 'COMPLETED',\n * });\n *\n * console.log(result.summary);\n * // Output: \"✓ Rpc Request: 3 events recorded (152ms)\"\n * ```\n */\n async recordRun(params: RecordRunParams): Promise<RecordRunResult> {\n // Try single-call endpoint first; fall back to 4-step orchestration\n // if the server doesn't support it yet (404).\n try {\n return await this.request<RecordRunResult>('/runs/record', {\n method: 'POST',\n body: JSON.stringify({\n customerId: params.customerId,\n workflow: params.workflow,\n events: params.events,\n status: params.status,\n errorMessage: params.errorMessage,\n errorCode: params.errorCode,\n externalRunId: params.externalRunId,\n correlationId: params.correlationId,\n metadata: params.metadata,\n }),\n });\n } catch (err) {\n if (err instanceof DripError && err.statusCode === 404) {\n return this._recordRunFallback(params);\n }\n throw err;\n }\n }\n\n /**\n * 4-step orchestration fallback for servers without POST /runs/record.\n * @internal\n */\n private async _recordRunFallback(params: RecordRunParams): Promise<RecordRunResult> {\n const startTime = Date.now();\n\n // Step 1: Resolve workflow\n let workflowId = params.workflow;\n let workflowName = params.workflow;\n const { data: workflows } = await this.listWorkflows();\n const match = workflows.find(\n (w) => w.slug === params.workflow || w.id === params.workflow,\n );\n if (match) {\n workflowId = match.id;\n workflowName = match.name;\n } else {\n const created = await this.request<Workflow>('/workflows', {\n method: 'POST',\n body: JSON.stringify({\n name: params.workflow.replace(/[_-]/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()),\n slug: params.workflow,\n productSurface: 'CUSTOM',\n }),\n });\n workflowId = created.id;\n workflowName = created.name;\n }\n\n // Step 2: Start run\n const run = await this.startRun({\n customerId: params.customerId,\n workflowId,\n correlationId: params.correlationId,\n externalRunId: params.externalRunId,\n metadata: params.metadata,\n });\n\n // Step 3: Emit events\n let eventsCreated = 0;\n let eventsDuplicates = 0;\n if (params.events.length > 0) {\n const batchEvents = params.events.map((evt, i) => ({\n runId: run.id,\n eventType: evt.eventType,\n quantity: evt.quantity ?? 1,\n units: evt.units,\n description: evt.description,\n costUnits: evt.costUnits,\n metadata: evt.metadata,\n idempotencyKey: params.externalRunId\n ? `${params.externalRunId}:${evt.eventType}:${i}`\n : undefined,\n }));\n const batchResult = await this.emitEventsBatch(batchEvents);\n eventsCreated = batchResult.created;\n eventsDuplicates = batchResult.duplicates;\n }\n\n // Step 4: End 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 const statusIcon = params.status === 'COMPLETED' ? '\\u2713' : params.status === 'FAILED' ? '\\u2717' : '\\u25CB';\n\n return {\n run: {\n id: run.id,\n workflowId,\n workflowName,\n status: endResult.status as RunStatus,\n durationMs: endResult.durationMs ?? durationMs,\n },\n events: { created: eventsCreated, duplicates: eventsDuplicates },\n totalCostUnits: endResult.totalCostUnits ?? null,\n summary: `${statusIcon} ${workflowName}: ${eventsCreated} events recorded (${endResult.durationMs ?? durationMs}ms)`,\n };\n }\n}\n\n// Default export for convenience\nexport default Drip;\n\n// ============================================================================\n// Pre-initialized Singleton\n// ============================================================================\n\n/**\n * Pre-initialized Drip client singleton.\n *\n * Reads configuration from environment variables:\n * - `DRIP_API_KEY` (required)\n * - `DRIP_BASE_URL` (optional)\n *\n * @example\n * ```typescript\n * import { drip } from '@drip-sdk/node';\n *\n * // One line to track usage\n * await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });\n * ```\n *\n * @throws {Error} on first use if DRIP_API_KEY is not set\n */\nlet _singleton: Drip | null = null;\n\nfunction getSingleton(): Drip {\n if (!_singleton) {\n _singleton = new Drip();\n }\n return _singleton;\n}\n\n/**\n * Pre-initialized Drip client singleton.\n *\n * Uses lazy initialization - only creates the client when first accessed.\n * Reads `DRIP_API_KEY` from environment variables.\n *\n * @example\n * ```typescript\n * import { drip } from '@drip-sdk/node';\n *\n * // Track usage with one line\n * await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });\n *\n * // Record a run\n * await drip.recordRun({\n * customerId: 'cust_123',\n * workflow: 'agent-run',\n * events: [{ eventType: 'llm.call', quantity: 1000, units: 'tokens' }],\n * status: 'COMPLETED',\n * });\n * ```\n */\nexport const drip: Drip = new Proxy({} as Drip, {\n get(_target, prop) {\n const instance = getSingleton();\n const value = instance[prop as keyof Drip];\n if (typeof value === 'function') {\n return value.bind(instance);\n }\n return value;\n },\n});\n"]}
package/dist/core.d.cts CHANGED
@@ -48,14 +48,21 @@ interface DripConfig {
48
48
  interface CreateCustomerParams {
49
49
  /**
50
50
  * Your internal customer/user ID for reconciliation.
51
+ * At least one of `externalCustomerId` or `onchainAddress` is required.
51
52
  * @example "user_12345"
52
53
  */
53
54
  externalCustomerId?: string;
54
55
  /**
55
56
  * The customer's Drip Smart Account address (derived from their EOA).
57
+ * At least one of `externalCustomerId` or `onchainAddress` is required.
56
58
  * @example "0x1234567890abcdef..."
57
59
  */
58
- onchainAddress: string;
60
+ onchainAddress?: string;
61
+ /**
62
+ * Whether this customer is internal-only (usage tracked but not billed).
63
+ * @default false
64
+ */
65
+ isInternal?: boolean;
59
66
  /**
60
67
  * Additional metadata to store with the customer.
61
68
  */
@@ -71,8 +78,10 @@ interface Customer {
71
78
  businessId?: string;
72
79
  /** Your external customer ID (if provided) */
73
80
  externalCustomerId: string | null;
74
- /** Customer's on-chain address */
75
- onchainAddress: string;
81
+ /** Customer's on-chain address (null for internal-only customers) */
82
+ onchainAddress: string | null;
83
+ /** Whether this customer is internal-only (usage tracked but not billed) */
84
+ isInternal?: boolean;
76
85
  /** Custom metadata */
77
86
  metadata: Record<string, unknown> | null;
78
87
  /** ISO timestamp of creation */
@@ -731,6 +740,11 @@ declare class Drip {
731
740
  * ```
732
741
  */
733
742
  recordRun(params: RecordRunParams): Promise<RecordRunResult>;
743
+ /**
744
+ * 4-step orchestration fallback for servers without POST /runs/record.
745
+ * @internal
746
+ */
747
+ private _recordRunFallback;
734
748
  }
735
749
 
736
750
  /**
package/dist/core.d.ts CHANGED
@@ -48,14 +48,21 @@ interface DripConfig {
48
48
  interface CreateCustomerParams {
49
49
  /**
50
50
  * Your internal customer/user ID for reconciliation.
51
+ * At least one of `externalCustomerId` or `onchainAddress` is required.
51
52
  * @example "user_12345"
52
53
  */
53
54
  externalCustomerId?: string;
54
55
  /**
55
56
  * The customer's Drip Smart Account address (derived from their EOA).
57
+ * At least one of `externalCustomerId` or `onchainAddress` is required.
56
58
  * @example "0x1234567890abcdef..."
57
59
  */
58
- onchainAddress: string;
60
+ onchainAddress?: string;
61
+ /**
62
+ * Whether this customer is internal-only (usage tracked but not billed).
63
+ * @default false
64
+ */
65
+ isInternal?: boolean;
59
66
  /**
60
67
  * Additional metadata to store with the customer.
61
68
  */
@@ -71,8 +78,10 @@ interface Customer {
71
78
  businessId?: string;
72
79
  /** Your external customer ID (if provided) */
73
80
  externalCustomerId: string | null;
74
- /** Customer's on-chain address */
75
- onchainAddress: string;
81
+ /** Customer's on-chain address (null for internal-only customers) */
82
+ onchainAddress: string | null;
83
+ /** Whether this customer is internal-only (usage tracked but not billed) */
84
+ isInternal?: boolean;
76
85
  /** Custom metadata */
77
86
  metadata: Record<string, unknown> | null;
78
87
  /** ISO timestamp of creation */
@@ -731,6 +740,11 @@ declare class Drip {
731
740
  * ```
732
741
  */
733
742
  recordRun(params: RecordRunParams): Promise<RecordRunResult>;
743
+ /**
744
+ * 4-step orchestration fallback for servers without POST /runs/record.
745
+ * @internal
746
+ */
747
+ private _recordRunFallback;
734
748
  }
735
749
 
736
750
  /**
package/dist/core.js CHANGED
@@ -1,3 +1,3 @@
1
- import {createHash}from'crypto';var k=0;function y(m,...t){let e=++k,n=t.filter(r=>r!==void 0).map(String);n.push(String(e));let s=createHash("sha256").update(n.join("|")).digest("hex").slice(0,24);return `${m}_${s}`}var u=class m extends Error{constructor(e,n,s){super(e);this.statusCode=n;this.code=s;this.name="DripError",Object.setPrototypeOf(this,m.prototype);}},f=class{apiKey;baseUrl;timeout;keyType;constructor(t={}){let e=t.apiKey??(typeof process<"u"?process.env.DRIP_API_KEY:void 0),n=t.baseUrl??(typeof process<"u"?process.env.DRIP_BASE_URL:void 0);if(!e)throw new Error("Drip API key is required. Either pass { apiKey } to constructor or set DRIP_API_KEY environment variable.");this.apiKey=e,this.baseUrl=n||"https://drip-app-hlunj.ondigitalocean.app/v1",this.timeout=t.timeout||3e4,e.startsWith("sk_")?this.keyType="secret":e.startsWith("pk_")?this.keyType="public":this.keyType="unknown";}async request(t,e={}){let n=new AbortController,s=setTimeout(()=>n.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${t}`,{...e,signal:n.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...e.headers}});if(r.status===204)return {success:!0};let o=await r.json();if(!r.ok)throw new u(o.message||o.error||"Request failed",r.status,o.code);return o}catch(r){throw r instanceof u?r:r instanceof Error&&r.name==="AbortError"?new u("Request timed out",408,"TIMEOUT"):new u(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(s);}}async ping(){let t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),n=this.baseUrl;n.endsWith("/v1/")?n=n.slice(0,-4):n.endsWith("/v1")&&(n=n.slice(0,-3)),n=n.replace(/\/+$/,"");let s=Date.now();try{let r=await fetch(`${n}/health`,{signal:t.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),o=Date.now()-s,a="unknown",l=Date.now();try{let c=await r.json();typeof c.status=="string"&&(a=c.status),typeof c.timestamp=="number"&&(l=c.timestamp);}catch{a=r.ok?"healthy":`error:${r.status}`;}return !r.ok&&a==="unknown"&&(a=`error:${r.status}`),{ok:r.ok&&a==="healthy",status:a,latencyMs:o,timestamp:l}}catch(r){throw r instanceof Error&&r.name==="AbortError"?new u("Request timed out",408,"TIMEOUT"):new u(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(e);}}async createCustomer(t){return this.request("/customers",{method:"POST",body:JSON.stringify(t)})}async getCustomer(t){return this.request(`/customers/${t}`)}async listCustomers(t){let e=new URLSearchParams;t?.limit&&e.set("limit",t.limit.toString()),t?.status&&e.set("status",t.status);let n=e.toString(),s=n?`/customers?${n}`:"/customers";return this.request(s)}async trackUsage(t){let e=t.idempotencyKey??y("track",t.customerId,t.meter,t.quantity);return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:e,units:t.units,description:t.description,metadata:t.metadata})})}async createWorkflow(t){return this.request("/workflows",{method:"POST",body:JSON.stringify(t)})}async listWorkflows(){return this.request("/workflows")}async startRun(t){return this.request("/runs",{method:"POST",body:JSON.stringify(t)})}async endRun(t,e){return this.request(`/runs/${t}`,{method:"PATCH",body:JSON.stringify(e)})}async getRun(t){return this.request(`/runs/${t}`)}async getRunTimeline(t,e){let n=new URLSearchParams;e?.limit&&n.set("limit",e.limit.toString()),e?.cursor&&n.set("cursor",e.cursor),e?.includeAnomalies!==void 0&&n.set("includeAnomalies",String(e.includeAnomalies)),e?.collapseRetries!==void 0&&n.set("collapseRetries",String(e.collapseRetries));let s=n.toString(),r=s?`/runs/${t}/timeline?${s}`:`/runs/${t}/timeline`;return this.request(r)}async emitEvent(t){let e=t.idempotencyKey??y("evt",t.runId,t.eventType,t.quantity);return this.request("/run-events",{method:"POST",body:JSON.stringify({...t,idempotencyKey:e})})}async emitEventsBatch(t){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:t})})}async recordRun(t){let e=Date.now(),n=t.workflow,s=t.workflow;if(!t.workflow.startsWith("wf_"))try{let d=(await this.listWorkflows()).data.find(i=>i.slug===t.workflow||i.id===t.workflow);if(d)n=d.id,s=d.name;else {let i=await this.createWorkflow({name:t.workflow.replace(/[_-]/g," ").replace(/\b\w/g,g=>g.toUpperCase()),slug:t.workflow,productSurface:"CUSTOM"});n=i.id,s=i.name;}}catch{n=t.workflow;}let r=await this.startRun({customerId:t.customerId,workflowId:n,externalRunId:t.externalRunId,correlationId:t.correlationId,metadata:t.metadata}),o=0,a=0;if(t.events.length>0){let w=t.events.map((i,g)=>({runId:r.id,eventType:i.eventType,quantity:i.quantity,units:i.units,description:i.description,costUnits:i.costUnits,metadata:i.metadata,idempotencyKey:t.externalRunId?`${t.externalRunId}:${i.eventType}:${g}`:y("run",r.id,i.eventType,g)})),d=await this.emitEventsBatch(w);o=d.created,a=d.duplicates;}let l=await this.endRun(r.id,{status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode}),c=Date.now()-e,R=t.events.length>0?`${o} events recorded`:"no events",h=`${t.status==="COMPLETED"?"\u2713":t.status==="FAILED"?"\u2717":"\u25CB"} ${s}: ${R} (${l.durationMs??c}ms)`;return {run:{id:r.id,workflowId:n,workflowName:s,status:t.status,durationMs:l.durationMs},events:{created:o,duplicates:a},totalCostUnits:l.totalCostUnits,summary:h}}},U=f,p=null;function C(){return p||(p=new f),p}var O=new Proxy({},{get(m,t){let e=C(),n=e[t];return typeof n=="function"?n.bind(e):n}});
2
- export{f as Drip,u as DripError,U as default,O as drip};//# sourceMappingURL=core.js.map
1
+ import {createHash}from'crypto';var C=0;function p(m,...t){let e=++C,n=t.filter(r=>r!==void 0).map(String);n.push(String(e));let s=createHash("sha256").update(n.join("|")).digest("hex").slice(0,24);return `${m}_${s}`}var a=class m extends Error{constructor(e,n,s){super(e);this.statusCode=n;this.code=s;this.name="DripError",Object.setPrototypeOf(this,m.prototype);}},f=class{apiKey;baseUrl;timeout;keyType;constructor(t={}){let e=t.apiKey??(typeof process<"u"?process.env.DRIP_API_KEY:void 0),n=t.baseUrl??(typeof process<"u"?process.env.DRIP_BASE_URL:void 0);if(!e)throw new Error("Drip API key is required. Either pass { apiKey } to constructor or set DRIP_API_KEY environment variable.");this.apiKey=e,this.baseUrl=n||"https://drip-app-hlunj.ondigitalocean.app/v1",this.timeout=t.timeout||3e4,e.startsWith("sk_")?this.keyType="secret":e.startsWith("pk_")?this.keyType="public":this.keyType="unknown";}async request(t,e={}){let n=new AbortController,s=setTimeout(()=>n.abort(),this.timeout);try{let r=await fetch(`${this.baseUrl}${t}`,{...e,signal:n.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...e.headers}});if(r.status===204)return {success:!0};let i=await r.json();if(!r.ok)throw new a(i.message||i.error||"Request failed",r.status,i.code);return i}catch(r){throw r instanceof a?r:r instanceof Error&&r.name==="AbortError"?new a("Request timed out",408,"TIMEOUT"):new a(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(s);}}async ping(){let t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),n=this.baseUrl;n.endsWith("/v1/")?n=n.slice(0,-4):n.endsWith("/v1")&&(n=n.slice(0,-3)),n=n.replace(/\/+$/,"");let s=Date.now();try{let r=await fetch(`${n}/health`,{signal:t.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),i=Date.now()-s,o="unknown",d=Date.now();try{let u=await r.json();typeof u.status=="string"&&(o=u.status),typeof u.timestamp=="number"&&(d=u.timestamp);}catch{o=r.ok?"healthy":`error:${r.status}`;}return !r.ok&&o==="unknown"&&(o=`error:${r.status}`),{ok:r.ok&&o==="healthy",status:o,latencyMs:i,timestamp:d}}catch(r){throw r instanceof Error&&r.name==="AbortError"?new a("Request timed out",408,"TIMEOUT"):new a(r instanceof Error?r.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(e);}}async createCustomer(t){return this.request("/customers",{method:"POST",body:JSON.stringify(t)})}async getCustomer(t){return this.request(`/customers/${t}`)}async listCustomers(t){let e=new URLSearchParams;t?.limit&&e.set("limit",t.limit.toString()),t?.status&&e.set("status",t.status);let n=e.toString(),s=n?`/customers?${n}`:"/customers";return this.request(s)}async trackUsage(t){let e=t.idempotencyKey??p("track",t.customerId,t.meter,t.quantity);return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:e,units:t.units,description:t.description,metadata:t.metadata})})}async createWorkflow(t){return this.request("/workflows",{method:"POST",body:JSON.stringify(t)})}async listWorkflows(){return this.request("/workflows")}async startRun(t){return this.request("/runs",{method:"POST",body:JSON.stringify(t)})}async endRun(t,e){return this.request(`/runs/${t}`,{method:"PATCH",body:JSON.stringify(e)})}async getRun(t){return this.request(`/runs/${t}`)}async getRunTimeline(t,e){let n=new URLSearchParams;e?.limit&&n.set("limit",e.limit.toString()),e?.cursor&&n.set("cursor",e.cursor),e?.includeAnomalies!==void 0&&n.set("includeAnomalies",String(e.includeAnomalies)),e?.collapseRetries!==void 0&&n.set("collapseRetries",String(e.collapseRetries));let s=n.toString(),r=s?`/runs/${t}/timeline?${s}`:`/runs/${t}/timeline`;return this.request(r)}async emitEvent(t){let e=t.idempotencyKey??p("evt",t.runId,t.eventType,t.quantity);return this.request("/run-events",{method:"POST",body:JSON.stringify({...t,idempotencyKey:e})})}async emitEventsBatch(t){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:t})})}async recordRun(t){try{return await this.request("/runs/record",{method:"POST",body:JSON.stringify({customerId:t.customerId,workflow:t.workflow,events:t.events,status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode,externalRunId:t.externalRunId,correlationId:t.correlationId,metadata:t.metadata})})}catch(e){if(e instanceof a&&e.statusCode===404)return this._recordRunFallback(t);throw e}}async _recordRunFallback(t){let e=Date.now(),n=t.workflow,s=t.workflow,{data:r}=await this.listWorkflows(),i=r.find(l=>l.slug===t.workflow||l.id===t.workflow);if(i)n=i.id,s=i.name;else {let l=await this.request("/workflows",{method:"POST",body:JSON.stringify({name:t.workflow.replace(/[_-]/g," ").replace(/\b\w/g,y=>y.toUpperCase()),slug:t.workflow,productSurface:"CUSTOM"})});n=l.id,s=l.name;}let o=await this.startRun({customerId:t.customerId,workflowId:n,correlationId:t.correlationId,externalRunId:t.externalRunId,metadata:t.metadata}),d=0,u=0;if(t.events.length>0){let l=t.events.map((c,I)=>({runId:o.id,eventType:c.eventType,quantity:c.quantity??1,units:c.units,description:c.description,costUnits:c.costUnits,metadata:c.metadata,idempotencyKey:t.externalRunId?`${t.externalRunId}:${c.eventType}:${I}`:void 0})),y=await this.emitEventsBatch(l);d=y.created,u=y.duplicates;}let g=await this.endRun(o.id,{status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode}),w=Date.now()-e,h=t.status==="COMPLETED"?"\u2713":t.status==="FAILED"?"\u2717":"\u25CB";return {run:{id:o.id,workflowId:n,workflowName:s,status:g.status,durationMs:g.durationMs??w},events:{created:d,duplicates:u},totalCostUnits:g.totalCostUnits??null,summary:`${h} ${s}: ${d} events recorded (${g.durationMs??w}ms)`}}},O=f,R=null;function T(){return R||(R=new f),R}var U=new Proxy({},{get(m,t){let e=T(),n=e[t];return typeof n=="function"?n.bind(e):n}});
2
+ export{f as Drip,a as DripError,O as default,U as drip};//# sourceMappingURL=core.js.map
3
3
  //# sourceMappingURL=core.js.map