@drip-sdk/node 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -150,6 +150,31 @@ declare class StreamMeter {
150
150
  * @packageDocumentation
151
151
  */
152
152
 
153
+ /**
154
+ * Retry options for API calls.
155
+ */
156
+ interface RetryOptions {
157
+ /**
158
+ * Maximum number of retry attempts.
159
+ * @default 3
160
+ */
161
+ maxAttempts?: number;
162
+ /**
163
+ * Base delay between retries in milliseconds (exponential backoff).
164
+ * @default 100
165
+ */
166
+ baseDelayMs?: number;
167
+ /**
168
+ * Maximum delay between retries in milliseconds.
169
+ * @default 5000
170
+ */
171
+ maxDelayMs?: number;
172
+ /**
173
+ * Custom function to determine if an error is retryable.
174
+ * By default, retries on network errors and 5xx status codes.
175
+ */
176
+ isRetryable?: (error: unknown) => boolean;
177
+ }
153
178
  /**
154
179
  * Configuration options for the Drip SDK client.
155
180
  */
@@ -807,6 +832,63 @@ interface RunTimeline {
807
832
  };
808
833
  summary: string;
809
834
  }
835
+ /**
836
+ * Parameters for wrapping an external API call with usage tracking.
837
+ * This ensures usage is recorded even if there's a crash/failure after the API call.
838
+ */
839
+ interface WrapApiCallParams<T> {
840
+ /**
841
+ * The Drip customer ID to charge.
842
+ */
843
+ customerId: string;
844
+ /**
845
+ * The usage meter/type to record against.
846
+ * Must match a meter configured in your pricing plan.
847
+ */
848
+ meter: string;
849
+ /**
850
+ * The async function that makes the external API call.
851
+ * This is the call you want to track (e.g., OpenAI, Anthropic, etc.)
852
+ */
853
+ call: () => Promise<T>;
854
+ /**
855
+ * Function to extract the usage quantity from the API call result.
856
+ * @example (result) => result.usage.total_tokens
857
+ */
858
+ extractUsage: (result: T) => number;
859
+ /**
860
+ * Custom idempotency key prefix.
861
+ * If not provided, a unique key is generated.
862
+ * The key ensures retries don't double-charge.
863
+ */
864
+ idempotencyKey?: string;
865
+ /**
866
+ * Additional metadata to attach to this usage event.
867
+ */
868
+ metadata?: Record<string, unknown>;
869
+ /**
870
+ * Retry configuration for the Drip charge call.
871
+ * The external API call is NOT retried (only called once).
872
+ */
873
+ retryOptions?: RetryOptions;
874
+ }
875
+ /**
876
+ * Result of a wrapped API call.
877
+ */
878
+ interface WrapApiCallResult<T> {
879
+ /**
880
+ * The result from the external API call.
881
+ */
882
+ result: T;
883
+ /**
884
+ * The charge result from Drip.
885
+ */
886
+ charge: ChargeResult;
887
+ /**
888
+ * The idempotency key used (useful for debugging).
889
+ */
890
+ idempotencyKey: string;
891
+ }
810
892
  /**
811
893
  * Error thrown by Drip SDK operations.
812
894
  */
@@ -982,6 +1064,82 @@ declare class Drip {
982
1064
  * ```
983
1065
  */
984
1066
  charge(params: ChargeParams): Promise<ChargeResult>;
1067
+ /**
1068
+ * Wraps an external API call with guaranteed usage recording.
1069
+ *
1070
+ * **This solves the crash-before-record problem:**
1071
+ * ```typescript
1072
+ * // DANGEROUS - usage lost if crash between lines 1 and 2:
1073
+ * const response = await openai.chat.completions.create({...}); // line 1
1074
+ * await drip.charge({ tokens: response.usage.total_tokens }); // line 2
1075
+ *
1076
+ * // SAFE - wrapApiCall guarantees recording with retry:
1077
+ * const { result } = await drip.wrapApiCall({
1078
+ * call: () => openai.chat.completions.create({...}),
1079
+ * extractUsage: (r) => r.usage.total_tokens,
1080
+ * ...
1081
+ * });
1082
+ * ```
1083
+ *
1084
+ * How it works:
1085
+ * 1. Generates idempotency key BEFORE the API call
1086
+ * 2. Makes the external API call (once, no retry)
1087
+ * 3. Records usage in Drip with retry + idempotency
1088
+ * 4. If recording fails transiently, retries are safe (no double-charge)
1089
+ *
1090
+ * @param params - Wrap parameters including the call and usage extractor
1091
+ * @returns The API result and charge details
1092
+ * @throws {DripError} If the Drip charge fails after retries
1093
+ * @throws {Error} If the external API call fails
1094
+ *
1095
+ * @example
1096
+ * ```typescript
1097
+ * // OpenAI example
1098
+ * const { result, charge } = await drip.wrapApiCall({
1099
+ * customerId: 'cust_abc123',
1100
+ * meter: 'tokens',
1101
+ * call: () => openai.chat.completions.create({
1102
+ * model: 'gpt-4',
1103
+ * messages: [{ role: 'user', content: 'Hello!' }],
1104
+ * }),
1105
+ * extractUsage: (r) => r.usage?.total_tokens ?? 0,
1106
+ * });
1107
+ *
1108
+ * console.log(result.choices[0].message.content);
1109
+ * console.log(`Charged: ${charge.charge.amountUsdc} USDC`);
1110
+ * ```
1111
+ *
1112
+ * @example
1113
+ * ```typescript
1114
+ * // Anthropic example
1115
+ * const { result, charge } = await drip.wrapApiCall({
1116
+ * customerId: 'cust_abc123',
1117
+ * meter: 'tokens',
1118
+ * call: () => anthropic.messages.create({
1119
+ * model: 'claude-3-opus-20240229',
1120
+ * max_tokens: 1024,
1121
+ * messages: [{ role: 'user', content: 'Hello!' }],
1122
+ * }),
1123
+ * extractUsage: (r) => r.usage.input_tokens + r.usage.output_tokens,
1124
+ * });
1125
+ * ```
1126
+ *
1127
+ * @example
1128
+ * ```typescript
1129
+ * // With custom retry options
1130
+ * const { result } = await drip.wrapApiCall({
1131
+ * customerId: 'cust_abc123',
1132
+ * meter: 'api_calls',
1133
+ * call: () => fetch('https://api.example.com/expensive'),
1134
+ * extractUsage: () => 1, // Fixed cost per call
1135
+ * retryOptions: {
1136
+ * maxAttempts: 5,
1137
+ * baseDelayMs: 200,
1138
+ * },
1139
+ * });
1140
+ * ```
1141
+ */
1142
+ wrapApiCall<T>(params: WrapApiCallParams<T>): Promise<WrapApiCallResult<T>>;
985
1143
  /**
986
1144
  * Records usage for internal visibility WITHOUT billing.
987
1145
  *
@@ -1595,4 +1753,4 @@ declare class Drip {
1595
1753
 
1596
1754
  // @ts-ignore
1597
1755
  export = Drip;
1598
- export { type BalanceResult, type Charge, type ChargeParams, type ChargeResult, type ChargeStatus, type CheckoutParams, type CheckoutResult, type CreateCustomerParams, type CreateWebhookParams, type CreateWebhookResponse, type CreateWorkflowParams, type Customer, type DeleteWebhookResponse, Drip, type DripConfig, DripError, type EmitEventParams, type EndRunParams, type EventResult, type ListChargesOptions, type ListChargesResponse, type ListCustomersOptions, type ListCustomersResponse, type ListMetersResponse, type ListWebhooksResponse, type Meter, type RecordRunEvent, type RecordRunParams, type RecordRunResult, type RunResult, type RunStatus, type RunTimeline, type StartRunParams, StreamMeter, type StreamMeterFlushResult, type StreamMeterOptions, type TrackUsageParams, type TrackUsageResult, type Webhook, type WebhookEventType, type Workflow };
1756
+ export { type BalanceResult, type Charge, type ChargeParams, type ChargeResult, type ChargeStatus, type CheckoutParams, type CheckoutResult, type CreateCustomerParams, type CreateWebhookParams, type CreateWebhookResponse, type CreateWorkflowParams, type Customer, type DeleteWebhookResponse, Drip, type DripConfig, DripError, type EmitEventParams, type EndRunParams, type EventResult, type ListChargesOptions, type ListChargesResponse, type ListCustomersOptions, type ListCustomersResponse, type ListMetersResponse, type ListWebhooksResponse, type Meter, type RecordRunEvent, type RecordRunParams, type RecordRunResult, type RetryOptions, type RunResult, type RunStatus, type RunTimeline, type StartRunParams, StreamMeter, type StreamMeterFlushResult, type StreamMeterOptions, type TrackUsageParams, type TrackUsageResult, type Webhook, type WebhookEventType, type Workflow, type WrapApiCallParams, type WrapApiCallResult };
package/dist/index.d.ts CHANGED
@@ -150,6 +150,31 @@ declare class StreamMeter {
150
150
  * @packageDocumentation
151
151
  */
152
152
 
153
+ /**
154
+ * Retry options for API calls.
155
+ */
156
+ interface RetryOptions {
157
+ /**
158
+ * Maximum number of retry attempts.
159
+ * @default 3
160
+ */
161
+ maxAttempts?: number;
162
+ /**
163
+ * Base delay between retries in milliseconds (exponential backoff).
164
+ * @default 100
165
+ */
166
+ baseDelayMs?: number;
167
+ /**
168
+ * Maximum delay between retries in milliseconds.
169
+ * @default 5000
170
+ */
171
+ maxDelayMs?: number;
172
+ /**
173
+ * Custom function to determine if an error is retryable.
174
+ * By default, retries on network errors and 5xx status codes.
175
+ */
176
+ isRetryable?: (error: unknown) => boolean;
177
+ }
153
178
  /**
154
179
  * Configuration options for the Drip SDK client.
155
180
  */
@@ -807,6 +832,63 @@ interface RunTimeline {
807
832
  };
808
833
  summary: string;
809
834
  }
835
+ /**
836
+ * Parameters for wrapping an external API call with usage tracking.
837
+ * This ensures usage is recorded even if there's a crash/failure after the API call.
838
+ */
839
+ interface WrapApiCallParams<T> {
840
+ /**
841
+ * The Drip customer ID to charge.
842
+ */
843
+ customerId: string;
844
+ /**
845
+ * The usage meter/type to record against.
846
+ * Must match a meter configured in your pricing plan.
847
+ */
848
+ meter: string;
849
+ /**
850
+ * The async function that makes the external API call.
851
+ * This is the call you want to track (e.g., OpenAI, Anthropic, etc.)
852
+ */
853
+ call: () => Promise<T>;
854
+ /**
855
+ * Function to extract the usage quantity from the API call result.
856
+ * @example (result) => result.usage.total_tokens
857
+ */
858
+ extractUsage: (result: T) => number;
859
+ /**
860
+ * Custom idempotency key prefix.
861
+ * If not provided, a unique key is generated.
862
+ * The key ensures retries don't double-charge.
863
+ */
864
+ idempotencyKey?: string;
865
+ /**
866
+ * Additional metadata to attach to this usage event.
867
+ */
868
+ metadata?: Record<string, unknown>;
869
+ /**
870
+ * Retry configuration for the Drip charge call.
871
+ * The external API call is NOT retried (only called once).
872
+ */
873
+ retryOptions?: RetryOptions;
874
+ }
875
+ /**
876
+ * Result of a wrapped API call.
877
+ */
878
+ interface WrapApiCallResult<T> {
879
+ /**
880
+ * The result from the external API call.
881
+ */
882
+ result: T;
883
+ /**
884
+ * The charge result from Drip.
885
+ */
886
+ charge: ChargeResult;
887
+ /**
888
+ * The idempotency key used (useful for debugging).
889
+ */
890
+ idempotencyKey: string;
891
+ }
810
892
  /**
811
893
  * Error thrown by Drip SDK operations.
812
894
  */
@@ -982,6 +1064,82 @@ declare class Drip {
982
1064
  * ```
983
1065
  */
984
1066
  charge(params: ChargeParams): Promise<ChargeResult>;
1067
+ /**
1068
+ * Wraps an external API call with guaranteed usage recording.
1069
+ *
1070
+ * **This solves the crash-before-record problem:**
1071
+ * ```typescript
1072
+ * // DANGEROUS - usage lost if crash between lines 1 and 2:
1073
+ * const response = await openai.chat.completions.create({...}); // line 1
1074
+ * await drip.charge({ tokens: response.usage.total_tokens }); // line 2
1075
+ *
1076
+ * // SAFE - wrapApiCall guarantees recording with retry:
1077
+ * const { result } = await drip.wrapApiCall({
1078
+ * call: () => openai.chat.completions.create({...}),
1079
+ * extractUsage: (r) => r.usage.total_tokens,
1080
+ * ...
1081
+ * });
1082
+ * ```
1083
+ *
1084
+ * How it works:
1085
+ * 1. Generates idempotency key BEFORE the API call
1086
+ * 2. Makes the external API call (once, no retry)
1087
+ * 3. Records usage in Drip with retry + idempotency
1088
+ * 4. If recording fails transiently, retries are safe (no double-charge)
1089
+ *
1090
+ * @param params - Wrap parameters including the call and usage extractor
1091
+ * @returns The API result and charge details
1092
+ * @throws {DripError} If the Drip charge fails after retries
1093
+ * @throws {Error} If the external API call fails
1094
+ *
1095
+ * @example
1096
+ * ```typescript
1097
+ * // OpenAI example
1098
+ * const { result, charge } = await drip.wrapApiCall({
1099
+ * customerId: 'cust_abc123',
1100
+ * meter: 'tokens',
1101
+ * call: () => openai.chat.completions.create({
1102
+ * model: 'gpt-4',
1103
+ * messages: [{ role: 'user', content: 'Hello!' }],
1104
+ * }),
1105
+ * extractUsage: (r) => r.usage?.total_tokens ?? 0,
1106
+ * });
1107
+ *
1108
+ * console.log(result.choices[0].message.content);
1109
+ * console.log(`Charged: ${charge.charge.amountUsdc} USDC`);
1110
+ * ```
1111
+ *
1112
+ * @example
1113
+ * ```typescript
1114
+ * // Anthropic example
1115
+ * const { result, charge } = await drip.wrapApiCall({
1116
+ * customerId: 'cust_abc123',
1117
+ * meter: 'tokens',
1118
+ * call: () => anthropic.messages.create({
1119
+ * model: 'claude-3-opus-20240229',
1120
+ * max_tokens: 1024,
1121
+ * messages: [{ role: 'user', content: 'Hello!' }],
1122
+ * }),
1123
+ * extractUsage: (r) => r.usage.input_tokens + r.usage.output_tokens,
1124
+ * });
1125
+ * ```
1126
+ *
1127
+ * @example
1128
+ * ```typescript
1129
+ * // With custom retry options
1130
+ * const { result } = await drip.wrapApiCall({
1131
+ * customerId: 'cust_abc123',
1132
+ * meter: 'api_calls',
1133
+ * call: () => fetch('https://api.example.com/expensive'),
1134
+ * extractUsage: () => 1, // Fixed cost per call
1135
+ * retryOptions: {
1136
+ * maxAttempts: 5,
1137
+ * baseDelayMs: 200,
1138
+ * },
1139
+ * });
1140
+ * ```
1141
+ */
1142
+ wrapApiCall<T>(params: WrapApiCallParams<T>): Promise<WrapApiCallResult<T>>;
985
1143
  /**
986
1144
  * Records usage for internal visibility WITHOUT billing.
987
1145
  *
@@ -1593,4 +1751,4 @@ declare class Drip {
1593
1751
  createStreamMeter(options: StreamMeterOptions): StreamMeter;
1594
1752
  }
1595
1753
 
1596
- export { type BalanceResult, type Charge, type ChargeParams, type ChargeResult, type ChargeStatus, type CheckoutParams, type CheckoutResult, type CreateCustomerParams, type CreateWebhookParams, type CreateWebhookResponse, type CreateWorkflowParams, type Customer, type DeleteWebhookResponse, Drip, type DripConfig, DripError, type EmitEventParams, type EndRunParams, type EventResult, type ListChargesOptions, type ListChargesResponse, type ListCustomersOptions, type ListCustomersResponse, type ListMetersResponse, type ListWebhooksResponse, type Meter, type RecordRunEvent, type RecordRunParams, type RecordRunResult, type RunResult, type RunStatus, type RunTimeline, type StartRunParams, StreamMeter, type StreamMeterFlushResult, type StreamMeterOptions, type TrackUsageParams, type TrackUsageResult, type Webhook, type WebhookEventType, type Workflow, Drip as default };
1754
+ export { type BalanceResult, type Charge, type ChargeParams, type ChargeResult, type ChargeStatus, type CheckoutParams, type CheckoutResult, type CreateCustomerParams, type CreateWebhookParams, type CreateWebhookResponse, type CreateWorkflowParams, type Customer, type DeleteWebhookResponse, Drip, type DripConfig, DripError, type EmitEventParams, type EndRunParams, type EventResult, type ListChargesOptions, type ListChargesResponse, type ListCustomersOptions, type ListCustomersResponse, type ListMetersResponse, type ListWebhooksResponse, type Meter, type RecordRunEvent, type RecordRunParams, type RecordRunResult, type RetryOptions, type RunResult, type RunStatus, type RunTimeline, type StartRunParams, StreamMeter, type StreamMeterFlushResult, type StreamMeterOptions, type TrackUsageParams, type TrackUsageResult, type Webhook, type WebhookEventType, type Workflow, type WrapApiCallParams, type WrapApiCallResult, Drip as default };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- var I=(m=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(m,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):m)(function(m){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+m+'" is not supported')});var w=class{_total=0;_flushed=false;_flushCount=0;_chargeFn;_options;constructor(t,e){this._chargeFn=t,this._options=e;}get total(){return this._total}get isFlushed(){return this._flushed}get flushCount(){return this._flushCount}async add(t){return t<=0?null:(this._total+=t,this._options.onAdd?.(t,this._total),this._options.flushThreshold!==void 0&&this._total>=this._options.flushThreshold?this.flush():null)}addSync(t){t<=0||(this._total+=t,this._options.onAdd?.(t,this._total));}async flush(){let t=this._total;if(this._total=0,t===0)return {success:true,quantity:0,charge:null,isReplay:false};let e=this._options.idempotencyKey?`${this._options.idempotencyKey}_flush_${this._flushCount}`:void 0,r=await this._chargeFn({customerId:this._options.customerId,meter:this._options.meter,quantity:t,idempotencyKey:e,metadata:this._options.metadata});this._flushed=true,this._flushCount++;let n={success:r.success,quantity:t,charge:r.charge,isReplay:r.isReplay};return this._options.onFlush?.(n),n}reset(){this._total=0;}};var h=class m extends Error{constructor(e,r,n){super(e);this.statusCode=r;this.code=n;this.name="DripError",Object.setPrototypeOf(this,m.prototype);}},R=class{apiKey;baseUrl;timeout;constructor(t){if(!t.apiKey)throw new Error("Drip API key is required");this.apiKey=t.apiKey,this.baseUrl=t.baseUrl||"https://api.drip.dev/v1",this.timeout=t.timeout||3e4;}async request(t,e={}){let r=new AbortController,n=setTimeout(()=>r.abort(),this.timeout);try{let s=await fetch(`${this.baseUrl}${t}`,{...e,signal:r.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...e.headers}});if(s.status===204)return {success:!0};let i=await s.json();if(!s.ok)throw new h(i.message||i.error||"Request failed",s.status,i.code);return i}catch(s){throw s instanceof h?s:s instanceof Error&&s.name==="AbortError"?new h("Request timed out",408,"TIMEOUT"):new h(s instanceof Error?s.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(n);}}async ping(){let t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),r=this.baseUrl;r.endsWith("/v1/")?r=r.slice(0,-4):r.endsWith("/v1")&&(r=r.slice(0,-3)),r=r.replace(/\/+$/,"");let n=Date.now();try{let s=await fetch(`${r}/health`,{signal:t.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),i=Date.now()-n,a="unknown",u=Date.now();try{let c=await s.json();typeof c.status=="string"&&(a=c.status),typeof c.timestamp=="number"&&(u=c.timestamp);}catch{a=s.ok?"healthy":`error:${s.status}`;}return !s.ok&&a==="unknown"&&(a=`error:${s.status}`),{ok:s.ok&&a==="healthy",status:a,latencyMs:i,timestamp:u}}catch(s){throw s instanceof Error&&s.name==="AbortError"?new h("Request timed out",408,"TIMEOUT"):new h(s instanceof Error?s.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 r=e.toString(),n=r?`/customers?${r}`:"/customers";return this.request(n)}async getBalance(t){return this.request(`/customers/${t}/balance`)}async charge(t){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:t.idempotencyKey,metadata:t.metadata})})}async trackUsage(t){return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:t.idempotencyKey,units:t.units,description:t.description,metadata:t.metadata})})}async getCharge(t){return this.request(`/charges/${t}`)}async listCharges(t){let e=new URLSearchParams;t?.customerId&&e.set("customerId",t.customerId),t?.status&&e.set("status",t.status),t?.limit&&e.set("limit",t.limit.toString()),t?.offset&&e.set("offset",t.offset.toString());let r=e.toString(),n=r?`/charges?${r}`:"/charges";return this.request(n)}async getChargeStatus(t){return this.request(`/charges/${t}/status`)}async checkout(t){let e=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:t.customerId,external_customer_id:t.externalCustomerId,amount:t.amount,return_url:t.returnUrl,cancel_url:t.cancelUrl,metadata:t.metadata})});return {id:e.id,url:e.url,expiresAt:e.expires_at,amountUsd:e.amount_usd}}async createWebhook(t){return this.request("/webhooks",{method:"POST",body:JSON.stringify(t)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(t){return this.request(`/webhooks/${t}`)}async deleteWebhook(t){return this.request(`/webhooks/${t}`,{method:"DELETE"})}async testWebhook(t){return this.request(`/webhooks/${t}/test`,{method:"POST"})}async rotateWebhookSecret(t){return this.request(`/webhooks/${t}/rotate-secret`,{method:"POST"})}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 getRunTimeline(t){return this.request(`/runs/${t}`)}async emitEvent(t){return this.request("/events",{method:"POST",body:JSON.stringify(t)})}async emitEventsBatch(t){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:t})})}async listMeters(){let t=await this.request("/pricing-plans");return {data:t.data.map(e=>({id:e.id,name:e.name,meter:e.unitType,unitPriceUsd:e.unitPriceUsd,isActive:e.isActive})),count:t.count}}async recordRun(t){let e=Date.now(),r=t.workflow,n=t.workflow;if(!t.workflow.startsWith("wf_"))try{let l=(await this.listWorkflows()).data.find(o=>o.slug===t.workflow||o.id===t.workflow);if(l)r=l.id,n=l.name;else {let o=await this.createWorkflow({name:t.workflow.replace(/[_-]/g," ").replace(/\b\w/g,g=>g.toUpperCase()),slug:t.workflow,productSurface:"AGENT"});r=o.id,n=o.name;}}catch{r=t.workflow;}let s=await this.startRun({customerId:t.customerId,workflowId:r,externalRunId:t.externalRunId,correlationId:t.correlationId,metadata:t.metadata}),i=0,a=0;if(t.events.length>0){let p=t.events.map((o,g)=>({runId:s.id,eventType:o.eventType,quantity:o.quantity,units:o.units,description:o.description,costUnits:o.costUnits,metadata:o.metadata,idempotencyKey:t.externalRunId?`${t.externalRunId}:${o.eventType}:${g}`:void 0})),l=await this.emitEventsBatch(p);i=l.created,a=l.duplicates;}let u=await this.endRun(s.id,{status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode}),c=Date.now()-e,y=t.events.length>0?`${i} events recorded`:"no events",f=`${t.status==="COMPLETED"?"\u2713":t.status==="FAILED"?"\u2717":"\u25CB"} ${n}: ${y} (${u.durationMs??c}ms)`;return {run:{id:s.id,workflowId:r,workflowName:n,status:t.status,durationMs:u.durationMs},events:{created:i,duplicates:a},totalCostUnits:u.totalCostUnits,summary:f}}static generateIdempotencyKey(t){let e=[t.customerId,t.runId??"no_run",t.stepName,String(t.sequence??0)],r=0,n=e.join("|");for(let s=0;s<n.length;s++){let i=n.charCodeAt(s);r=(r<<5)-r+i,r=r&r;}return `drip_${Math.abs(r).toString(36)}_${t.stepName.slice(0,16)}`}static async verifyWebhookSignature(t,e,r,n=300){if(!t||!e||!r)return false;try{let s=e.split(","),i=s.find(d=>d.startsWith("t=")),a=s.find(d=>d.startsWith("v1="));if(!i||!a)return !1;let u=parseInt(i.slice(2),10),c=a.slice(3);if(isNaN(u))return !1;let y=Math.floor(Date.now()/1e3);if(Math.abs(y-u)>n)return !1;let b=`${u}.${t}`,f=new TextEncoder,p=f.encode(r),l=f.encode(b),o=await crypto.subtle.importKey("raw",p,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),g=await crypto.subtle.sign("HMAC",o,l),C=Array.from(new Uint8Array(g)).map(d=>d.toString(16).padStart(2,"0")).join("");if(c.length!==C.length)return !1;let k=0;for(let d=0;d<c.length;d++)k|=c.charCodeAt(d)^C.charCodeAt(d);return k===0}catch{return false}}static verifyWebhookSignatureSync(t,e,r,n=300){if(!t||!e||!r)return false;try{let s=e.split(","),i=s.find(g=>g.startsWith("t=")),a=s.find(g=>g.startsWith("v1="));if(!i||!a)return !1;let u=parseInt(i.slice(2),10),c=a.slice(3);if(isNaN(u))return !1;let y=Math.floor(Date.now()/1e3);if(Math.abs(y-u)>n)return !1;let b=I("crypto"),f=`${u}.${t}`,p=b.createHmac("sha256",r).update(f).digest("hex"),l=Buffer.from(c),o=Buffer.from(p);return l.length!==o.length?!1:b.timingSafeEqual(l,o)}catch{return false}}static generateWebhookSignature(t,e,r){let n=I("crypto"),s=r??Math.floor(Date.now()/1e3),i=`${s}.${t}`,a=n.createHmac("sha256",e).update(i).digest("hex");return `t=${s},v1=${a}`}createStreamMeter(t){return new w(this.charge.bind(this),t)}},A=R;
2
- export{R as Drip,h as DripError,w as StreamMeter,A as default};//# sourceMappingURL=index.js.map
1
+ var R=(u=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(u,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):u)(function(u){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+u+'" is not supported')});var w=class{_total=0;_flushed=false;_flushCount=0;_chargeFn;_options;constructor(t,e){this._chargeFn=t,this._options=e;}get total(){return this._total}get isFlushed(){return this._flushed}get flushCount(){return this._flushCount}async add(t){return t<=0?null:(this._total+=t,this._options.onAdd?.(t,this._total),this._options.flushThreshold!==void 0&&this._total>=this._options.flushThreshold?this.flush():null)}addSync(t){t<=0||(this._total+=t,this._options.onAdd?.(t,this._total));}async flush(){let t=this._total;if(this._total=0,t===0)return {success:true,quantity:0,charge:null,isReplay:false};let e=this._options.idempotencyKey?`${this._options.idempotencyKey}_flush_${this._flushCount}`:void 0,r=await this._chargeFn({customerId:this._options.customerId,meter:this._options.meter,quantity:t,idempotencyKey:e,metadata:this._options.metadata});this._flushed=true,this._flushCount++;let n={success:r.success,quantity:t,charge:r.charge,isReplay:r.isReplay};return this._options.onFlush?.(n),n}reset(){this._total=0;}};var C={maxAttempts:3,baseDelayMs:100,maxDelayMs:5e3};function E(u){return u instanceof Error&&(u.message.includes("fetch")||u.message.includes("network"))?true:u instanceof h?u.statusCode>=500||u.statusCode===408||u.statusCode===429:false}async function v(u,t={}){let e=t.maxAttempts??C.maxAttempts,r=t.baseDelayMs??C.baseDelayMs,n=t.maxDelayMs??C.maxDelayMs,s=t.isRetryable??E,i;for(let o=1;o<=e;o++)try{return await u()}catch(c){if(i=c,o===e||!s(c))throw c;let l=Math.min(r*Math.pow(2,o-1)+Math.random()*100,n);await new Promise(f=>setTimeout(f,l));}throw i}var h=class u extends Error{constructor(e,r,n){super(e);this.statusCode=r;this.code=n;this.name="DripError",Object.setPrototypeOf(this,u.prototype);}},k=class{apiKey;baseUrl;timeout;constructor(t){if(!t.apiKey)throw new Error("Drip API key is required");this.apiKey=t.apiKey,this.baseUrl=t.baseUrl||"https://api.drip.dev/v1",this.timeout=t.timeout||3e4;}async request(t,e={}){let r=new AbortController,n=setTimeout(()=>r.abort(),this.timeout);try{let s=await fetch(`${this.baseUrl}${t}`,{...e,signal:r.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...e.headers}});if(s.status===204)return {success:!0};let i=await s.json();if(!s.ok)throw new h(i.message||i.error||"Request failed",s.status,i.code);return i}catch(s){throw s instanceof h?s:s instanceof Error&&s.name==="AbortError"?new h("Request timed out",408,"TIMEOUT"):new h(s instanceof Error?s.message:"Unknown error",0,"UNKNOWN")}finally{clearTimeout(n);}}async ping(){let t=new AbortController,e=setTimeout(()=>t.abort(),this.timeout),r=this.baseUrl;r.endsWith("/v1/")?r=r.slice(0,-4):r.endsWith("/v1")&&(r=r.slice(0,-3)),r=r.replace(/\/+$/,"");let n=Date.now();try{let s=await fetch(`${r}/health`,{signal:t.signal,headers:{Authorization:`Bearer ${this.apiKey}`}}),i=Date.now()-n,o="unknown",c=Date.now();try{let l=await s.json();typeof l.status=="string"&&(o=l.status),typeof l.timestamp=="number"&&(c=l.timestamp);}catch{o=s.ok?"healthy":`error:${s.status}`;}return !s.ok&&o==="unknown"&&(o=`error:${s.status}`),{ok:s.ok&&o==="healthy",status:o,latencyMs:i,timestamp:c}}catch(s){throw s instanceof Error&&s.name==="AbortError"?new h("Request timed out",408,"TIMEOUT"):new h(s instanceof Error?s.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 r=e.toString(),n=r?`/customers?${r}`:"/customers";return this.request(n)}async getBalance(t){return this.request(`/customers/${t}/balance`)}async charge(t){return this.request("/usage",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:t.idempotencyKey,metadata:t.metadata})})}async wrapApiCall(t){let e=t.idempotencyKey??`wrap_${Date.now()}_${Math.random().toString(36).slice(2,11)}`,r=await t.call(),n=t.extractUsage(r),s=await v(()=>this.charge({customerId:t.customerId,meter:t.meter,quantity:n,idempotencyKey:e,metadata:t.metadata}),t.retryOptions);return {result:r,charge:s,idempotencyKey:e}}async trackUsage(t){return this.request("/usage/internal",{method:"POST",body:JSON.stringify({customerId:t.customerId,usageType:t.meter,quantity:t.quantity,idempotencyKey:t.idempotencyKey,units:t.units,description:t.description,metadata:t.metadata})})}async getCharge(t){return this.request(`/charges/${t}`)}async listCharges(t){let e=new URLSearchParams;t?.customerId&&e.set("customerId",t.customerId),t?.status&&e.set("status",t.status),t?.limit&&e.set("limit",t.limit.toString()),t?.offset&&e.set("offset",t.offset.toString());let r=e.toString(),n=r?`/charges?${r}`:"/charges";return this.request(n)}async getChargeStatus(t){return this.request(`/charges/${t}/status`)}async checkout(t){let e=await this.request("/checkout",{method:"POST",body:JSON.stringify({customer_id:t.customerId,external_customer_id:t.externalCustomerId,amount:t.amount,return_url:t.returnUrl,cancel_url:t.cancelUrl,metadata:t.metadata})});return {id:e.id,url:e.url,expiresAt:e.expires_at,amountUsd:e.amount_usd}}async createWebhook(t){return this.request("/webhooks",{method:"POST",body:JSON.stringify(t)})}async listWebhooks(){return this.request("/webhooks")}async getWebhook(t){return this.request(`/webhooks/${t}`)}async deleteWebhook(t){return this.request(`/webhooks/${t}`,{method:"DELETE"})}async testWebhook(t){return this.request(`/webhooks/${t}/test`,{method:"POST"})}async rotateWebhookSecret(t){return this.request(`/webhooks/${t}/rotate-secret`,{method:"POST"})}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 getRunTimeline(t){return this.request(`/runs/${t}`)}async emitEvent(t){return this.request("/events",{method:"POST",body:JSON.stringify(t)})}async emitEventsBatch(t){return this.request("/run-events/batch",{method:"POST",body:JSON.stringify({events:t})})}async listMeters(){let t=await this.request("/pricing-plans");return {data:t.data.map(e=>({id:e.id,name:e.name,meter:e.unitType,unitPriceUsd:e.unitPriceUsd,isActive:e.isActive})),count:t.count}}async recordRun(t){let e=Date.now(),r=t.workflow,n=t.workflow;if(!t.workflow.startsWith("wf_"))try{let d=(await this.listWorkflows()).data.find(a=>a.slug===t.workflow||a.id===t.workflow);if(d)r=d.id,n=d.name;else {let a=await this.createWorkflow({name:t.workflow.replace(/[_-]/g," ").replace(/\b\w/g,g=>g.toUpperCase()),slug:t.workflow,productSurface:"AGENT"});r=a.id,n=a.name;}}catch{r=t.workflow;}let s=await this.startRun({customerId:t.customerId,workflowId:r,externalRunId:t.externalRunId,correlationId:t.correlationId,metadata:t.metadata}),i=0,o=0;if(t.events.length>0){let y=t.events.map((a,g)=>({runId:s.id,eventType:a.eventType,quantity:a.quantity,units:a.units,description:a.description,costUnits:a.costUnits,metadata:a.metadata,idempotencyKey:t.externalRunId?`${t.externalRunId}:${a.eventType}:${g}`:void 0})),d=await this.emitEventsBatch(y);i=d.created,o=d.duplicates;}let c=await this.endRun(s.id,{status:t.status,errorMessage:t.errorMessage,errorCode:t.errorCode}),l=Date.now()-e,f=t.events.length>0?`${i} events recorded`:"no events",p=`${t.status==="COMPLETED"?"\u2713":t.status==="FAILED"?"\u2717":"\u25CB"} ${n}: ${f} (${c.durationMs??l}ms)`;return {run:{id:s.id,workflowId:r,workflowName:n,status:t.status,durationMs:c.durationMs},events:{created:i,duplicates:o},totalCostUnits:c.totalCostUnits,summary:p}}static generateIdempotencyKey(t){let e=[t.customerId,t.runId??"no_run",t.stepName,String(t.sequence??0)],r=0,n=e.join("|");for(let s=0;s<n.length;s++){let i=n.charCodeAt(s);r=(r<<5)-r+i,r=r&r;}return `drip_${Math.abs(r).toString(36)}_${t.stepName.slice(0,16)}`}static async verifyWebhookSignature(t,e,r,n=300){if(!t||!e||!r)return false;try{let s=e.split(","),i=s.find(m=>m.startsWith("t=")),o=s.find(m=>m.startsWith("v1="));if(!i||!o)return !1;let c=parseInt(i.slice(2),10),l=o.slice(3);if(isNaN(c))return !1;let f=Math.floor(Date.now()/1e3);if(Math.abs(f-c)>n)return !1;let b=`${c}.${t}`,p=new TextEncoder,y=p.encode(r),d=p.encode(b),a=globalThis.crypto?.subtle??R("crypto").webcrypto.subtle,g=await a.importKey("raw",y,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),x=await a.sign("HMAC",g,d),I=Array.from(new Uint8Array(x)).map(m=>m.toString(16).padStart(2,"0")).join("");if(l.length!==I.length)return !1;let P=0;for(let m=0;m<l.length;m++)P|=l.charCodeAt(m)^I.charCodeAt(m);return P===0}catch{return false}}static verifyWebhookSignatureSync(t,e,r,n=300){if(!t||!e||!r)return false;try{let s=e.split(","),i=s.find(g=>g.startsWith("t=")),o=s.find(g=>g.startsWith("v1="));if(!i||!o)return !1;let c=parseInt(i.slice(2),10),l=o.slice(3);if(isNaN(c))return !1;let f=Math.floor(Date.now()/1e3);if(Math.abs(f-c)>n)return !1;let b=R("crypto"),p=`${c}.${t}`,y=b.createHmac("sha256",r).update(p).digest("hex"),d=Buffer.from(l),a=Buffer.from(y);return d.length!==a.length?!1:b.timingSafeEqual(d,a)}catch{return false}}static generateWebhookSignature(t,e,r){let n=R("crypto"),s=r??Math.floor(Date.now()/1e3),i=`${s}.${t}`,o=n.createHmac("sha256",e).update(i).digest("hex");return `t=${s},v1=${o}`}createStreamMeter(t){return new w(this.charge.bind(this),t)}},_=k;
2
+ export{k as Drip,h as DripError,w as StreamMeter,_ as default};//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map