scrapely 1.2.24 → 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 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
  });
@@ -140,7 +140,7 @@ export declare class ChargingManager {
140
140
  * default-dataset-item event on the default dataset when it is priced, and
141
141
  * how many rows the remaining budget pays for in full.
142
142
  */
143
- _rowCharges(charges: Record<string, number>, isDefaultDataset: boolean, rowCount: number): {
143
+ _rowCharges(charges: Record<string, number>, isDefaultDataset: boolean, rowCount: number, batch?: boolean): {
144
144
  perRow: Record<string, number>;
145
145
  rows: number;
146
146
  };
@@ -296,12 +296,14 @@ export declare function validateRowCharges(charges: unknown): Record<string, num
296
296
  * the rows that are kept, so no row is stored part-paid and no event is billed
297
297
  * for a row that was not stored. Unpriced events are ignored.
298
298
  */
299
- export declare function pushDataWithCharges<T>({ chargingManager, items, charges, isDefaultDataset, pushFn, }: {
299
+ export declare function pushDataWithCharges<T>({ chargingManager, items, charges, isDefaultDataset, pushFn, chargePer, }: {
300
300
  chargingManager: ChargingManager;
301
301
  items: T | T[];
302
302
  charges: Record<string, number>;
303
303
  isDefaultDataset: boolean;
304
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';
305
307
  }): Promise<ChargeResult>;
306
308
  /**
307
309
  * @internal True when a dataset push certainly stored nothing: the storage API
package/dist/charging.js CHANGED
@@ -286,7 +286,7 @@ class ChargingManager {
286
286
  * default-dataset-item event on the default dataset when it is priced, and
287
287
  * how many rows the remaining budget pays for in full.
288
288
  */
289
- _rowCharges(charges, isDefaultDataset, rowCount) {
289
+ _rowCharges(charges, isDefaultDataset, rowCount, batch = false) {
290
290
  const perRow = {};
291
291
  for (const [name, n] of Object.entries(charges)) {
292
292
  if ((this.calculateEventPrice(name) ?? 0) > 0)
@@ -294,11 +294,13 @@ class ChargingManager {
294
294
  }
295
295
  if (isDefaultDataset) {
296
296
  const def = resolveDefaultDatasetItemEventName(this.pricingInfo);
297
+ // Per stored row, also for a batch.
297
298
  if (!(def in perRow) && (this.calculateEventPrice(def) ?? 0) > 0)
298
- perRow[def] = 1;
299
+ perRow[def] = batch ? rowCount : 1;
299
300
  }
300
301
  const rowCost = Object.entries(perRow).reduce((a, [name, n]) => a + (this.calculateEventPrice(name) ?? 0) * n, 0);
301
- let rows = rowCount;
302
+ // Units charged for: rows, or the one batch (rowCost is then the batch's cost).
303
+ let rows = batch ? 1 : rowCount;
302
304
  if (rowCost > 0 && Number.isFinite(this.maxTotalChargeUsd)) {
303
305
  const fit = Math.floor(Number((this.remainingBudgetUsd() / rowCost).toFixed(4)));
304
306
  rows = Math.min(rows, Math.max(0, fit));
@@ -1337,8 +1339,11 @@ function validateRowCharges(charges) {
1337
1339
  * the rows that are kept, so no row is stored part-paid and no event is billed
1338
1340
  * for a row that was not stored. Unpriced events are ignored.
1339
1341
  */
1340
- async function pushDataWithCharges({ chargingManager, items, charges, isDefaultDataset, pushFn, }) {
1342
+ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultDataset, pushFn, chargePer = 'row', }) {
1341
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';
1342
1347
  const rowsIn = Array.isArray(items) ? items : [items];
1343
1348
  const single = !Array.isArray(items);
1344
1349
  const store = (rows) => chargeGatedPush.run(true, () => pushFn(single ? rows[0] : rows));
@@ -1353,7 +1358,7 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
1353
1358
  await store(rowsIn);
1354
1359
  return { eventChargeLimitReached: false, chargedCount: 0, chargeableWithinLimit: {} };
1355
1360
  }
1356
- let { perRow, rows } = chargingManager._rowCharges(perRowIn, isDefaultDataset, rowsIn.length);
1361
+ let { perRow, rows } = chargingManager._rowCharges(perRowIn, isDefaultDataset, rowsIn.length, batch);
1357
1362
  if (Object.keys(perRow).length === 0) {
1358
1363
  // Only free events: an unpaid push (prepaid-row rules apply).
1359
1364
  const pastLimit = chargingManager.isChargeLimitReached();
@@ -1425,23 +1430,24 @@ async function pushDataWithCharges({ chargingManager, items, charges, isDefaultD
1425
1430
  chargingManager._noteRefusedDefaultDatasetItems(rowsIn.length);
1426
1431
  return { eventChargeLimitReached: true, chargedCount: 0, chargeableWithinLimit: chargingManager._chargeableWithinLimit() };
1427
1432
  }
1428
- if (rows < rowsIn.length) {
1433
+ const kept = batch ? rowsIn : rowsIn.slice(0, rows);
1434
+ if (kept.length < rowsIn.length) {
1429
1435
  chargingManager._markChargeLimitHit();
1430
1436
  if (isDefaultDataset)
1431
- chargingManager._noteRefusedDefaultDatasetItems(rowsIn.length - rows);
1437
+ chargingManager._noteRefusedDefaultDatasetItems(rowsIn.length - kept.length);
1432
1438
  }
1433
1439
  try {
1434
- await store(rowsIn.slice(0, rows));
1440
+ await store(kept);
1435
1441
  }
1436
1442
  catch (pushErr) {
1437
- console.error(`[Scrapely] Dataset push failed after charges accepted (rows=${rows}): ${pushErr.message}; voiding the charges`);
1443
+ console.error(`[Scrapely] Dataset push failed after charges accepted (rows=${kept.length}): ${pushErr.message}; voiding the charges`);
1438
1444
  if (pushCertainlyFailed(pushErr))
1439
1445
  await undo(ops);
1440
1446
  throw pushErr;
1441
1447
  }
1442
1448
  return {
1443
- eventChargeLimitReached: rows < rowsIn.length || chargingManager.isChargeLimitReached(),
1444
- chargedCount: rows,
1449
+ eventChargeLimitReached: kept.length < rowsIn.length || chargingManager.isChargeLimitReached(),
1450
+ chargedCount: kept.length,
1445
1451
  chargeableWithinLimit: chargingManager._chargeableWithinLimit(),
1446
1452
  };
1447
1453
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapely",
3
- "version": "1.2.24",
3
+ "version": "1.2.25",
4
4
  "description": "Scrapely SDK for building JavaScript and TypeScript Actors",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",