@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/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
+ }