scrapely 1.2.23 → 1.2.25
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/actor.d.ts +7 -1
- package/dist/actor.js +1 -0
- package/dist/charging.d.ts +16 -2
- package/dist/charging.js +44 -14
- package/package.json +1 -1
package/dist/actor.d.ts
CHANGED
|
@@ -21,8 +21,14 @@ import { type ApiStorageConfig } from './storage/api-storage-client.js';
|
|
|
21
21
|
import { ChargingManager, type ChargeOptions as ChargingChargeOptions, type ChargeResult as ChargingChargeResult } from './charging.js';
|
|
22
22
|
/** Options for a multi-event charged push (`Actor.pushData(row, { charges })`). */
|
|
23
23
|
export interface PushDataChargeOptions {
|
|
24
|
-
/** Events per row, e.g. `{ result: 1, page: 3 }
|
|
24
|
+
/** Events per row, e.g. `{ result: 1, page: 3 }` (per batch with `chargePer: 'batch'`). */
|
|
25
25
|
charges: Record<string, number>;
|
|
26
|
+
/**
|
|
27
|
+
* 'row' (default): `charges` for every row. 'batch': once for the whole push, e.g.
|
|
28
|
+
* `pushData(pageRows, { charges: { document: 1 }, chargePer: 'batch' })`: all rows are
|
|
29
|
+
* stored if the charge is accepted, none if it is refused.
|
|
30
|
+
*/
|
|
31
|
+
chargePer?: 'row' | 'batch';
|
|
26
32
|
}
|
|
27
33
|
import { createProxyConfiguration, type CreateProxyConfigurationOptions } from './proxy-configuration.js';
|
|
28
34
|
import { type BrowserConfiguration, type BrowserConfigurationOptions } from './browser-configuration.js';
|
package/dist/actor.js
CHANGED
|
@@ -526,6 +526,7 @@ class Actor {
|
|
|
526
526
|
chargingManager: this.chargingManager,
|
|
527
527
|
items: item,
|
|
528
528
|
charges: eventNameOrOptions.charges,
|
|
529
|
+
chargePer: eventNameOrOptions.chargePer ?? 'row',
|
|
529
530
|
isDefaultDataset: dataset.id === this.config.get('defaultDatasetId'),
|
|
530
531
|
pushFn: async (rows) => { await dataset.pushData(rows); },
|
|
531
532
|
});
|
package/dist/charging.d.ts
CHANGED
|
@@ -93,10 +93,22 @@ export declare class ChargingManager {
|
|
|
93
93
|
*/
|
|
94
94
|
private platformOtherUsd;
|
|
95
95
|
private lastRemainingBudgetNd;
|
|
96
|
+
/** True once the platform reported a remaining budget (its usage is then in the local budget). */
|
|
97
|
+
private platformBudgetSeen;
|
|
96
98
|
/** maxTotalChargeUsd minus this SDK's charges and the platform's other run spend. */
|
|
97
99
|
private remainingBudgetUsd;
|
|
98
100
|
/** Fold the last charge response's remainingBudgetNd into the local budget (monotonic). */
|
|
99
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;
|
|
100
112
|
/** @internal Platform spend outside this SDK's charges (tests). */
|
|
101
113
|
_platformOtherUsd(): number;
|
|
102
114
|
/** @internal Called by Actor shutdown before the final flush. */
|
|
@@ -128,7 +140,7 @@ export declare class ChargingManager {
|
|
|
128
140
|
* default-dataset-item event on the default dataset when it is priced, and
|
|
129
141
|
* how many rows the remaining budget pays for in full.
|
|
130
142
|
*/
|
|
131
|
-
_rowCharges(charges: Record<string, number>, isDefaultDataset: boolean, rowCount: number): {
|
|
143
|
+
_rowCharges(charges: Record<string, number>, isDefaultDataset: boolean, rowCount: number, batch?: boolean): {
|
|
132
144
|
perRow: Record<string, number>;
|
|
133
145
|
rows: number;
|
|
134
146
|
};
|
|
@@ -284,12 +296,14 @@ export declare function validateRowCharges(charges: unknown): Record<string, num
|
|
|
284
296
|
* the rows that are kept, so no row is stored part-paid and no event is billed
|
|
285
297
|
* for a row that was not stored. Unpriced events are ignored.
|
|
286
298
|
*/
|
|
287
|
-
export declare function pushDataWithCharges<T>({ chargingManager, items, charges, isDefaultDataset, pushFn, }: {
|
|
299
|
+
export declare function pushDataWithCharges<T>({ chargingManager, items, charges, isDefaultDataset, pushFn, chargePer, }: {
|
|
288
300
|
chargingManager: ChargingManager;
|
|
289
301
|
items: T | T[];
|
|
290
302
|
charges: Record<string, number>;
|
|
291
303
|
isDefaultDataset: boolean;
|
|
292
304
|
pushFn: (rows: T | T[]) => Promise<void>;
|
|
305
|
+
/** 'batch': charge `charges` once for the whole push; all rows stored if accepted, none if refused. */
|
|
306
|
+
chargePer?: 'row' | 'batch';
|
|
293
307
|
}): Promise<ChargeResult>;
|
|
294
308
|
/**
|
|
295
309
|
* @internal True when a dataset push certainly stored nothing: the storage API
|
package/dist/charging.js
CHANGED
|
@@ -172,6 +172,8 @@ class ChargingManager {
|
|
|
172
172
|
*/
|
|
173
173
|
platformOtherUsd = 0;
|
|
174
174
|
lastRemainingBudgetNd = null;
|
|
175
|
+
/** True once the platform reported a remaining budget (its usage is then in the local budget). */
|
|
176
|
+
platformBudgetSeen = false;
|
|
175
177
|
/** maxTotalChargeUsd minus this SDK's charges and the platform's other run spend. */
|
|
176
178
|
remainingBudgetUsd() {
|
|
177
179
|
return this.maxTotalChargeUsd - this.calculateTotalChargedAmount() - this.platformOtherUsd;
|
|
@@ -182,10 +184,28 @@ class ChargingManager {
|
|
|
182
184
|
this.lastRemainingBudgetNd = null;
|
|
183
185
|
if (nd === null || nd < 0 || !Number.isFinite(this.maxTotalChargeUsd))
|
|
184
186
|
return;
|
|
187
|
+
this.platformBudgetSeen = true;
|
|
185
188
|
const other = Number((this.maxTotalChargeUsd - nd / 1e9 - this.calculateTotalChargedAmount()).toFixed(9));
|
|
186
189
|
if (other > this.platformOtherUsd)
|
|
187
190
|
this.platformOtherUsd = other;
|
|
188
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
|
+
}
|
|
189
209
|
/** @internal Platform spend outside this SDK's charges (tests). */
|
|
190
210
|
_platformOtherUsd() {
|
|
191
211
|
return this.platformOtherUsd;
|
|
@@ -266,7 +286,7 @@ class ChargingManager {
|
|
|
266
286
|
* default-dataset-item event on the default dataset when it is priced, and
|
|
267
287
|
* how many rows the remaining budget pays for in full.
|
|
268
288
|
*/
|
|
269
|
-
_rowCharges(charges, isDefaultDataset, rowCount) {
|
|
289
|
+
_rowCharges(charges, isDefaultDataset, rowCount, batch = false) {
|
|
270
290
|
const perRow = {};
|
|
271
291
|
for (const [name, n] of Object.entries(charges)) {
|
|
272
292
|
if ((this.calculateEventPrice(name) ?? 0) > 0)
|
|
@@ -274,11 +294,13 @@ class ChargingManager {
|
|
|
274
294
|
}
|
|
275
295
|
if (isDefaultDataset) {
|
|
276
296
|
const def = resolveDefaultDatasetItemEventName(this.pricingInfo);
|
|
297
|
+
// Per stored row, also for a batch.
|
|
277
298
|
if (!(def in perRow) && (this.calculateEventPrice(def) ?? 0) > 0)
|
|
278
|
-
perRow[def] = 1;
|
|
299
|
+
perRow[def] = batch ? rowCount : 1;
|
|
279
300
|
}
|
|
280
301
|
const rowCost = Object.entries(perRow).reduce((a, [name, n]) => a + (this.calculateEventPrice(name) ?? 0) * n, 0);
|
|
281
|
-
|
|
302
|
+
// Units charged for: rows, or the one batch (rowCost is then the batch's cost).
|
|
303
|
+
let rows = batch ? 1 : rowCount;
|
|
282
304
|
if (rowCost > 0 && Number.isFinite(this.maxTotalChargeUsd)) {
|
|
283
305
|
const fit = Math.floor(Number((this.remainingBudgetUsd() / rowCost).toFixed(4)));
|
|
284
306
|
rows = Math.min(rows, Math.max(0, fit));
|
|
@@ -1317,8 +1339,11 @@ function validateRowCharges(charges) {
|
|
|
1317
1339
|
* the rows that are kept, so no row is stored part-paid and no event is billed
|
|
1318
1340
|
* for a row that was not stored. Unpriced events are ignored.
|
|
1319
1341
|
*/
|
|
1320
|
-
async function pushDataWithCharges({ chargingManager, items, charges, isDefaultDataset, pushFn, }) {
|
|
1342
|
+
async function pushDataWithCharges({ chargingManager, items, charges, isDefaultDataset, pushFn, chargePer = 'row', }) {
|
|
1321
1343
|
const perRowIn = validateRowCharges(charges);
|
|
1344
|
+
if (chargePer !== 'row' && chargePer !== 'batch')
|
|
1345
|
+
throw new Error("chargePer must be 'row' or 'batch'");
|
|
1346
|
+
const batch = chargePer === 'batch';
|
|
1322
1347
|
const rowsIn = Array.isArray(items) ? items : [items];
|
|
1323
1348
|
const single = !Array.isArray(items);
|
|
1324
1349
|
const store = (rows) => chargeGatedPush.run(true, () => pushFn(single ? rows[0] : rows));
|
|
@@ -1333,7 +1358,7 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
|
|
|
1333
1358
|
await store(rowsIn);
|
|
1334
1359
|
return { eventChargeLimitReached: false, chargedCount: 0, chargeableWithinLimit: {} };
|
|
1335
1360
|
}
|
|
1336
|
-
let { perRow, rows } = chargingManager._rowCharges(perRowIn, isDefaultDataset, rowsIn.length);
|
|
1361
|
+
let { perRow, rows } = chargingManager._rowCharges(perRowIn, isDefaultDataset, rowsIn.length, batch);
|
|
1337
1362
|
if (Object.keys(perRow).length === 0) {
|
|
1338
1363
|
// Only free events: an unpaid push (prepaid-row rules apply).
|
|
1339
1364
|
const pastLimit = chargingManager.isChargeLimitReached();
|
|
@@ -1361,7 +1386,7 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
|
|
|
1361
1386
|
};
|
|
1362
1387
|
const order = Object.entries(perRow).sort(([a], [b]) => Number(isSyntheticEvent(a)) - Number(isSyntheticEvent(b)) || a.localeCompare(b));
|
|
1363
1388
|
let ops = [];
|
|
1364
|
-
for (let attempt = 0; attempt <
|
|
1389
|
+
for (let attempt = 0; attempt < 6 && rows > 0; attempt++) {
|
|
1365
1390
|
ops = [];
|
|
1366
1391
|
let shortTo = null;
|
|
1367
1392
|
for (const [name, n] of order) {
|
|
@@ -1389,10 +1414,14 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
|
|
|
1389
1414
|
}
|
|
1390
1415
|
if (shortTo === null)
|
|
1391
1416
|
break;
|
|
1392
|
-
// An event came back short: void this attempt and retry with the
|
|
1417
|
+
// An event came back short: void this attempt and retry with the whole
|
|
1418
|
+
// rows the budget covers at the FULL per-row cost (sizing from the short
|
|
1419
|
+
// event alone came back short again: appstore 64df0a3608d74af88, 0 saved).
|
|
1420
|
+
// At least one single-row try: the last affordable row may still fit.
|
|
1421
|
+
const attemptOps = ops.map((op) => ({ name: op.name, charged: op.charged }));
|
|
1393
1422
|
await undo(ops);
|
|
1394
1423
|
ops = [];
|
|
1395
|
-
rows =
|
|
1424
|
+
rows = Math.min(rows - 1, Math.max(chargingManager._rowsAfterShort(attemptOps, perRow), 1));
|
|
1396
1425
|
}
|
|
1397
1426
|
if (rows <= 0 || ops.length === 0) {
|
|
1398
1427
|
await undo(ops);
|
|
@@ -1401,23 +1430,24 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
|
|
|
1401
1430
|
chargingManager._noteRefusedDefaultDatasetItems(rowsIn.length);
|
|
1402
1431
|
return { eventChargeLimitReached: true, chargedCount: 0, chargeableWithinLimit: chargingManager._chargeableWithinLimit() };
|
|
1403
1432
|
}
|
|
1404
|
-
|
|
1433
|
+
const kept = batch ? rowsIn : rowsIn.slice(0, rows);
|
|
1434
|
+
if (kept.length < rowsIn.length) {
|
|
1405
1435
|
chargingManager._markChargeLimitHit();
|
|
1406
1436
|
if (isDefaultDataset)
|
|
1407
|
-
chargingManager._noteRefusedDefaultDatasetItems(rowsIn.length -
|
|
1437
|
+
chargingManager._noteRefusedDefaultDatasetItems(rowsIn.length - kept.length);
|
|
1408
1438
|
}
|
|
1409
1439
|
try {
|
|
1410
|
-
await store(
|
|
1440
|
+
await store(kept);
|
|
1411
1441
|
}
|
|
1412
1442
|
catch (pushErr) {
|
|
1413
|
-
console.error(`[Scrapely] Dataset push failed after charges accepted (rows=${
|
|
1443
|
+
console.error(`[Scrapely] Dataset push failed after charges accepted (rows=${kept.length}): ${pushErr.message}; voiding the charges`);
|
|
1414
1444
|
if (pushCertainlyFailed(pushErr))
|
|
1415
1445
|
await undo(ops);
|
|
1416
1446
|
throw pushErr;
|
|
1417
1447
|
}
|
|
1418
1448
|
return {
|
|
1419
|
-
eventChargeLimitReached:
|
|
1420
|
-
chargedCount:
|
|
1449
|
+
eventChargeLimitReached: kept.length < rowsIn.length || chargingManager.isChargeLimitReached(),
|
|
1450
|
+
chargedCount: kept.length,
|
|
1421
1451
|
chargeableWithinLimit: chargingManager._chargeableWithinLimit(),
|
|
1422
1452
|
};
|
|
1423
1453
|
});
|