@drawbridge/drawbridge-utils 0.0.70 → 0.0.72
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/billing.cjs +83 -0
- package/dist/billing.d.cts +114 -2
- package/dist/billing.d.ts +114 -2
- package/dist/billing.js +83 -0
- package/package.json +1 -1
package/dist/billing.cjs
CHANGED
|
@@ -277,9 +277,35 @@ var scrapeFee = ({ provider, bytes, requests, url, cold }) => {
|
|
|
277
277
|
}
|
|
278
278
|
return LOCAL_FLAT_CENTS;
|
|
279
279
|
};
|
|
280
|
+
var scrapeBreakdown = ({ provider, bytes, requests, url }) => {
|
|
281
|
+
if (provider === "brightdata") {
|
|
282
|
+
return {
|
|
283
|
+
provider,
|
|
284
|
+
basis: "bytes",
|
|
285
|
+
units: bytes || 0,
|
|
286
|
+
rate: ratePerGB(url),
|
|
287
|
+
rateUnit: "GB",
|
|
288
|
+
cents: scrapeFee({ provider, bytes, url, cold: true })
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (provider === "brightdata-unlocker") {
|
|
292
|
+
return {
|
|
293
|
+
provider,
|
|
294
|
+
basis: "requests",
|
|
295
|
+
units: requests || 0,
|
|
296
|
+
// cpmCents is cents-per-request; ×10 expresses it as dollars per 1,000.
|
|
297
|
+
rate: cpmCents(url) * 10,
|
|
298
|
+
rateUnit: "1K requests",
|
|
299
|
+
cents: scrapeFee({ provider, requests, url, cold: true })
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return { provider: provider || "local", basis: "local", units: null, rate: null, rateUnit: null, cents: LOCAL_FLAT_CENTS };
|
|
303
|
+
};
|
|
280
304
|
var scrape = {
|
|
281
305
|
// Cents for a scrape given its provider/bytes/cold basis.
|
|
282
306
|
fee: scrapeFee,
|
|
307
|
+
// Itemized cold-fee breakdown (basis/units/rate/cents) for display + the scrape doc.
|
|
308
|
+
breakdown: scrapeBreakdown,
|
|
283
309
|
// Deferred bill(scrapeId) the scrape store's resolve() hands the generation
|
|
284
310
|
// flow. Atomically claims the cold (first-time) fee via the `charged` flag so
|
|
285
311
|
// a BrightData scrape's metered cost is recovered ONCE; later reuses pay the
|
|
@@ -361,6 +387,63 @@ var action = {
|
|
|
361
387
|
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
|
|
362
388
|
}
|
|
363
389
|
},
|
|
390
|
+
// Batch variant of record() for a caller writing many ledger rows inside ONE
|
|
391
|
+
// transaction. A session forbids concurrent ops, so the alternative is a serial
|
|
392
|
+
// await-loop costing N round-trips; this does a single bulkWrite of idempotent
|
|
393
|
+
// { usage, key } upserts (ordered:false), then bumps usage.totals.actions by the
|
|
394
|
+
// summed units of ONLY the genuinely-inserted rows (upsertedIds) — mirroring
|
|
395
|
+
// record()'s per-row `inserted` gate. Rows are deduped by key within the batch
|
|
396
|
+
// so two upserts can't target the same { usage, key }. Pass `session` to join
|
|
397
|
+
// the caller's transaction (the intended path).
|
|
398
|
+
bulk: async ({ controller, organization, usageId, rows = [], session }) => {
|
|
399
|
+
if (!controller || !organization || !usageId || !Array.isArray(rows) || rows.length === 0) return;
|
|
400
|
+
const seen = /* @__PURE__ */ new Set();
|
|
401
|
+
const valid = [];
|
|
402
|
+
for (const row of rows) {
|
|
403
|
+
const { type, units = 1, key, meta = {}, traceId } = row || {};
|
|
404
|
+
const amount2 = Number(units);
|
|
405
|
+
if (!type || !key || !Number.isFinite(amount2) || amount2 === 0) continue;
|
|
406
|
+
if (seen.has(key)) continue;
|
|
407
|
+
seen.add(key);
|
|
408
|
+
valid.push({ type, amount: amount2, key, meta, traceId });
|
|
409
|
+
}
|
|
410
|
+
if (valid.length === 0) return;
|
|
411
|
+
const result = await controller.bulk({
|
|
412
|
+
collection: "action",
|
|
413
|
+
pipeline: valid.map(({ type, amount: amount2, key, meta, traceId }) => ({
|
|
414
|
+
updateOne: {
|
|
415
|
+
filter: { key, usage: usageId },
|
|
416
|
+
update: {
|
|
417
|
+
$setOnInsert: {
|
|
418
|
+
organization,
|
|
419
|
+
usage: usageId,
|
|
420
|
+
type,
|
|
421
|
+
units: amount2,
|
|
422
|
+
key,
|
|
423
|
+
meta,
|
|
424
|
+
...traceId && { traceId }
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
upsert: true
|
|
428
|
+
}
|
|
429
|
+
})),
|
|
430
|
+
options: {
|
|
431
|
+
bypassDocumentValidation: true,
|
|
432
|
+
ordered: false,
|
|
433
|
+
...session && { session }
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
const amount = Object.keys((result == null ? void 0 : result.upsertedIds) || {}).reduce(
|
|
437
|
+
(sum, index) => {
|
|
438
|
+
var _a;
|
|
439
|
+
return sum + (((_a = valid[Number(index)]) == null ? void 0 : _a.amount) || 0);
|
|
440
|
+
},
|
|
441
|
+
0
|
|
442
|
+
);
|
|
443
|
+
if (amount > 0) {
|
|
444
|
+
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
|
|
445
|
+
}
|
|
446
|
+
},
|
|
364
447
|
// Authoritative per-period total to bill — sum(units) over the period's ledger
|
|
365
448
|
// rows. The Stripe meter emit (in the Stripe/sync layer) reports this number.
|
|
366
449
|
sum: async ({ controller, usageId }) => {
|
package/dist/billing.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { credit, debit } from './transactions.cjs';
|
|
2
|
-
import {
|
|
2
|
+
import { incrementUsageTotals, isDuplicateKey } from './usage.cjs';
|
|
3
3
|
import { createLogger } from '@drawbridge/drawbridge-telemetry';
|
|
4
4
|
|
|
5
5
|
// Billing — the single source of truth for what Drawbridge charges, and the
|
|
@@ -9,7 +9,7 @@ import { createLogger } from '@drawbridge/drawbridge-telemetry';
|
|
|
9
9
|
// billing.ai { price, cost, bill } AI requests → credit rail (debit)
|
|
10
10
|
// billing.scrape { fee, bill } scrape fees → credit rail (debit)
|
|
11
11
|
// billing.grant { bonus, admin } promo credits → credit rail (credit)
|
|
12
|
-
// billing.action { record, sum }
|
|
12
|
+
// billing.action { record, bulk, sum } usage actions → Stripe meter rail
|
|
13
13
|
//
|
|
14
14
|
// Sits on top of transactions.js (the generic credit/debit money rail) and
|
|
15
15
|
// usage.js (the action-ledger + usage-totals primitives). Shopify order fees are
|
|
@@ -226,11 +226,53 @@ const scrapeFee = ({ provider, bytes, requests, url, cold }) => {
|
|
|
226
226
|
|
|
227
227
|
};
|
|
228
228
|
|
|
229
|
+
// Itemized cold-fee breakdown for the scrape doc / admin dashboard: the same cents
|
|
230
|
+
// scrapeFee charges on a COLD scrape, plus the basis, the metered units, and the
|
|
231
|
+
// wholesale provider rate behind it. Reuse hits aren't represented here — the doc
|
|
232
|
+
// records its cold acquisition cost. `rate` is the wholesale rate in dollars per
|
|
233
|
+
// `rateUnit` (pre-markup, i.e. the BrightData list price); `cents` is the retail fee
|
|
234
|
+
// billed (incl MARKUP). Local renders have no external cost — flat, no rate.
|
|
235
|
+
const scrapeBreakdown = ({ provider, bytes, requests, url }) => {
|
|
236
|
+
|
|
237
|
+
if( provider === 'brightdata' ){
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
provider,
|
|
241
|
+
basis : 'bytes',
|
|
242
|
+
units : bytes || 0,
|
|
243
|
+
rate : ratePerGB( url ),
|
|
244
|
+
rateUnit : 'GB',
|
|
245
|
+
cents : scrapeFee({ provider, bytes, url, cold : true })
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if( provider === 'brightdata-unlocker' ){
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
provider,
|
|
254
|
+
basis : 'requests',
|
|
255
|
+
units : requests || 0,
|
|
256
|
+
// cpmCents is cents-per-request; ×10 expresses it as dollars per 1,000.
|
|
257
|
+
rate : cpmCents( url ) * 10,
|
|
258
|
+
rateUnit : '1K requests',
|
|
259
|
+
cents : scrapeFee({ provider, requests, url, cold : true })
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { provider : provider || 'local', basis : 'local', units : null, rate : null, rateUnit : null, cents : LOCAL_FLAT_CENTS };
|
|
265
|
+
|
|
266
|
+
};
|
|
267
|
+
|
|
229
268
|
const scrape = {
|
|
230
269
|
|
|
231
270
|
// Cents for a scrape given its provider/bytes/cold basis.
|
|
232
271
|
fee : scrapeFee,
|
|
233
272
|
|
|
273
|
+
// Itemized cold-fee breakdown (basis/units/rate/cents) for display + the scrape doc.
|
|
274
|
+
breakdown : scrapeBreakdown,
|
|
275
|
+
|
|
234
276
|
// Deferred bill(scrapeId) the scrape store's resolve() hands the generation
|
|
235
277
|
// flow. Atomically claims the cold (first-time) fee via the `charged` flag so
|
|
236
278
|
// a BrightData scrape's metered cost is recovered ONCE; later reuses pay the
|
|
@@ -357,6 +399,76 @@ const action = {
|
|
|
357
399
|
|
|
358
400
|
},
|
|
359
401
|
|
|
402
|
+
// Batch variant of record() for a caller writing many ledger rows inside ONE
|
|
403
|
+
// transaction. A session forbids concurrent ops, so the alternative is a serial
|
|
404
|
+
// await-loop costing N round-trips; this does a single bulkWrite of idempotent
|
|
405
|
+
// { usage, key } upserts (ordered:false), then bumps usage.totals.actions by the
|
|
406
|
+
// summed units of ONLY the genuinely-inserted rows (upsertedIds) — mirroring
|
|
407
|
+
// record()'s per-row `inserted` gate. Rows are deduped by key within the batch
|
|
408
|
+
// so two upserts can't target the same { usage, key }. Pass `session` to join
|
|
409
|
+
// the caller's transaction (the intended path).
|
|
410
|
+
bulk : async ({ controller, organization, usageId, rows = [], session }) => {
|
|
411
|
+
|
|
412
|
+
if( ! controller || ! organization || ! usageId || ! Array.isArray( rows ) || rows.length === 0 ) return;
|
|
413
|
+
|
|
414
|
+
const seen = new Set();
|
|
415
|
+
const valid = [];
|
|
416
|
+
|
|
417
|
+
for( const row of rows ){
|
|
418
|
+
|
|
419
|
+
const { type, units = 1, key, meta = {}, traceId } = row || {};
|
|
420
|
+
const amount = Number( units );
|
|
421
|
+
|
|
422
|
+
if( ! type || ! key || ! Number.isFinite( amount ) || amount === 0 ) continue;
|
|
423
|
+
if( seen.has( key ) ) continue;
|
|
424
|
+
|
|
425
|
+
seen.add( key );
|
|
426
|
+
valid.push({ type, amount, key, meta, traceId });
|
|
427
|
+
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if( valid.length === 0 ) return;
|
|
431
|
+
|
|
432
|
+
const result = await controller.bulk({
|
|
433
|
+
collection : 'action',
|
|
434
|
+
pipeline : valid.map( ({ type, amount, key, meta, traceId }) => ({
|
|
435
|
+
updateOne : {
|
|
436
|
+
filter : { key, usage : usageId },
|
|
437
|
+
update : {
|
|
438
|
+
$setOnInsert : {
|
|
439
|
+
organization,
|
|
440
|
+
usage : usageId,
|
|
441
|
+
type,
|
|
442
|
+
units : amount,
|
|
443
|
+
key,
|
|
444
|
+
meta,
|
|
445
|
+
...( traceId && { traceId })
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
upsert : true
|
|
449
|
+
}
|
|
450
|
+
})),
|
|
451
|
+
options : {
|
|
452
|
+
bypassDocumentValidation : true,
|
|
453
|
+
ordered : false,
|
|
454
|
+
...( session && { session })
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// upsertedIds = { <opIndex>: _id } for newly-inserted rows; sum their units.
|
|
459
|
+
const amount = Object.keys( result?.upsertedIds || {} ).reduce(
|
|
460
|
+
( sum, index ) => sum + ( valid[ Number( index ) ]?.amount || 0 ),
|
|
461
|
+
0
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
if( amount > 0 ){
|
|
465
|
+
|
|
466
|
+
await incrementUsageTotals({ controller, usageId, $inc : { 'totals.actions' : amount }, session });
|
|
467
|
+
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
},
|
|
471
|
+
|
|
360
472
|
// Authoritative per-period total to bill — sum(units) over the period's ledger
|
|
361
473
|
// rows. The Stripe meter emit (in the Stripe/sync layer) reports this number.
|
|
362
474
|
sum : async ({ controller, usageId }) => {
|
package/dist/billing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { credit, debit } from './transactions.js';
|
|
2
|
-
import {
|
|
2
|
+
import { incrementUsageTotals, isDuplicateKey } from './usage.js';
|
|
3
3
|
import { createLogger } from '@drawbridge/drawbridge-telemetry';
|
|
4
4
|
|
|
5
5
|
// Billing — the single source of truth for what Drawbridge charges, and the
|
|
@@ -9,7 +9,7 @@ import { createLogger } from '@drawbridge/drawbridge-telemetry';
|
|
|
9
9
|
// billing.ai { price, cost, bill } AI requests → credit rail (debit)
|
|
10
10
|
// billing.scrape { fee, bill } scrape fees → credit rail (debit)
|
|
11
11
|
// billing.grant { bonus, admin } promo credits → credit rail (credit)
|
|
12
|
-
// billing.action { record, sum }
|
|
12
|
+
// billing.action { record, bulk, sum } usage actions → Stripe meter rail
|
|
13
13
|
//
|
|
14
14
|
// Sits on top of transactions.js (the generic credit/debit money rail) and
|
|
15
15
|
// usage.js (the action-ledger + usage-totals primitives). Shopify order fees are
|
|
@@ -226,11 +226,53 @@ const scrapeFee = ({ provider, bytes, requests, url, cold }) => {
|
|
|
226
226
|
|
|
227
227
|
};
|
|
228
228
|
|
|
229
|
+
// Itemized cold-fee breakdown for the scrape doc / admin dashboard: the same cents
|
|
230
|
+
// scrapeFee charges on a COLD scrape, plus the basis, the metered units, and the
|
|
231
|
+
// wholesale provider rate behind it. Reuse hits aren't represented here — the doc
|
|
232
|
+
// records its cold acquisition cost. `rate` is the wholesale rate in dollars per
|
|
233
|
+
// `rateUnit` (pre-markup, i.e. the BrightData list price); `cents` is the retail fee
|
|
234
|
+
// billed (incl MARKUP). Local renders have no external cost — flat, no rate.
|
|
235
|
+
const scrapeBreakdown = ({ provider, bytes, requests, url }) => {
|
|
236
|
+
|
|
237
|
+
if( provider === 'brightdata' ){
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
provider,
|
|
241
|
+
basis : 'bytes',
|
|
242
|
+
units : bytes || 0,
|
|
243
|
+
rate : ratePerGB( url ),
|
|
244
|
+
rateUnit : 'GB',
|
|
245
|
+
cents : scrapeFee({ provider, bytes, url, cold : true })
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if( provider === 'brightdata-unlocker' ){
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
provider,
|
|
254
|
+
basis : 'requests',
|
|
255
|
+
units : requests || 0,
|
|
256
|
+
// cpmCents is cents-per-request; ×10 expresses it as dollars per 1,000.
|
|
257
|
+
rate : cpmCents( url ) * 10,
|
|
258
|
+
rateUnit : '1K requests',
|
|
259
|
+
cents : scrapeFee({ provider, requests, url, cold : true })
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { provider : provider || 'local', basis : 'local', units : null, rate : null, rateUnit : null, cents : LOCAL_FLAT_CENTS };
|
|
265
|
+
|
|
266
|
+
};
|
|
267
|
+
|
|
229
268
|
const scrape = {
|
|
230
269
|
|
|
231
270
|
// Cents for a scrape given its provider/bytes/cold basis.
|
|
232
271
|
fee : scrapeFee,
|
|
233
272
|
|
|
273
|
+
// Itemized cold-fee breakdown (basis/units/rate/cents) for display + the scrape doc.
|
|
274
|
+
breakdown : scrapeBreakdown,
|
|
275
|
+
|
|
234
276
|
// Deferred bill(scrapeId) the scrape store's resolve() hands the generation
|
|
235
277
|
// flow. Atomically claims the cold (first-time) fee via the `charged` flag so
|
|
236
278
|
// a BrightData scrape's metered cost is recovered ONCE; later reuses pay the
|
|
@@ -357,6 +399,76 @@ const action = {
|
|
|
357
399
|
|
|
358
400
|
},
|
|
359
401
|
|
|
402
|
+
// Batch variant of record() for a caller writing many ledger rows inside ONE
|
|
403
|
+
// transaction. A session forbids concurrent ops, so the alternative is a serial
|
|
404
|
+
// await-loop costing N round-trips; this does a single bulkWrite of idempotent
|
|
405
|
+
// { usage, key } upserts (ordered:false), then bumps usage.totals.actions by the
|
|
406
|
+
// summed units of ONLY the genuinely-inserted rows (upsertedIds) — mirroring
|
|
407
|
+
// record()'s per-row `inserted` gate. Rows are deduped by key within the batch
|
|
408
|
+
// so two upserts can't target the same { usage, key }. Pass `session` to join
|
|
409
|
+
// the caller's transaction (the intended path).
|
|
410
|
+
bulk : async ({ controller, organization, usageId, rows = [], session }) => {
|
|
411
|
+
|
|
412
|
+
if( ! controller || ! organization || ! usageId || ! Array.isArray( rows ) || rows.length === 0 ) return;
|
|
413
|
+
|
|
414
|
+
const seen = new Set();
|
|
415
|
+
const valid = [];
|
|
416
|
+
|
|
417
|
+
for( const row of rows ){
|
|
418
|
+
|
|
419
|
+
const { type, units = 1, key, meta = {}, traceId } = row || {};
|
|
420
|
+
const amount = Number( units );
|
|
421
|
+
|
|
422
|
+
if( ! type || ! key || ! Number.isFinite( amount ) || amount === 0 ) continue;
|
|
423
|
+
if( seen.has( key ) ) continue;
|
|
424
|
+
|
|
425
|
+
seen.add( key );
|
|
426
|
+
valid.push({ type, amount, key, meta, traceId });
|
|
427
|
+
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if( valid.length === 0 ) return;
|
|
431
|
+
|
|
432
|
+
const result = await controller.bulk({
|
|
433
|
+
collection : 'action',
|
|
434
|
+
pipeline : valid.map( ({ type, amount, key, meta, traceId }) => ({
|
|
435
|
+
updateOne : {
|
|
436
|
+
filter : { key, usage : usageId },
|
|
437
|
+
update : {
|
|
438
|
+
$setOnInsert : {
|
|
439
|
+
organization,
|
|
440
|
+
usage : usageId,
|
|
441
|
+
type,
|
|
442
|
+
units : amount,
|
|
443
|
+
key,
|
|
444
|
+
meta,
|
|
445
|
+
...( traceId && { traceId })
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
upsert : true
|
|
449
|
+
}
|
|
450
|
+
})),
|
|
451
|
+
options : {
|
|
452
|
+
bypassDocumentValidation : true,
|
|
453
|
+
ordered : false,
|
|
454
|
+
...( session && { session })
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// upsertedIds = { <opIndex>: _id } for newly-inserted rows; sum their units.
|
|
459
|
+
const amount = Object.keys( result?.upsertedIds || {} ).reduce(
|
|
460
|
+
( sum, index ) => sum + ( valid[ Number( index ) ]?.amount || 0 ),
|
|
461
|
+
0
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
if( amount > 0 ){
|
|
465
|
+
|
|
466
|
+
await incrementUsageTotals({ controller, usageId, $inc : { 'totals.actions' : amount }, session });
|
|
467
|
+
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
},
|
|
471
|
+
|
|
360
472
|
// Authoritative per-period total to bill — sum(units) over the period's ledger
|
|
361
473
|
// rows. The Stripe meter emit (in the Stripe/sync layer) reports this number.
|
|
362
474
|
sum : async ({ controller, usageId }) => {
|
package/dist/billing.js
CHANGED
|
@@ -248,9 +248,35 @@ var scrapeFee = ({ provider, bytes, requests, url, cold }) => {
|
|
|
248
248
|
}
|
|
249
249
|
return LOCAL_FLAT_CENTS;
|
|
250
250
|
};
|
|
251
|
+
var scrapeBreakdown = ({ provider, bytes, requests, url }) => {
|
|
252
|
+
if (provider === "brightdata") {
|
|
253
|
+
return {
|
|
254
|
+
provider,
|
|
255
|
+
basis: "bytes",
|
|
256
|
+
units: bytes || 0,
|
|
257
|
+
rate: ratePerGB(url),
|
|
258
|
+
rateUnit: "GB",
|
|
259
|
+
cents: scrapeFee({ provider, bytes, url, cold: true })
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (provider === "brightdata-unlocker") {
|
|
263
|
+
return {
|
|
264
|
+
provider,
|
|
265
|
+
basis: "requests",
|
|
266
|
+
units: requests || 0,
|
|
267
|
+
// cpmCents is cents-per-request; ×10 expresses it as dollars per 1,000.
|
|
268
|
+
rate: cpmCents(url) * 10,
|
|
269
|
+
rateUnit: "1K requests",
|
|
270
|
+
cents: scrapeFee({ provider, requests, url, cold: true })
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { provider: provider || "local", basis: "local", units: null, rate: null, rateUnit: null, cents: LOCAL_FLAT_CENTS };
|
|
274
|
+
};
|
|
251
275
|
var scrape = {
|
|
252
276
|
// Cents for a scrape given its provider/bytes/cold basis.
|
|
253
277
|
fee: scrapeFee,
|
|
278
|
+
// Itemized cold-fee breakdown (basis/units/rate/cents) for display + the scrape doc.
|
|
279
|
+
breakdown: scrapeBreakdown,
|
|
254
280
|
// Deferred bill(scrapeId) the scrape store's resolve() hands the generation
|
|
255
281
|
// flow. Atomically claims the cold (first-time) fee via the `charged` flag so
|
|
256
282
|
// a BrightData scrape's metered cost is recovered ONCE; later reuses pay the
|
|
@@ -332,6 +358,63 @@ var action = {
|
|
|
332
358
|
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
|
|
333
359
|
}
|
|
334
360
|
},
|
|
361
|
+
// Batch variant of record() for a caller writing many ledger rows inside ONE
|
|
362
|
+
// transaction. A session forbids concurrent ops, so the alternative is a serial
|
|
363
|
+
// await-loop costing N round-trips; this does a single bulkWrite of idempotent
|
|
364
|
+
// { usage, key } upserts (ordered:false), then bumps usage.totals.actions by the
|
|
365
|
+
// summed units of ONLY the genuinely-inserted rows (upsertedIds) — mirroring
|
|
366
|
+
// record()'s per-row `inserted` gate. Rows are deduped by key within the batch
|
|
367
|
+
// so two upserts can't target the same { usage, key }. Pass `session` to join
|
|
368
|
+
// the caller's transaction (the intended path).
|
|
369
|
+
bulk: async ({ controller, organization, usageId, rows = [], session }) => {
|
|
370
|
+
if (!controller || !organization || !usageId || !Array.isArray(rows) || rows.length === 0) return;
|
|
371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
372
|
+
const valid = [];
|
|
373
|
+
for (const row of rows) {
|
|
374
|
+
const { type, units = 1, key, meta = {}, traceId } = row || {};
|
|
375
|
+
const amount2 = Number(units);
|
|
376
|
+
if (!type || !key || !Number.isFinite(amount2) || amount2 === 0) continue;
|
|
377
|
+
if (seen.has(key)) continue;
|
|
378
|
+
seen.add(key);
|
|
379
|
+
valid.push({ type, amount: amount2, key, meta, traceId });
|
|
380
|
+
}
|
|
381
|
+
if (valid.length === 0) return;
|
|
382
|
+
const result = await controller.bulk({
|
|
383
|
+
collection: "action",
|
|
384
|
+
pipeline: valid.map(({ type, amount: amount2, key, meta, traceId }) => ({
|
|
385
|
+
updateOne: {
|
|
386
|
+
filter: { key, usage: usageId },
|
|
387
|
+
update: {
|
|
388
|
+
$setOnInsert: {
|
|
389
|
+
organization,
|
|
390
|
+
usage: usageId,
|
|
391
|
+
type,
|
|
392
|
+
units: amount2,
|
|
393
|
+
key,
|
|
394
|
+
meta,
|
|
395
|
+
...traceId && { traceId }
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
upsert: true
|
|
399
|
+
}
|
|
400
|
+
})),
|
|
401
|
+
options: {
|
|
402
|
+
bypassDocumentValidation: true,
|
|
403
|
+
ordered: false,
|
|
404
|
+
...session && { session }
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
const amount = Object.keys((result == null ? void 0 : result.upsertedIds) || {}).reduce(
|
|
408
|
+
(sum, index) => {
|
|
409
|
+
var _a;
|
|
410
|
+
return sum + (((_a = valid[Number(index)]) == null ? void 0 : _a.amount) || 0);
|
|
411
|
+
},
|
|
412
|
+
0
|
|
413
|
+
);
|
|
414
|
+
if (amount > 0) {
|
|
415
|
+
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
|
|
416
|
+
}
|
|
417
|
+
},
|
|
335
418
|
// Authoritative per-period total to bill — sum(units) over the period's ledger
|
|
336
419
|
// rows. The Stripe meter emit (in the Stripe/sync layer) reports this number.
|
|
337
420
|
sum: async ({ controller, usageId }) => {
|
package/package.json
CHANGED