@littlebearapps/platform-consumer-sdk 1.0.0
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 +306 -0
- package/package.json +53 -0
- package/src/ai-gateway.ts +305 -0
- package/src/constants.ts +147 -0
- package/src/costs.ts +590 -0
- package/src/do-heartbeat.ts +249 -0
- package/src/dynamic-patterns.ts +273 -0
- package/src/errors.ts +285 -0
- package/src/features.ts +149 -0
- package/src/heartbeat.ts +27 -0
- package/src/index.ts +950 -0
- package/src/logging.ts +543 -0
- package/src/middleware.ts +447 -0
- package/src/patterns.ts +156 -0
- package/src/proxy.ts +732 -0
- package/src/retry.ts +19 -0
- package/src/service-client.ts +291 -0
- package/src/telemetry.ts +342 -0
- package/src/timeout.ts +212 -0
- package/src/tracing.ts +403 -0
- package/src/types.ts +465 -0
package/src/costs.ts
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Cost Calculation Module
|
|
3
|
+
*
|
|
4
|
+
* Provides proration constants and cost calculation functions for different
|
|
5
|
+
* time granularities (hourly, daily, monthly).
|
|
6
|
+
*
|
|
7
|
+
* Problem solved: Monthly base costs (e.g., $5/mo for Workers Paid) were being
|
|
8
|
+
* applied without proration to hourly snapshots, causing inflated cost estimates.
|
|
9
|
+
*
|
|
10
|
+
* Solution:
|
|
11
|
+
* - Hourly: base_cost = monthly_cost / HOURS_PER_MONTH (~730)
|
|
12
|
+
* - Daily: base_cost = monthly_cost / DAYS_PER_MONTH (~30.42)
|
|
13
|
+
* - Monthly: full base_cost
|
|
14
|
+
*
|
|
15
|
+
* @see https://developers.cloudflare.com/workers/platform/pricing/
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// PRORATION CONSTANTS
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Average hours per month: 365 * 24 / 12 = 730
|
|
24
|
+
* Used for hourly snapshot proration.
|
|
25
|
+
*/
|
|
26
|
+
export const HOURS_PER_MONTH = (365 * 24) / 12; // ~730
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Average days per month: 365 / 12 = 30.4167
|
|
30
|
+
* Used for daily rollup proration.
|
|
31
|
+
*/
|
|
32
|
+
export const DAYS_PER_MONTH = 365 / 12; // ~30.42
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// PRICING TIERS (Consolidated from dashboard/src/lib/cloudflare/costs.ts)
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Cloudflare pricing constants (Workers Paid plan, as of Jan 2025)
|
|
40
|
+
* Single source of truth for all cost calculations.
|
|
41
|
+
*
|
|
42
|
+
* NOTE: When updating pricing, also update dashboard/src/lib/cloudflare/costs.ts CF_PRICING
|
|
43
|
+
*/
|
|
44
|
+
export const PRICING_TIERS = {
|
|
45
|
+
// Workers Paid: $5/month base + usage
|
|
46
|
+
workers: {
|
|
47
|
+
baseCostMonthly: 5.0,
|
|
48
|
+
includedRequests: 10_000_000, // 10M included
|
|
49
|
+
requestsPerMillion: 0.3, // $0.30 per million after included
|
|
50
|
+
cpuMsPerMillion: 0.02, // $0.02 per million CPU ms
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// D1: Pay per use
|
|
54
|
+
d1: {
|
|
55
|
+
rowsReadPerBillion: 0.001, // $0.001 per billion rows read
|
|
56
|
+
rowsWrittenPerMillion: 1.0, // $1.00 per million rows written
|
|
57
|
+
storagePerGb: 0.75, // $0.75 per GB-month
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// KV: Pay per use
|
|
61
|
+
kv: {
|
|
62
|
+
readsPerMillion: 0.5, // $0.50 per million reads
|
|
63
|
+
writesPerMillion: 5.0, // $5.00 per million writes
|
|
64
|
+
deletesPerMillion: 5.0, // $5.00 per million deletes
|
|
65
|
+
listsPerMillion: 5.0, // $5.00 per million lists
|
|
66
|
+
storagePerGb: 0.5, // $0.50 per GB-month
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// R2: Pay per use (no egress fees!)
|
|
70
|
+
r2: {
|
|
71
|
+
storagePerGbMonth: 0.015, // $0.015 per GB-month
|
|
72
|
+
classAPerMillion: 4.5, // $4.50 per million Class A ops
|
|
73
|
+
classBPerMillion: 0.36, // $0.36 per million Class B ops
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Durable Objects: Pay per use
|
|
77
|
+
durableObjects: {
|
|
78
|
+
requestsPerMillion: 0.15, // $0.15 per million requests
|
|
79
|
+
gbSecondsPerMillion: 12.5, // $12.50 per million GB-seconds
|
|
80
|
+
storagePerGbMonth: 0.2, // $0.20 per GB-month
|
|
81
|
+
readsPerMillion: 0.2, // $0.20 per million storage reads
|
|
82
|
+
writesPerMillion: 1.0, // $1.00 per million storage writes
|
|
83
|
+
deletesPerMillion: 1.0, // $1.00 per million storage deletes
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Vectorize: Pay per use
|
|
87
|
+
vectorize: {
|
|
88
|
+
storedDimensionsPerMillion: 0.01, // $0.01 per million stored dimensions
|
|
89
|
+
queriedDimensionsPerMillion: 0.01, // $0.01 per million queried dimensions
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// AI Gateway: Free tier (logs only)
|
|
93
|
+
aiGateway: {
|
|
94
|
+
free: true,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// Workers AI: Pay per use
|
|
98
|
+
workersAI: {
|
|
99
|
+
neuronsPerThousand: 0.011, // $0.011 per 1,000 neurons
|
|
100
|
+
models: {
|
|
101
|
+
'@cf/meta/llama-3.1-8b-instruct-fp8': { input: 0.152, output: 0.287 },
|
|
102
|
+
'@cf/meta/llama-3.2-3b-instruct': { input: 0.1, output: 0.2 },
|
|
103
|
+
default: { input: 0.2, output: 0.4 },
|
|
104
|
+
} as Record<string, { input: number; output: number }>,
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Pages: Generous free tier
|
|
108
|
+
pages: {
|
|
109
|
+
buildCost: 0.15, // $0.15 per build after 500 free
|
|
110
|
+
bandwidthPerGb: 0.02, // $0.02 per GB after 100GB free
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// Queues: Pay per use
|
|
114
|
+
queues: {
|
|
115
|
+
messagesPerMillion: 0.4, // $0.40 per million messages
|
|
116
|
+
operationsPerMillion: 0.4, // $0.40 per million operations
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// Workflows: Beta (no pricing yet)
|
|
120
|
+
workflows: {
|
|
121
|
+
free: true, // Currently in beta
|
|
122
|
+
},
|
|
123
|
+
} as const;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Workers Paid Plan monthly allowances (for net cost calculation)
|
|
127
|
+
* These are subtracted before calculating billable usage.
|
|
128
|
+
*/
|
|
129
|
+
export const PAID_ALLOWANCES = {
|
|
130
|
+
d1: {
|
|
131
|
+
rowsRead: 25_000_000_000, // 25 billion/month
|
|
132
|
+
rowsWritten: 50_000_000, // 50 million/month
|
|
133
|
+
},
|
|
134
|
+
kv: {
|
|
135
|
+
reads: 10_000_000, // 10 million/month
|
|
136
|
+
writes: 1_000_000, // 1 million/month
|
|
137
|
+
deletes: 1_000_000, // 1 million/month
|
|
138
|
+
lists: 1_000_000, // 1 million/month
|
|
139
|
+
},
|
|
140
|
+
r2: {
|
|
141
|
+
storage: 10_000_000_000, // 10 GB
|
|
142
|
+
classA: 1_000_000, // 1 million/month
|
|
143
|
+
classB: 10_000_000, // 10 million/month
|
|
144
|
+
},
|
|
145
|
+
durableObjects: {
|
|
146
|
+
requests: 1_000_000, // 1 million/month (Workers Paid Plan)
|
|
147
|
+
gbSeconds: 400_000, // 400K GB-seconds/month
|
|
148
|
+
},
|
|
149
|
+
vectorize: {
|
|
150
|
+
storedDimensions: 10_000_000, // 10 million/month
|
|
151
|
+
queriedDimensions: 50_000_000, // 50 million/month
|
|
152
|
+
},
|
|
153
|
+
queues: {
|
|
154
|
+
operations: 1_000_000, // 1 million operations/month
|
|
155
|
+
},
|
|
156
|
+
} as const;
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// HOURLY COST CALCULATION
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Usage metrics for hourly cost calculation.
|
|
164
|
+
* Represents a single hour's worth of resource consumption.
|
|
165
|
+
*/
|
|
166
|
+
export interface HourlyUsageMetrics {
|
|
167
|
+
workersRequests: number;
|
|
168
|
+
workersCpuMs: number;
|
|
169
|
+
d1Reads: number;
|
|
170
|
+
d1Writes: number;
|
|
171
|
+
kvReads: number;
|
|
172
|
+
kvWrites: number;
|
|
173
|
+
kvDeletes?: number;
|
|
174
|
+
kvLists?: number;
|
|
175
|
+
r2ClassA: number;
|
|
176
|
+
r2ClassB: number;
|
|
177
|
+
vectorizeQueries: number;
|
|
178
|
+
aiGatewayRequests: number;
|
|
179
|
+
durableObjectsRequests: number;
|
|
180
|
+
durableObjectsGbSeconds?: number;
|
|
181
|
+
workersAINeurons?: number;
|
|
182
|
+
queuesMessages?: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Cost breakdown result for hourly snapshots.
|
|
187
|
+
*/
|
|
188
|
+
export interface HourlyCostBreakdown {
|
|
189
|
+
workers: number;
|
|
190
|
+
d1: number;
|
|
191
|
+
kv: number;
|
|
192
|
+
r2: number;
|
|
193
|
+
durableObjects: number;
|
|
194
|
+
vectorize: number;
|
|
195
|
+
aiGateway: number;
|
|
196
|
+
workersAI: number;
|
|
197
|
+
pages: number;
|
|
198
|
+
queues: number;
|
|
199
|
+
workflows: number;
|
|
200
|
+
total: number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Calculate costs for a single hour's usage metrics.
|
|
205
|
+
*
|
|
206
|
+
* IMPORTANT: This prorates the Workers base cost ($5/month) to ~$0.00685/hour
|
|
207
|
+
* to avoid inflated cost estimates in hourly snapshots.
|
|
208
|
+
*
|
|
209
|
+
* For hourly breakdown:
|
|
210
|
+
* - Base cost is prorated: $5 / 730 hours = ~$0.00685/hour
|
|
211
|
+
* - Usage costs are calculated from actual hourly consumption
|
|
212
|
+
* - Free tier allowances are prorated hourly
|
|
213
|
+
*
|
|
214
|
+
* @param usage - The hourly usage metrics
|
|
215
|
+
* @returns Cost breakdown for the hour
|
|
216
|
+
*/
|
|
217
|
+
export function calculateHourlyCosts(usage: HourlyUsageMetrics): HourlyCostBreakdown {
|
|
218
|
+
// Prorate base cost: $5/month / 730 hours
|
|
219
|
+
const hourlyBaseCost = PRICING_TIERS.workers.baseCostMonthly / HOURS_PER_MONTH;
|
|
220
|
+
|
|
221
|
+
// Prorate included requests: 10M/month / 730 hours
|
|
222
|
+
const hourlyIncludedRequests = PRICING_TIERS.workers.includedRequests / HOURS_PER_MONTH;
|
|
223
|
+
|
|
224
|
+
// Workers: prorated base cost + usage-based cost
|
|
225
|
+
const overageRequests = Math.max(0, usage.workersRequests - hourlyIncludedRequests);
|
|
226
|
+
const workersUsageCost =
|
|
227
|
+
(overageRequests / 1_000_000) * PRICING_TIERS.workers.requestsPerMillion +
|
|
228
|
+
(usage.workersCpuMs / 1_000_000) * PRICING_TIERS.workers.cpuMsPerMillion;
|
|
229
|
+
const workersCost = hourlyBaseCost + workersUsageCost;
|
|
230
|
+
|
|
231
|
+
// D1 — subtract prorated Workers Paid plan allowances (25B reads, 50M writes per month)
|
|
232
|
+
const hourlyD1ReadsAllowance = PAID_ALLOWANCES.d1.rowsRead / HOURS_PER_MONTH;
|
|
233
|
+
const hourlyD1WritesAllowance = PAID_ALLOWANCES.d1.rowsWritten / HOURS_PER_MONTH;
|
|
234
|
+
const d1Cost =
|
|
235
|
+
(Math.max(0, usage.d1Reads - hourlyD1ReadsAllowance) / 1_000_000_000) *
|
|
236
|
+
PRICING_TIERS.d1.rowsReadPerBillion +
|
|
237
|
+
(Math.max(0, usage.d1Writes - hourlyD1WritesAllowance) / 1_000_000) *
|
|
238
|
+
PRICING_TIERS.d1.rowsWrittenPerMillion;
|
|
239
|
+
|
|
240
|
+
// KV — subtract prorated allowances (10M reads, 1M writes, 1M deletes, 1M lists per month)
|
|
241
|
+
const hourlyKvReadsAllowance = PAID_ALLOWANCES.kv.reads / HOURS_PER_MONTH;
|
|
242
|
+
const hourlyKvWritesAllowance = PAID_ALLOWANCES.kv.writes / HOURS_PER_MONTH;
|
|
243
|
+
const hourlyKvDeletesAllowance = PAID_ALLOWANCES.kv.deletes / HOURS_PER_MONTH;
|
|
244
|
+
const hourlyKvListsAllowance = PAID_ALLOWANCES.kv.lists / HOURS_PER_MONTH;
|
|
245
|
+
const kvCost =
|
|
246
|
+
(Math.max(0, usage.kvReads - hourlyKvReadsAllowance) / 1_000_000) *
|
|
247
|
+
PRICING_TIERS.kv.readsPerMillion +
|
|
248
|
+
(Math.max(0, usage.kvWrites - hourlyKvWritesAllowance) / 1_000_000) *
|
|
249
|
+
PRICING_TIERS.kv.writesPerMillion +
|
|
250
|
+
(Math.max(0, (usage.kvDeletes ?? 0) - hourlyKvDeletesAllowance) / 1_000_000) *
|
|
251
|
+
PRICING_TIERS.kv.deletesPerMillion +
|
|
252
|
+
(Math.max(0, (usage.kvLists ?? 0) - hourlyKvListsAllowance) / 1_000_000) *
|
|
253
|
+
PRICING_TIERS.kv.listsPerMillion;
|
|
254
|
+
|
|
255
|
+
// R2 — subtract prorated allowances (1M Class A, 10M Class B per month)
|
|
256
|
+
const hourlyR2ClassAAllowance = PAID_ALLOWANCES.r2.classA / HOURS_PER_MONTH;
|
|
257
|
+
const hourlyR2ClassBAllowance = PAID_ALLOWANCES.r2.classB / HOURS_PER_MONTH;
|
|
258
|
+
const r2Cost =
|
|
259
|
+
(Math.max(0, usage.r2ClassA - hourlyR2ClassAAllowance) / 1_000_000) *
|
|
260
|
+
PRICING_TIERS.r2.classAPerMillion +
|
|
261
|
+
(Math.max(0, usage.r2ClassB - hourlyR2ClassBAllowance) / 1_000_000) *
|
|
262
|
+
PRICING_TIERS.r2.classBPerMillion;
|
|
263
|
+
|
|
264
|
+
// Vectorize — subtract prorated allowances (50M queried dimensions per month)
|
|
265
|
+
const hourlyVectorizeAllowance = PAID_ALLOWANCES.vectorize.queriedDimensions / HOURS_PER_MONTH;
|
|
266
|
+
const vectorizeCost =
|
|
267
|
+
(Math.max(0, usage.vectorizeQueries - hourlyVectorizeAllowance) / 1_000_000) *
|
|
268
|
+
PRICING_TIERS.vectorize.queriedDimensionsPerMillion;
|
|
269
|
+
|
|
270
|
+
// AI Gateway (free)
|
|
271
|
+
const aiGatewayCost = 0;
|
|
272
|
+
|
|
273
|
+
// Durable Objects — subtract prorated allowances (1M requests, 400K GB-s per month)
|
|
274
|
+
const hourlyDoRequestsAllowance = PAID_ALLOWANCES.durableObjects.requests / HOURS_PER_MONTH;
|
|
275
|
+
const hourlyDoGbSecondsAllowance = PAID_ALLOWANCES.durableObjects.gbSeconds / HOURS_PER_MONTH;
|
|
276
|
+
const durableObjectsCost =
|
|
277
|
+
(Math.max(0, usage.durableObjectsRequests - hourlyDoRequestsAllowance) / 1_000_000) *
|
|
278
|
+
PRICING_TIERS.durableObjects.requestsPerMillion +
|
|
279
|
+
(Math.max(0, (usage.durableObjectsGbSeconds ?? 0) - hourlyDoGbSecondsAllowance) / 1_000_000) *
|
|
280
|
+
PRICING_TIERS.durableObjects.gbSecondsPerMillion;
|
|
281
|
+
|
|
282
|
+
// Workers AI (neurons-based)
|
|
283
|
+
const neuronsPerUsd = PRICING_TIERS.workersAI.neuronsPerThousand / 1000;
|
|
284
|
+
const workersAICost = (usage.workersAINeurons ?? 0) * neuronsPerUsd;
|
|
285
|
+
|
|
286
|
+
// Queues
|
|
287
|
+
const queuesCost =
|
|
288
|
+
((usage.queuesMessages ?? 0) / 1_000_000) * PRICING_TIERS.queues.messagesPerMillion;
|
|
289
|
+
|
|
290
|
+
// Pages and Workflows (typically 0 for hourly granularity)
|
|
291
|
+
const pagesCost = 0;
|
|
292
|
+
const workflowsCost = 0;
|
|
293
|
+
|
|
294
|
+
const total =
|
|
295
|
+
workersCost +
|
|
296
|
+
d1Cost +
|
|
297
|
+
kvCost +
|
|
298
|
+
r2Cost +
|
|
299
|
+
vectorizeCost +
|
|
300
|
+
aiGatewayCost +
|
|
301
|
+
durableObjectsCost +
|
|
302
|
+
workersAICost +
|
|
303
|
+
queuesCost +
|
|
304
|
+
pagesCost +
|
|
305
|
+
workflowsCost;
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
workers: workersCost,
|
|
309
|
+
d1: d1Cost,
|
|
310
|
+
kv: kvCost,
|
|
311
|
+
r2: r2Cost,
|
|
312
|
+
durableObjects: durableObjectsCost,
|
|
313
|
+
vectorize: vectorizeCost,
|
|
314
|
+
aiGateway: aiGatewayCost,
|
|
315
|
+
workersAI: workersAICost,
|
|
316
|
+
pages: pagesCost,
|
|
317
|
+
queues: queuesCost,
|
|
318
|
+
workflows: workflowsCost,
|
|
319
|
+
total,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Calculate the prorated base cost for a given number of hours.
|
|
325
|
+
* Useful for custom time period calculations.
|
|
326
|
+
*
|
|
327
|
+
* @param hours - Number of hours in the period
|
|
328
|
+
* @returns Prorated base cost in USD
|
|
329
|
+
*/
|
|
330
|
+
export function prorateBaseCost(hours: number): number {
|
|
331
|
+
return (PRICING_TIERS.workers.baseCostMonthly / HOURS_PER_MONTH) * hours;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Calculate the prorated base cost for a given number of days.
|
|
336
|
+
* Useful for daily rollup calculations.
|
|
337
|
+
*
|
|
338
|
+
* @param days - Number of days in the period
|
|
339
|
+
* @returns Prorated base cost in USD
|
|
340
|
+
*/
|
|
341
|
+
export function prorateBaseCostByDays(days: number): number {
|
|
342
|
+
return (PRICING_TIERS.workers.baseCostMonthly / DAYS_PER_MONTH) * days;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// =============================================================================
|
|
346
|
+
// DAILY BILLABLE COST CALCULATION
|
|
347
|
+
// =============================================================================
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Account-level daily usage metrics for billable cost calculation.
|
|
351
|
+
* These are the raw usage values aggregated across all projects.
|
|
352
|
+
* Named differently to avoid conflict with DailyUsageMetrics from dashboard.
|
|
353
|
+
*/
|
|
354
|
+
export interface AccountDailyUsage {
|
|
355
|
+
workersRequests: number;
|
|
356
|
+
workersCpuMs: number;
|
|
357
|
+
d1RowsRead: number;
|
|
358
|
+
d1RowsWritten: number;
|
|
359
|
+
d1StorageBytes: number;
|
|
360
|
+
kvReads: number;
|
|
361
|
+
kvWrites: number;
|
|
362
|
+
kvDeletes: number;
|
|
363
|
+
kvLists: number;
|
|
364
|
+
kvStorageBytes: number;
|
|
365
|
+
r2ClassA: number;
|
|
366
|
+
r2ClassB: number;
|
|
367
|
+
r2StorageBytes: number;
|
|
368
|
+
doRequests: number;
|
|
369
|
+
doGbSeconds: number;
|
|
370
|
+
doStorageReads: number;
|
|
371
|
+
doStorageWrites: number;
|
|
372
|
+
doStorageDeletes: number;
|
|
373
|
+
vectorizeQueries: number;
|
|
374
|
+
vectorizeStoredDimensions: number;
|
|
375
|
+
aiGatewayRequests: number;
|
|
376
|
+
workersAINeurons: number;
|
|
377
|
+
queuesMessagesProduced: number;
|
|
378
|
+
queuesMessagesConsumed: number;
|
|
379
|
+
pagesDeployments: number;
|
|
380
|
+
pagesBandwidthBytes: number;
|
|
381
|
+
workflowsExecutions: number;
|
|
382
|
+
workflowsCpuMs: number;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Billable cost breakdown result for daily rollups.
|
|
387
|
+
* All values are in USD after allowance subtraction.
|
|
388
|
+
*/
|
|
389
|
+
export interface DailyBillableCostBreakdown {
|
|
390
|
+
workers: number;
|
|
391
|
+
d1: number;
|
|
392
|
+
kv: number;
|
|
393
|
+
r2: number;
|
|
394
|
+
durableObjects: number;
|
|
395
|
+
vectorize: number;
|
|
396
|
+
aiGateway: number;
|
|
397
|
+
workersAI: number;
|
|
398
|
+
pages: number;
|
|
399
|
+
queues: number;
|
|
400
|
+
workflows: number;
|
|
401
|
+
total: number;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Calculate actual billable costs for a daily period with proper allowance subtraction.
|
|
406
|
+
*
|
|
407
|
+
* Formula: billable_cost = max(0, usage - prorated_allowance) * rate
|
|
408
|
+
*
|
|
409
|
+
* This function applies the Workers Paid Plan allowances at account level,
|
|
410
|
+
* meaning allowances are subtracted ONCE from total account usage,
|
|
411
|
+
* not per-project.
|
|
412
|
+
*
|
|
413
|
+
* @param usage - The daily usage metrics (should be account-level totals)
|
|
414
|
+
* @param daysElapsed - Days elapsed in current billing period (for proration)
|
|
415
|
+
* @param daysInPeriod - Total days in billing period (typically 28-31)
|
|
416
|
+
* @returns Billable cost breakdown with allowances applied
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* // Account used 2M D1 writes in 15 days of a 30-day month
|
|
420
|
+
* // Monthly allowance: 50M writes, prorated: 25M writes
|
|
421
|
+
* // Billable: max(0, 2M - 25M) * $1/M = $0 (under allowance)
|
|
422
|
+
*/
|
|
423
|
+
export function calculateDailyBillableCosts(
|
|
424
|
+
usage: AccountDailyUsage,
|
|
425
|
+
daysElapsed: number,
|
|
426
|
+
daysInPeriod: number
|
|
427
|
+
): DailyBillableCostBreakdown {
|
|
428
|
+
// Proration factor: how much of monthly allowance is available for this period
|
|
429
|
+
const prorationFactor = daysInPeriod > 0 ? daysElapsed / daysInPeriod : 1;
|
|
430
|
+
|
|
431
|
+
// Helper: calculate billable usage after prorated allowance
|
|
432
|
+
const billableUsage = (usageValue: number, monthlyAllowance: number): number => {
|
|
433
|
+
const proratedAllowance = monthlyAllowance * prorationFactor;
|
|
434
|
+
return Math.max(0, usageValue - proratedAllowance);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// ==========================================================================
|
|
438
|
+
// Workers: $5/month base + $0.30/million requests after 10M included
|
|
439
|
+
// ==========================================================================
|
|
440
|
+
// Prorate base cost for the billing period
|
|
441
|
+
const workersBaseCost = (PRICING_TIERS.workers.baseCostMonthly / daysInPeriod) * daysElapsed;
|
|
442
|
+
|
|
443
|
+
// Requests: subtract prorated 10M included
|
|
444
|
+
const billableRequests = billableUsage(
|
|
445
|
+
usage.workersRequests,
|
|
446
|
+
PRICING_TIERS.workers.includedRequests
|
|
447
|
+
);
|
|
448
|
+
const workersRequestsCost =
|
|
449
|
+
(billableRequests / 1_000_000) * PRICING_TIERS.workers.requestsPerMillion;
|
|
450
|
+
|
|
451
|
+
// CPU time: no allowance, pure usage
|
|
452
|
+
const workersCpuCost = (usage.workersCpuMs / 1_000_000) * PRICING_TIERS.workers.cpuMsPerMillion;
|
|
453
|
+
|
|
454
|
+
const workersCost = workersBaseCost + workersRequestsCost + workersCpuCost;
|
|
455
|
+
|
|
456
|
+
// ==========================================================================
|
|
457
|
+
// D1: 25B reads + 50M writes included
|
|
458
|
+
// ==========================================================================
|
|
459
|
+
const billableD1Reads = billableUsage(usage.d1RowsRead, PAID_ALLOWANCES.d1.rowsRead);
|
|
460
|
+
const billableD1Writes = billableUsage(usage.d1RowsWritten, PAID_ALLOWANCES.d1.rowsWritten);
|
|
461
|
+
|
|
462
|
+
const d1Cost =
|
|
463
|
+
(billableD1Reads / 1_000_000_000) * PRICING_TIERS.d1.rowsReadPerBillion +
|
|
464
|
+
(billableD1Writes / 1_000_000) * PRICING_TIERS.d1.rowsWrittenPerMillion +
|
|
465
|
+
(usage.d1StorageBytes / 1_000_000_000) * PRICING_TIERS.d1.storagePerGb;
|
|
466
|
+
|
|
467
|
+
// ==========================================================================
|
|
468
|
+
// KV: 10M reads, 1M writes/deletes/lists included
|
|
469
|
+
// ==========================================================================
|
|
470
|
+
const billableKvReads = billableUsage(usage.kvReads, PAID_ALLOWANCES.kv.reads);
|
|
471
|
+
const billableKvWrites = billableUsage(usage.kvWrites, PAID_ALLOWANCES.kv.writes);
|
|
472
|
+
const billableKvDeletes = billableUsage(usage.kvDeletes, PAID_ALLOWANCES.kv.deletes);
|
|
473
|
+
const billableKvLists = billableUsage(usage.kvLists, PAID_ALLOWANCES.kv.lists);
|
|
474
|
+
|
|
475
|
+
const kvCost =
|
|
476
|
+
(billableKvReads / 1_000_000) * PRICING_TIERS.kv.readsPerMillion +
|
|
477
|
+
(billableKvWrites / 1_000_000) * PRICING_TIERS.kv.writesPerMillion +
|
|
478
|
+
(billableKvDeletes / 1_000_000) * PRICING_TIERS.kv.deletesPerMillion +
|
|
479
|
+
(billableKvLists / 1_000_000) * PRICING_TIERS.kv.listsPerMillion +
|
|
480
|
+
(usage.kvStorageBytes / 1_000_000_000) * PRICING_TIERS.kv.storagePerGb;
|
|
481
|
+
|
|
482
|
+
// ==========================================================================
|
|
483
|
+
// R2: 10GB storage, 1M Class A, 10M Class B included
|
|
484
|
+
// ==========================================================================
|
|
485
|
+
const billableR2ClassA = billableUsage(usage.r2ClassA, PAID_ALLOWANCES.r2.classA);
|
|
486
|
+
const billableR2ClassB = billableUsage(usage.r2ClassB, PAID_ALLOWANCES.r2.classB);
|
|
487
|
+
const billableR2Storage = billableUsage(usage.r2StorageBytes, PAID_ALLOWANCES.r2.storage);
|
|
488
|
+
|
|
489
|
+
const r2Cost =
|
|
490
|
+
(billableR2ClassA / 1_000_000) * PRICING_TIERS.r2.classAPerMillion +
|
|
491
|
+
(billableR2ClassB / 1_000_000) * PRICING_TIERS.r2.classBPerMillion +
|
|
492
|
+
(billableR2Storage / 1_000_000_000) * PRICING_TIERS.r2.storagePerGbMonth;
|
|
493
|
+
|
|
494
|
+
// ==========================================================================
|
|
495
|
+
// Durable Objects: 1M requests, 400K GB-seconds included
|
|
496
|
+
// ==========================================================================
|
|
497
|
+
const billableDoRequests = billableUsage(
|
|
498
|
+
usage.doRequests,
|
|
499
|
+
PAID_ALLOWANCES.durableObjects.requests
|
|
500
|
+
);
|
|
501
|
+
const billableDoGbSeconds = billableUsage(
|
|
502
|
+
usage.doGbSeconds,
|
|
503
|
+
PAID_ALLOWANCES.durableObjects.gbSeconds
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const durableObjectsCost =
|
|
507
|
+
(billableDoRequests / 1_000_000) * PRICING_TIERS.durableObjects.requestsPerMillion +
|
|
508
|
+
(billableDoGbSeconds / 1_000_000) * PRICING_TIERS.durableObjects.gbSecondsPerMillion +
|
|
509
|
+
(usage.doStorageReads / 1_000_000) * PRICING_TIERS.durableObjects.readsPerMillion +
|
|
510
|
+
(usage.doStorageWrites / 1_000_000) * PRICING_TIERS.durableObjects.writesPerMillion +
|
|
511
|
+
(usage.doStorageDeletes / 1_000_000) * PRICING_TIERS.durableObjects.deletesPerMillion;
|
|
512
|
+
|
|
513
|
+
// ==========================================================================
|
|
514
|
+
// Vectorize: 10M stored + 50M queried dimensions included
|
|
515
|
+
// ==========================================================================
|
|
516
|
+
const billableVectorizeStored = billableUsage(
|
|
517
|
+
usage.vectorizeStoredDimensions,
|
|
518
|
+
PAID_ALLOWANCES.vectorize.storedDimensions
|
|
519
|
+
);
|
|
520
|
+
const billableVectorizeQueried = billableUsage(
|
|
521
|
+
usage.vectorizeQueries,
|
|
522
|
+
PAID_ALLOWANCES.vectorize.queriedDimensions
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const vectorizeCost =
|
|
526
|
+
(billableVectorizeStored / 1_000_000) * PRICING_TIERS.vectorize.storedDimensionsPerMillion +
|
|
527
|
+
(billableVectorizeQueried / 1_000_000) * PRICING_TIERS.vectorize.queriedDimensionsPerMillion;
|
|
528
|
+
|
|
529
|
+
// ==========================================================================
|
|
530
|
+
// AI Gateway: Free (logs only)
|
|
531
|
+
// ==========================================================================
|
|
532
|
+
const aiGatewayCost = 0;
|
|
533
|
+
|
|
534
|
+
// ==========================================================================
|
|
535
|
+
// Workers AI: No included allowance (conservative - pay per use)
|
|
536
|
+
// ==========================================================================
|
|
537
|
+
const neuronsPerUsd = PRICING_TIERS.workersAI.neuronsPerThousand / 1000;
|
|
538
|
+
const workersAICost = usage.workersAINeurons * neuronsPerUsd;
|
|
539
|
+
|
|
540
|
+
// ==========================================================================
|
|
541
|
+
// Queues: 1M operations included
|
|
542
|
+
// ==========================================================================
|
|
543
|
+
const totalQueueMessages = usage.queuesMessagesProduced + usage.queuesMessagesConsumed;
|
|
544
|
+
const billableQueueMessages = billableUsage(totalQueueMessages, PAID_ALLOWANCES.queues.operations);
|
|
545
|
+
const queuesCost =
|
|
546
|
+
(billableQueueMessages / 1_000_000) * PRICING_TIERS.queues.messagesPerMillion;
|
|
547
|
+
|
|
548
|
+
// ==========================================================================
|
|
549
|
+
// Pages: 500 builds free, 100GB bandwidth free
|
|
550
|
+
// ==========================================================================
|
|
551
|
+
// Note: We track builds and bandwidth but proration is complex
|
|
552
|
+
// For now, assume under free tier (500 builds/month, 100GB bandwidth)
|
|
553
|
+
const pagesCost = 0; // TODO: Implement pages cost if exceeding free tier
|
|
554
|
+
|
|
555
|
+
// ==========================================================================
|
|
556
|
+
// Workflows: Beta (no pricing yet)
|
|
557
|
+
// ==========================================================================
|
|
558
|
+
const workflowsCost = 0;
|
|
559
|
+
|
|
560
|
+
// ==========================================================================
|
|
561
|
+
// Total
|
|
562
|
+
// ==========================================================================
|
|
563
|
+
const total =
|
|
564
|
+
workersCost +
|
|
565
|
+
d1Cost +
|
|
566
|
+
kvCost +
|
|
567
|
+
r2Cost +
|
|
568
|
+
durableObjectsCost +
|
|
569
|
+
vectorizeCost +
|
|
570
|
+
aiGatewayCost +
|
|
571
|
+
workersAICost +
|
|
572
|
+
queuesCost +
|
|
573
|
+
pagesCost +
|
|
574
|
+
workflowsCost;
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
workers: Math.max(0, workersCost),
|
|
578
|
+
d1: Math.max(0, d1Cost),
|
|
579
|
+
kv: Math.max(0, kvCost),
|
|
580
|
+
r2: Math.max(0, r2Cost),
|
|
581
|
+
durableObjects: Math.max(0, durableObjectsCost),
|
|
582
|
+
vectorize: Math.max(0, vectorizeCost),
|
|
583
|
+
aiGateway: aiGatewayCost,
|
|
584
|
+
workersAI: Math.max(0, workersAICost),
|
|
585
|
+
pages: pagesCost,
|
|
586
|
+
queues: Math.max(0, queuesCost),
|
|
587
|
+
workflows: workflowsCost,
|
|
588
|
+
total: Math.max(0, total),
|
|
589
|
+
};
|
|
590
|
+
}
|