scrapely 1.2.9 → 1.2.10
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 +10 -1
- package/dist/charging.js +80 -10
- package/package.json +1 -1
package/dist/charging.d.ts
CHANGED
|
@@ -122,6 +122,8 @@ export declare class ChargingManager {
|
|
|
122
122
|
private getEventPricing;
|
|
123
123
|
private normalizeChargingEventName;
|
|
124
124
|
private calculateMaxChargesByPrice;
|
|
125
|
+
/** @internal Remaining allowance per event (pushDataAndCharge's uncertain path). */
|
|
126
|
+
_chargeableWithinLimit(): Record<string, number>;
|
|
125
127
|
private getChargeableWithinLimit;
|
|
126
128
|
private quarantineStalePending;
|
|
127
129
|
/**
|
|
@@ -137,7 +139,14 @@ export declare class ChargingManager {
|
|
|
137
139
|
* (POST /v2/actor-runs/:runId/charge/void), then unwind the local
|
|
138
140
|
* reservation. Returns true when the platform voided it.
|
|
139
141
|
*/
|
|
140
|
-
|
|
142
|
+
/**
|
|
143
|
+
* @internal Cancel a charge whose outcome was never confirmed, for items that
|
|
144
|
+
* were then not stored: drop the pending intent (so the exit flush never
|
|
145
|
+
* posts it) and void it in case the original request did apply. A 404 here
|
|
146
|
+
* means it never applied.
|
|
147
|
+
*/
|
|
148
|
+
_cancelUncertainCharge(eventName: string, operationId: string, reservedCount: number): Promise<boolean>;
|
|
149
|
+
_voidCharge(eventName: string, operationId: string, reservedCount: number, notFoundMeansVoided?: boolean): Promise<boolean>;
|
|
141
150
|
/**
|
|
142
151
|
* @internal Retry unconfirmed voids (caller holds the operation lock).
|
|
143
152
|
* Returns false while any earlier void is still unconfirmed.
|
package/dist/charging.js
CHANGED
|
@@ -345,6 +345,10 @@ class ChargingManager {
|
|
|
345
345
|
const roundedResult = Math.floor(Number(unroundedResult.toFixed(4)));
|
|
346
346
|
return Math.max(0, roundedResult);
|
|
347
347
|
}
|
|
348
|
+
/** @internal Remaining allowance per event (pushDataAndCharge's uncertain path). */
|
|
349
|
+
_chargeableWithinLimit() {
|
|
350
|
+
return this.chargingState === undefined ? {} : this.getChargeableWithinLimit();
|
|
351
|
+
}
|
|
348
352
|
getChargeableWithinLimit() {
|
|
349
353
|
if (Object.keys(this.pricingInfo).length === 0) {
|
|
350
354
|
return {};
|
|
@@ -414,7 +418,17 @@ class ChargingManager {
|
|
|
414
418
|
* (POST /v2/actor-runs/:runId/charge/void), then unwind the local
|
|
415
419
|
* reservation. Returns true when the platform voided it.
|
|
416
420
|
*/
|
|
417
|
-
|
|
421
|
+
/**
|
|
422
|
+
* @internal Cancel a charge whose outcome was never confirmed, for items that
|
|
423
|
+
* were then not stored: drop the pending intent (so the exit flush never
|
|
424
|
+
* posts it) and void it in case the original request did apply. A 404 here
|
|
425
|
+
* means it never applied.
|
|
426
|
+
*/
|
|
427
|
+
async _cancelUncertainCharge(eventName, operationId, reservedCount) {
|
|
428
|
+
this.pendingCharges.delete(operationId);
|
|
429
|
+
return this._voidCharge(eventName, operationId, reservedCount, true);
|
|
430
|
+
}
|
|
431
|
+
async _voidCharge(eventName, operationId, reservedCount, notFoundMeansVoided = false) {
|
|
418
432
|
if (!this.isAtHome || !this.actorRunId)
|
|
419
433
|
return false;
|
|
420
434
|
const runClient = this.apifyClient.run(this.actorRunId);
|
|
@@ -431,6 +445,13 @@ class ChargingManager {
|
|
|
431
445
|
}
|
|
432
446
|
catch (err) {
|
|
433
447
|
const status = httpStatusOf(err);
|
|
448
|
+
// Only the void route's own answer proves it never applied; a server
|
|
449
|
+
// without the route (older deployment) also answers 404.
|
|
450
|
+
if (status === 404 && notFoundMeansVoided && apiErrorTypeOf(err) === 'charge-not-found') {
|
|
451
|
+
// The unconfirmed charge never reached the ledger: nothing billed.
|
|
452
|
+
this.applyAcceptedCount(eventName, reservedCount, 0);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
434
455
|
// 404: nothing to void; 409: already settled; 4xx auth: permanent.
|
|
435
456
|
// The charge stands; keep the local reservation that matches it.
|
|
436
457
|
if (status !== undefined && status >= 400 && status < 500)
|
|
@@ -440,7 +461,7 @@ class ChargingManager {
|
|
|
440
461
|
}
|
|
441
462
|
}
|
|
442
463
|
// Outcome unknown: keep the reservation and retry before the next push / at exit.
|
|
443
|
-
this.pendingVoids.push({ eventName, operationId, reservedCount });
|
|
464
|
+
this.pendingVoids.push({ eventName, operationId, reservedCount, notFoundMeansVoided });
|
|
444
465
|
console.error(`[Scrapely] Void of charge ${operationId} ('${eventName}') not confirmed yet; will retry`);
|
|
445
466
|
return false;
|
|
446
467
|
}
|
|
@@ -452,7 +473,7 @@ class ChargingManager {
|
|
|
452
473
|
const pending = this.pendingVoids;
|
|
453
474
|
this.pendingVoids = [];
|
|
454
475
|
for (const v of pending) {
|
|
455
|
-
await this._voidCharge(v.eventName, v.operationId, v.reservedCount);
|
|
476
|
+
await this._voidCharge(v.eventName, v.operationId, v.reservedCount, v.notFoundMeansVoided);
|
|
456
477
|
}
|
|
457
478
|
return this.pendingVoids.length === 0;
|
|
458
479
|
}
|
|
@@ -784,10 +805,30 @@ async function pushDataAndCharge({ chargingManager, items, eventName, isDefaultD
|
|
|
784
805
|
try {
|
|
785
806
|
for (const [name, chargeCount] of ordered) {
|
|
786
807
|
chargingManager._takeLastAcceptedOperationId?.();
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
808
|
+
let r;
|
|
809
|
+
try {
|
|
810
|
+
r = await chargingManager._chargeExclusive({
|
|
811
|
+
eventName: name,
|
|
812
|
+
count: chargeCount,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
catch (chargeErr) {
|
|
816
|
+
if (!(chargeErr instanceof ChargeUncertainError))
|
|
817
|
+
throw chargeErr;
|
|
818
|
+
// The platform did not confirm the charge (5xx / timeouts after
|
|
819
|
+
// retries). Never drop the items for that: the intent stays
|
|
820
|
+
// pending with its idempotency key and is re-posted by the exit
|
|
821
|
+
// flush, so the items are stored now and billed once.
|
|
822
|
+
console.warn(`[Scrapely] Charge for '${name}' not confirmed yet (${chargeErr.operationId}); storing the items and retrying the charge later`);
|
|
823
|
+
r = {
|
|
824
|
+
eventChargeLimitReached: false,
|
|
825
|
+
chargedCount: chargeErr.count,
|
|
826
|
+
chargeableWithinLimit: chargingManager._chargeableWithinLimit?.() ?? {},
|
|
827
|
+
};
|
|
828
|
+
results.push(r);
|
|
829
|
+
reservedInOp.push({ name, reserved: chargeErr.count, operationId: chargeErr.operationId, uncertain: true });
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
791
832
|
results.push(r);
|
|
792
833
|
reservedInOp.push({ name, reserved: r.chargedCount, operationId: chargingManager._takeLastAcceptedOperationId?.() });
|
|
793
834
|
}
|
|
@@ -814,7 +855,12 @@ async function pushDataAndCharge({ chargingManager, items, eventName, isDefaultD
|
|
|
814
855
|
const [name] = ordered[i];
|
|
815
856
|
const reserved = results[i].chargedCount;
|
|
816
857
|
if (reserved > acceptedForItems) {
|
|
817
|
-
|
|
858
|
+
// Nothing stored and a platform charge was made: leave its local
|
|
859
|
+
// count to the void below, which releases it only once the
|
|
860
|
+
// platform confirms (never on a server without the void route).
|
|
861
|
+
const platformOp = acceptedForItems <= 0 && reservedInOp[i]?.operationId;
|
|
862
|
+
if (!platformOp)
|
|
863
|
+
chargingManager.applyAcceptedCount(name, reserved, acceptedForItems);
|
|
818
864
|
results[i] = {
|
|
819
865
|
...results[i],
|
|
820
866
|
chargedCount: acceptedForItems,
|
|
@@ -824,6 +870,20 @@ async function pushDataAndCharge({ chargingManager, items, eventName, isDefaultD
|
|
|
824
870
|
}
|
|
825
871
|
chargeResult = results.reduce(mergeChargeResults);
|
|
826
872
|
if (acceptedForItems <= 0) {
|
|
873
|
+
// Nothing will be stored (another event of this push was capped to
|
|
874
|
+
// zero): undo every platform charge this operation made, so no
|
|
875
|
+
// result is billed for items that are never saved.
|
|
876
|
+
for (let i = 0; i < reservedInOp.length; i++) {
|
|
877
|
+
const { name, operationId, uncertain, reserved } = reservedInOp[i];
|
|
878
|
+
if (reserved <= 0 || !operationId)
|
|
879
|
+
continue;
|
|
880
|
+
// Original count: the local reservation is released only when
|
|
881
|
+
// the platform confirms the void (or that it never applied).
|
|
882
|
+
if (uncertain)
|
|
883
|
+
await chargingManager._cancelUncertainCharge?.(name, operationId, reserved);
|
|
884
|
+
else
|
|
885
|
+
await chargingManager._voidCharge(name, operationId, reserved);
|
|
886
|
+
}
|
|
827
887
|
return {
|
|
828
888
|
eventChargeLimitReached: true,
|
|
829
889
|
chargedCount: chargeResult.chargedCount,
|
|
@@ -848,11 +908,15 @@ async function pushDataAndCharge({ chargingManager, items, eventName, isDefaultD
|
|
|
848
908
|
if (!pushCertainlyFailed(pushErr))
|
|
849
909
|
throw pushErr;
|
|
850
910
|
for (let i = 0; i < reservedInOp.length; i++) {
|
|
851
|
-
const { name, operationId } = reservedInOp[i];
|
|
911
|
+
const { name, operationId, uncertain } = reservedInOp[i];
|
|
852
912
|
const reserved = results[i].chargedCount;
|
|
853
913
|
if (reserved <= 0)
|
|
854
914
|
continue;
|
|
855
|
-
if (operationId) {
|
|
915
|
+
if (operationId && uncertain) {
|
|
916
|
+
// Never re-post a charge for items that were not stored.
|
|
917
|
+
await chargingManager._cancelUncertainCharge?.(name, operationId, reserved);
|
|
918
|
+
}
|
|
919
|
+
else if (operationId) {
|
|
856
920
|
await chargingManager._voidCharge(name, operationId, reserved);
|
|
857
921
|
}
|
|
858
922
|
else if (isSyntheticEvent(name)) {
|
|
@@ -895,3 +959,9 @@ function pushCertainlyFailed(err) {
|
|
|
895
959
|
}
|
|
896
960
|
return false;
|
|
897
961
|
}
|
|
962
|
+
/** API error type (e.g. 'charge-not-found') of a client error, if the server sent one. */
|
|
963
|
+
function apiErrorTypeOf(err) {
|
|
964
|
+
const e = err;
|
|
965
|
+
const t = e?.type ?? e?.data?.error?.type ?? e?.response?.data?.error?.type;
|
|
966
|
+
return typeof t === 'string' ? t : undefined;
|
|
967
|
+
}
|