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.
@@ -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(((this.maxTotalChargeUsd - this.calculateTotalChargedAmount()) / rowCost).toFixed(4)));
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 = (this.maxTotalChargeUsd - this.calculateTotalChargedAmount()) / price;
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 < 4 && rows > 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 rows it pays for.
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 = shortTo;
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.22",
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",