scrapely 1.2.22 → 1.2.24
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/charging.d.ts +25 -0
- package/dist/charging.js +57 -5
- package/package.json +2 -1
package/dist/charging.d.ts
CHANGED
|
@@ -86,6 +86,31 @@ export declare class ChargingManager {
|
|
|
86
86
|
* Actors never go positive, so they get nothing extra for free.
|
|
87
87
|
*/
|
|
88
88
|
private prepaidRowBalance;
|
|
89
|
+
/**
|
|
90
|
+
* Run spend the platform counts against maxTotalChargeUsd that this SDK does
|
|
91
|
+
* not charge itself (compute/proxy/browser usage, server-billed events).
|
|
92
|
+
* Learnt from the charge endpoint's remainingBudgetNd; only ever grows.
|
|
93
|
+
*/
|
|
94
|
+
private platformOtherUsd;
|
|
95
|
+
private lastRemainingBudgetNd;
|
|
96
|
+
/** True once the platform reported a remaining budget (its usage is then in the local budget). */
|
|
97
|
+
private platformBudgetSeen;
|
|
98
|
+
/** maxTotalChargeUsd minus this SDK's charges and the platform's other run spend. */
|
|
99
|
+
private remainingBudgetUsd;
|
|
100
|
+
/** Fold the last charge response's remainingBudgetNd into the local budget (monotonic). */
|
|
101
|
+
private syncPlatformBudget;
|
|
102
|
+
/**
|
|
103
|
+
* @internal Whole rows a multi-event retry can pay for after a short answer:
|
|
104
|
+
* what the voided attempt was accepted (budget the void gives back) at the
|
|
105
|
+
* full per-row cost, or the local remaining budget once the platform has
|
|
106
|
+
* reported it (then it includes the leftover after the short answer).
|
|
107
|
+
*/
|
|
108
|
+
_rowsAfterShort(attempt: Array<{
|
|
109
|
+
name: string;
|
|
110
|
+
charged: number;
|
|
111
|
+
}>, perRow: Record<string, number>): number;
|
|
112
|
+
/** @internal Platform spend outside this SDK's charges (tests). */
|
|
113
|
+
_platformOtherUsd(): number;
|
|
89
114
|
/** @internal Called by Actor shutdown before the final flush. */
|
|
90
115
|
close(): void;
|
|
91
116
|
/**
|
package/dist/charging.js
CHANGED
|
@@ -165,6 +165,51 @@ class ChargingManager {
|
|
|
165
165
|
* Actors never go positive, so they get nothing extra for free.
|
|
166
166
|
*/
|
|
167
167
|
prepaidRowBalance = 0;
|
|
168
|
+
/**
|
|
169
|
+
* Run spend the platform counts against maxTotalChargeUsd that this SDK does
|
|
170
|
+
* not charge itself (compute/proxy/browser usage, server-billed events).
|
|
171
|
+
* Learnt from the charge endpoint's remainingBudgetNd; only ever grows.
|
|
172
|
+
*/
|
|
173
|
+
platformOtherUsd = 0;
|
|
174
|
+
lastRemainingBudgetNd = null;
|
|
175
|
+
/** True once the platform reported a remaining budget (its usage is then in the local budget). */
|
|
176
|
+
platformBudgetSeen = false;
|
|
177
|
+
/** maxTotalChargeUsd minus this SDK's charges and the platform's other run spend. */
|
|
178
|
+
remainingBudgetUsd() {
|
|
179
|
+
return this.maxTotalChargeUsd - this.calculateTotalChargedAmount() - this.platformOtherUsd;
|
|
180
|
+
}
|
|
181
|
+
/** Fold the last charge response's remainingBudgetNd into the local budget (monotonic). */
|
|
182
|
+
syncPlatformBudget() {
|
|
183
|
+
const nd = this.lastRemainingBudgetNd;
|
|
184
|
+
this.lastRemainingBudgetNd = null;
|
|
185
|
+
if (nd === null || nd < 0 || !Number.isFinite(this.maxTotalChargeUsd))
|
|
186
|
+
return;
|
|
187
|
+
this.platformBudgetSeen = true;
|
|
188
|
+
const other = Number((this.maxTotalChargeUsd - nd / 1e9 - this.calculateTotalChargedAmount()).toFixed(9));
|
|
189
|
+
if (other > this.platformOtherUsd)
|
|
190
|
+
this.platformOtherUsd = other;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @internal Whole rows a multi-event retry can pay for after a short answer:
|
|
194
|
+
* what the voided attempt was accepted (budget the void gives back) at the
|
|
195
|
+
* full per-row cost, or the local remaining budget once the platform has
|
|
196
|
+
* reported it (then it includes the leftover after the short answer).
|
|
197
|
+
*/
|
|
198
|
+
_rowsAfterShort(attempt, perRow) {
|
|
199
|
+
const rowCost = Object.entries(perRow).reduce((a, [n, c]) => a + (this.calculateEventPrice(n) ?? 0) * c, 0);
|
|
200
|
+
if (rowCost <= 0)
|
|
201
|
+
return 0;
|
|
202
|
+
const spent = attempt.reduce((a, op) => a + (this.calculateEventPrice(op.name) ?? 0) * op.charged, 0);
|
|
203
|
+
let fit = Math.floor(Number((spent / rowCost).toFixed(4)));
|
|
204
|
+
if (this.platformBudgetSeen && Number.isFinite(this.maxTotalChargeUsd)) {
|
|
205
|
+
fit = Math.max(fit, Math.floor(Number((this.remainingBudgetUsd() / rowCost).toFixed(4))));
|
|
206
|
+
}
|
|
207
|
+
return Math.max(0, fit);
|
|
208
|
+
}
|
|
209
|
+
/** @internal Platform spend outside this SDK's charges (tests). */
|
|
210
|
+
_platformOtherUsd() {
|
|
211
|
+
return this.platformOtherUsd;
|
|
212
|
+
}
|
|
168
213
|
/** @internal Called by Actor shutdown before the final flush. */
|
|
169
214
|
close() {
|
|
170
215
|
this._closed = true;
|
|
@@ -255,7 +300,7 @@ class ChargingManager {
|
|
|
255
300
|
const rowCost = Object.entries(perRow).reduce((a, [name, n]) => a + (this.calculateEventPrice(name) ?? 0) * n, 0);
|
|
256
301
|
let rows = rowCount;
|
|
257
302
|
if (rowCost > 0 && Number.isFinite(this.maxTotalChargeUsd)) {
|
|
258
|
-
const fit = Math.floor(Number((
|
|
303
|
+
const fit = Math.floor(Number((this.remainingBudgetUsd() / rowCost).toFixed(4)));
|
|
259
304
|
rows = Math.min(rows, Math.max(0, fit));
|
|
260
305
|
}
|
|
261
306
|
return { perRow, rows };
|
|
@@ -543,7 +588,7 @@ class ChargingManager {
|
|
|
543
588
|
return eventName;
|
|
544
589
|
}
|
|
545
590
|
calculateMaxChargesByPrice(price) {
|
|
546
|
-
const unroundedResult =
|
|
591
|
+
const unroundedResult = this.remainingBudgetUsd() / price;
|
|
547
592
|
const roundedResult = Math.floor(Number(unroundedResult.toFixed(4)));
|
|
548
593
|
return Math.max(0, roundedResult);
|
|
549
594
|
}
|
|
@@ -651,6 +696,8 @@ class ChargingManager {
|
|
|
651
696
|
});
|
|
652
697
|
const body = extractChargeResponseBody(response);
|
|
653
698
|
const accepted = readAcceptedCount(body, intent.count);
|
|
699
|
+
const remaining = body.remainingBudgetNd;
|
|
700
|
+
this.lastRemainingBudgetNd = typeof remaining === 'number' && Number.isFinite(remaining) && remaining >= 0 ? remaining : null;
|
|
654
701
|
return accepted;
|
|
655
702
|
}
|
|
656
703
|
catch (err) {
|
|
@@ -956,6 +1003,7 @@ class ChargingManager {
|
|
|
956
1003
|
if (accepted < chargedCount)
|
|
957
1004
|
this.markChargeLimitHit();
|
|
958
1005
|
chargedCount = this.applyAcceptedCount(normalizedEventName, chargedCount, accepted, eventPricing.price);
|
|
1006
|
+
this.syncPlatformBudget();
|
|
959
1007
|
}
|
|
960
1008
|
else {
|
|
961
1009
|
throw new ChargeUncertainError(`Charge for event '${normalizedEventName}' (count=${chargedCount}) was not confirmed by the platform; operationId=${intent.idempotencyKey}`, {
|
|
@@ -1333,7 +1381,7 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
|
|
|
1333
1381
|
};
|
|
1334
1382
|
const order = Object.entries(perRow).sort(([a], [b]) => Number(isSyntheticEvent(a)) - Number(isSyntheticEvent(b)) || a.localeCompare(b));
|
|
1335
1383
|
let ops = [];
|
|
1336
|
-
for (let attempt = 0; attempt <
|
|
1384
|
+
for (let attempt = 0; attempt < 6 && rows > 0; attempt++) {
|
|
1337
1385
|
ops = [];
|
|
1338
1386
|
let shortTo = null;
|
|
1339
1387
|
for (const [name, n] of order) {
|
|
@@ -1361,10 +1409,14 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
|
|
|
1361
1409
|
}
|
|
1362
1410
|
if (shortTo === null)
|
|
1363
1411
|
break;
|
|
1364
|
-
// An event came back short: void this attempt and retry with the
|
|
1412
|
+
// An event came back short: void this attempt and retry with the whole
|
|
1413
|
+
// rows the budget covers at the FULL per-row cost (sizing from the short
|
|
1414
|
+
// event alone came back short again: appstore 64df0a3608d74af88, 0 saved).
|
|
1415
|
+
// At least one single-row try: the last affordable row may still fit.
|
|
1416
|
+
const attemptOps = ops.map((op) => ({ name: op.name, charged: op.charged }));
|
|
1365
1417
|
await undo(ops);
|
|
1366
1418
|
ops = [];
|
|
1367
|
-
rows =
|
|
1419
|
+
rows = Math.min(rows - 1, Math.max(chargingManager._rowsAfterShort(attemptOps, perRow), 1));
|
|
1368
1420
|
}
|
|
1369
1421
|
if (rows <= 0 || ops.length === 0) {
|
|
1370
1422
|
await undo(ops);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scrapely",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.24",
|
|
4
4
|
"description": "Scrapely SDK for building JavaScript and TypeScript Actors",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"test:charging": "npx tsx --test tests/charging-pending.test.ts",
|
|
25
25
|
"test:charge-limit-guard": "npx tsx --test tests/charge-limit-guard.test.ts",
|
|
26
26
|
"test:charge-multi-event": "npx tsx --test tests/charge-multi-event.test.ts",
|
|
27
|
+
"test:charge-platform-budget": "npx tsx --test tests/charge-platform-budget.test.ts",
|
|
27
28
|
"test:storage-retries": "npx tsx --test tests/storage-retries.test.ts",
|
|
28
29
|
"test:push-idempotency": "npx tsx --test tests/push-idempotency.test.ts",
|
|
29
30
|
"test:stdio-drain": "npx tsx --test tests/stdio-drain.test.ts",
|