-
Notifications
You must be signed in to change notification settings - Fork 1
refactor(gteam-internal): delegate safePushData to pushDataWithSchemaRepair #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,39 @@ | ||
| import { NonRetryableError } from '@crawlee/core'; | ||
| import type { ChargeResult } from 'apify'; | ||
| import { Actor, log } from 'apify'; | ||
| import { ApifyApiError } from 'apify-client'; | ||
|
|
||
| import { pushDataWithSchemaRepair } from '../push-data-with-schema-repair/index.js'; | ||
|
|
||
| // Actor.pushData(items, eventName)'s resolved value when every item got | ||
| // dropped and the push itself never ran — mirrors what a charge-less push | ||
| // would report, so callers checking `chargeableWithinLimit` don't crash on | ||
| // a missing result. | ||
| const NO_CHARGE_RESULT: ChargeResult = { | ||
| eventChargeLimitReached: false, | ||
| chargedCount: 0, | ||
| chargeableWithinLimit: {}, | ||
| }; | ||
|
|
||
| /** | ||
| * Wraps a push call, converting a dataset schema-validation failure into a | ||
| * {@link NonRetryableError} instead of a retryable one — a schema mismatch | ||
| * will never fix itself on retry, so retrying just burns compute. | ||
| * Wraps a push call, repairing or dropping items that fail dataset | ||
| * schema-validation instead of letting the whole batch fail. Every dropped | ||
| * item is logged, so existing log-based monitoring keeps working. | ||
| */ | ||
| async function wrapPushData<R>(pushDataFn: () => Promise<R>): Promise<R> { | ||
| try { | ||
| return await pushDataFn(); | ||
| } catch (error) { | ||
| if (!(error instanceof ApifyApiError) || !Array.isArray(error.data?.invalidItems)) { | ||
| throw error; | ||
| } | ||
| const msg = 'Dataset validation failed'; | ||
| for (const { validationErrors } of error.data.invalidItems) { | ||
| log.error(msg, { msg, error: `${error}`, validationErrors }); | ||
| } | ||
| throw new NonRetryableError(msg); | ||
| async function wrapPushData<T extends object, R>( | ||
| data: T | T[], | ||
| pushFn: (items: T[]) => Promise<R>, | ||
| ): Promise<R | undefined> { | ||
| const msg = 'Dataset validation failed'; | ||
| const { droppedItems, pushResult } = await pushDataWithSchemaRepair(pushFn, data); | ||
| for (const { errors } of droppedItems) { | ||
| log.error(msg, { msg, validationErrors: errors }); | ||
| } | ||
| return pushResult; | ||
| } | ||
|
|
||
| /** | ||
| * Pushes to the default dataset, or a named dataset when `alias` is given, | ||
| * converting a schema-validation failure into a {@link NonRetryableError} | ||
| * instead of leaving it to be retried. | ||
| * Pushes to the default dataset, or a named dataset when `alias` is given. | ||
| * Items that fail dataset schema-validation are repaired where possible and | ||
| * dropped (with a logged error) otherwise, instead of failing the whole push. | ||
| * | ||
| * @param data - A single item or array of items to push. | ||
| * @param options.alias - Alias of the dataset to push to, opened via {@link Actor.openDataset}. | ||
|
|
@@ -45,9 +52,9 @@ export async function safePushData<T extends object>( | |
| ): Promise<void>; | ||
| /** | ||
| * Pushes to the default dataset and atomically charges for `eventName`, via | ||
| * {@link Actor.pushData}'s built-in pay-per-event support. Converts a | ||
| * schema-validation failure into a {@link NonRetryableError} instead of | ||
| * leaving it to be retried. | ||
| * {@link Actor.pushData}'s built-in pay-per-event support. Items that fail | ||
| * dataset schema-validation are repaired where possible and dropped (with a | ||
| * logged error) otherwise, instead of failing the whole push. | ||
| * | ||
| * @param data - A single item or array of items to push. | ||
| * @param options.eventName - Pay-per-event event name to charge for this push. | ||
|
|
@@ -68,18 +75,17 @@ export async function safePushData<T extends object>( | |
| ): Promise<ChargeResult | void> { | ||
| const { alias, eventName } = options ?? {}; | ||
|
|
||
| // Default dataset: Actor.pushData(data, eventName) already pushes and | ||
| // Default dataset: Actor.pushData(items, eventName) already pushes and | ||
| // charges atomically, so delegate to it as-is instead of reimplementing charging. | ||
| if (!alias) { | ||
| if (eventName) { | ||
| return wrapPushData(async () => Actor.pushData(data, eventName)); | ||
| const pushResult = await wrapPushData(data, async (items) => Actor.pushData(items, eventName)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should have this as a feature of the main thing actually 👀
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we never successfully push anything, we don't get back any pushResult to return to the caller. Then it is really up to the team how they want to manage it, we should give empty charge result out of nowhere Or did you mean something else? |
||
| return pushResult ?? NO_CHARGE_RESULT; | ||
| } | ||
| return wrapPushData(async () => Actor.pushData(data)); | ||
| return wrapPushData(data, async (items) => Actor.pushData(items)); | ||
| } | ||
|
|
||
| // Named dataset: charging is handled by the caller — call Actor.charge() themselves after this push. | ||
| const dataset = await Actor.openDataset<T>({ alias }); | ||
| return wrapPushData(async () => { | ||
| await dataset.pushData(data); | ||
| }); | ||
| return wrapPushData(data, async (items) => dataset.pushData(items)); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pushDataWithSchemaRepairalready logs stuff btw 👀There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We thought about adding our own logs on top of the ones that are already present to keep our existing monitoring flow