@apify/actor-utils 0.1.3-beta.0 → 0.1.3-beta.2
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/README.md +62 -20
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/{safePushData.d.ts → pushDataWithSchemaRepair.d.ts} +18 -8
- package/dist/src/pushDataWithSchemaRepair.d.ts.map +1 -0
- package/dist/src/pushDataWithSchemaRepair.js +448 -0
- package/dist/src/pushDataWithSchemaRepair.js.map +1 -0
- package/package.json +1 -1
- package/dist/src/safePushData.d.ts.map +0 -1
- package/dist/src/safePushData.js +0 -285
- package/dist/src/safePushData.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pushDataWithSchemaRepair
|
|
2
2
|
|
|
3
3
|
TypeScript library wrapper that survives Apify dataset schema-validation
|
|
4
4
|
failures. When an upstream data source produces an item that doesn't match
|
|
5
5
|
the dataset's JSON schema, the platform rejects the **entire batch** with
|
|
6
|
-
a 400 error — losing every other valid item in that push.
|
|
7
|
-
parses the validation error, strips the
|
|
6
|
+
a 400 error — losing every other valid item in that push.
|
|
7
|
+
`pushDataWithSchemaRepair` parses the validation error, strips the
|
|
8
|
+
offending fields, and retries.
|
|
8
9
|
|
|
9
10
|
## Usage
|
|
10
11
|
|
|
11
12
|
```ts
|
|
12
13
|
import { Actor } from 'apify';
|
|
13
|
-
import {
|
|
14
|
+
import { pushDataWithSchemaRepair } from 'apify-actor-utils';
|
|
14
15
|
|
|
15
16
|
await Actor.init();
|
|
16
17
|
|
|
17
|
-
const result = await
|
|
18
|
+
const result = await pushDataWithSchemaRepair((batch) => Actor.pushData(batch), items);
|
|
18
19
|
|
|
19
20
|
console.log(result);
|
|
20
|
-
// {
|
|
21
|
+
// { pushedCount: 2, droppedItems: [...], attemptCount: 2, pushResult: undefined }
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
Accepts a single item or an array. `pushFn` is the first positional arg
|
|
24
25
|
and is required — the library itself never imports the Apify SDK, and a
|
|
25
26
|
CI check forbids `.pushData(` from appearing anywhere in the source.
|
|
26
27
|
|
|
28
|
+
Whatever `pushFn` resolves to comes back as `pushResult`, so a push
|
|
29
|
+
function with a meaningful return value stays usable:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const { pushResult } = await pushDataWithSchemaRepair((batch) => client.dataset(id).pushItems(batch), items);
|
|
33
|
+
```
|
|
34
|
+
|
|
27
35
|
## Performance notes
|
|
28
36
|
|
|
29
37
|
The happy path is a single `try/await pushFn(items)` with **no extra
|
|
@@ -38,11 +46,12 @@ every AJV error per item:
|
|
|
38
46
|
|
|
39
47
|
| Error | Action |
|
|
40
48
|
| ------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
41
|
-
| `required` at
|
|
42
|
-
| `additionalProperties`
|
|
49
|
+
| `required` (at any depth) | Set the missing field to `null` and mark the path as a placeholder. |
|
|
50
|
+
| `additionalProperties` (any depth) | Delete the unknown property. |
|
|
43
51
|
| `type` / `format` / etc. at the root | Item itself is the wrong shape → dropped. |
|
|
44
52
|
| Constraint on a **placeholder** path | Replace with a type-aware default (see below). If no default is known → dropped. |
|
|
45
53
|
| Constraint on **user-supplied** data | Delete the field. If the schema later marks it required, a placeholder takes over. |
|
|
54
|
+
| Nothing on the item was actionable | Item is dropped — an unchanged item would fail identically on the next push. |
|
|
46
55
|
|
|
47
56
|
### Placeholder defaults
|
|
48
57
|
|
|
@@ -73,19 +82,52 @@ The retry loop chases one layer of errors per round
|
|
|
73
82
|
(`required` → `type` → push) until either the push succeeds
|
|
74
83
|
or `maxAttempts` (default 5) is hit.
|
|
75
84
|
|
|
85
|
+
### When the attempt cap is hit
|
|
86
|
+
|
|
87
|
+
Items still failing on the last allowed attempt are dropped — but the rest
|
|
88
|
+
of the batch is **not** lost with them. Because a rejected push stores
|
|
89
|
+
nothing at all, the wrapper drops the incurable items and then makes one
|
|
90
|
+
final push with the survivors, which the API already validated in the
|
|
91
|
+
previous round. That final push is counted in `attemptCount`, so a run that
|
|
92
|
+
exhausts `maxAttempts: 5` can report `attemptCount: 6`.
|
|
93
|
+
|
|
94
|
+
## Logging
|
|
95
|
+
|
|
96
|
+
Every failed round logs which fields went wrong, so you can fix the schema
|
|
97
|
+
(or the scraper) without digging through the returned `droppedItems`:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
pushDataWithSchemaRepair: schema validation failed on attempt 1: 12 invalid item(s); repaired fields: /age (type), /name (required), /tags/[] (type); dropped 2 item(s) on unfixable fields: /email (format); retrying with 10 item(s).
|
|
101
|
+
pushDataWithSchemaRepair: gave up after 5 attempts; dropped 3 item(s) still failing on fields: /sku (pattern); pushing the 9 valid item(s) left.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The field list is a **set**, not a per-item breakdown — one bad field
|
|
105
|
+
usually shows up on many items in a batch, and knowing which item had which
|
|
106
|
+
problem rarely changes what you do about it. Array indices collapse
|
|
107
|
+
(`/tags/0`, `/tags/7` → `/tags/[]`) for the same reason, and the list is
|
|
108
|
+
capped at 20 entries with the rest reported as `(+N more)`.
|
|
109
|
+
|
|
76
110
|
## Options
|
|
77
111
|
|
|
78
|
-
| Option | Type | Default | Notes
|
|
79
|
-
| ------------- | -------- | ------- |
|
|
80
|
-
| `maxAttempts` | `number` | `5` |
|
|
112
|
+
| Option | Type | Default | Notes |
|
|
113
|
+
| ------------- | -------- | ------- | -------------------------------------------------------------- |
|
|
114
|
+
| `maxAttempts` | `number` | `5` | Cap on repair rounds, plus the final salvage push if it's hit. |
|
|
81
115
|
|
|
82
116
|
## Return shape
|
|
83
117
|
|
|
118
|
+
Names say what they hold: `*Count` is a number, `*Items` is an array of
|
|
119
|
+
objects.
|
|
120
|
+
|
|
84
121
|
```ts
|
|
85
|
-
interface
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
interface PushDataWithSchemaRepairResult<T, R = unknown> {
|
|
123
|
+
/** How many of the caller's items made it into the dataset. */
|
|
124
|
+
pushedCount: number;
|
|
125
|
+
/** The items we couldn't repair, each with the errors that doomed it. */
|
|
126
|
+
droppedItems: { item: T; errors: ValidationError[] }[];
|
|
127
|
+
/** How many times `pushFn` was actually called. */
|
|
128
|
+
attemptCount: number;
|
|
129
|
+
/** What the successful `pushFn` call resolved to; absent if none did. */
|
|
130
|
+
pushResult?: R;
|
|
89
131
|
}
|
|
90
132
|
```
|
|
91
133
|
|
|
@@ -103,12 +145,12 @@ Highlights:
|
|
|
103
145
|
|
|
104
146
|
```
|
|
105
147
|
.
|
|
106
|
-
├── index.ts
|
|
107
|
-
├── src/
|
|
108
|
-
├── test/
|
|
148
|
+
├── index.ts # package entry point, re-exports src/
|
|
149
|
+
├── src/pushDataWithSchemaRepair.ts # the library
|
|
150
|
+
├── test/pushDataWithSchemaRepair.test.ts # node:test suite
|
|
109
151
|
├── scripts/
|
|
110
|
-
│ ├── check-pushdata.mjs
|
|
111
|
-
│ └── probe-errors.mjs
|
|
152
|
+
│ ├── check-pushdata.mjs # CI guard against direct .pushData() calls
|
|
153
|
+
│ └── probe-errors.mjs # reference: re-derive the API error shape
|
|
112
154
|
├── tsconfig.json
|
|
113
155
|
└── package.json
|
|
114
156
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type { ValidationError, DroppedItem,
|
|
1
|
+
export { pushDataWithSchemaRepair, isSchemaValidationError } from './src/pushDataWithSchemaRepair.js';
|
|
2
|
+
export type { ValidationError, DroppedItem, PushDataWithSchemaRepairResult, PushDataWithSchemaRepairOptions, PushFn, } from './src/pushDataWithSchemaRepair.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACtG,YAAY,EACR,eAAe,EACf,WAAW,EACX,8BAA8B,EAC9B,+BAA+B,EAC/B,MAAM,GACT,MAAM,mCAAmC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { pushDataWithSchemaRepair, isSchemaValidationError } from './src/pushDataWithSchemaRepair.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC"}
|
|
@@ -23,15 +23,23 @@ export interface DroppedItem<T> {
|
|
|
23
23
|
item: T;
|
|
24
24
|
errors: ValidationError[];
|
|
25
25
|
}
|
|
26
|
-
export interface
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
export interface PushDataWithSchemaRepairResult<T, R = unknown> {
|
|
27
|
+
/** How many of the caller's items made it into the dataset. */
|
|
28
|
+
pushedCount: number;
|
|
29
|
+
/** The items we couldn't repair, each with the errors that doomed it. */
|
|
30
|
+
droppedItems: DroppedItem<T>[];
|
|
31
|
+
/** How many times `pushFn` was actually called. */
|
|
32
|
+
attemptCount: number;
|
|
33
|
+
/**
|
|
34
|
+
* What the successful `pushFn` call resolved to (e.g. the API response).
|
|
35
|
+
* Absent when no push ever succeeded — i.e. when every item was dropped.
|
|
36
|
+
*/
|
|
37
|
+
pushResult?: R;
|
|
30
38
|
}
|
|
31
|
-
export interface
|
|
39
|
+
export interface PushDataWithSchemaRepairOptions {
|
|
32
40
|
maxAttempts?: number;
|
|
33
41
|
}
|
|
34
|
-
export type PushFn<T> = (items: T[]) => Promise<
|
|
42
|
+
export type PushFn<T, R = unknown> = (items: T[]) => Promise<R>;
|
|
35
43
|
/**
|
|
36
44
|
* Push `input` via `pushFn`, surviving Apify dataset schema-validation
|
|
37
45
|
* failures by cleaning offending items and retrying.
|
|
@@ -40,7 +48,9 @@ export type PushFn<T> = (items: T[]) => Promise<unknown>;
|
|
|
40
48
|
* direct `.pushData()` calls in this library, so the binding lives at the
|
|
41
49
|
* call site: `(b) => Actor.pushData(b)` or
|
|
42
50
|
* `(b) => client.dataset(id).pushItems(b)`.
|
|
51
|
+
*
|
|
52
|
+
* Whatever `pushFn` resolves to is handed back untouched as `pushResult`.
|
|
43
53
|
*/
|
|
44
|
-
export declare function
|
|
54
|
+
export declare function pushDataWithSchemaRepair<T, R = unknown>(pushFn: PushFn<T, R>, input: T | T[], options?: PushDataWithSchemaRepairOptions): Promise<PushDataWithSchemaRepairResult<T, R>>;
|
|
45
55
|
export {};
|
|
46
|
-
//# sourceMappingURL=
|
|
56
|
+
//# sourceMappingURL=pushDataWithSchemaRepair.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pushDataWithSchemaRepair.d.ts","sourceRoot":"","sources":["../../src/pushDataWithSchemaRepair.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,iBAAiB,4BAA4B,CAAC;AAcpD,MAAM,WAAW,eAAe;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,WAAW;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACvC;AAKD,UAAU,qBAAqB;IAC3B,IAAI,EAAE,OAAO,iBAAiB,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,YAAY,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC;CACzC;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,qBAAqB,CAMlF;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,eAAe,EAAE,CAAC;CAC7B;AAID,MAAM,WAAW,8BAA8B,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO;IAC1D,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/B,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,+BAA+B;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhE;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EACzD,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EACpB,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,EACd,OAAO,GAAE,+BAAoC,GAC9C,OAAO,CAAC,8BAA8B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAe/C"}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
// pushDataWithSchemaRepair: parse the Apify dataset schema-validation error,
|
|
2
|
+
// repair the offending items (strip bad fields, placeholder missing required
|
|
3
|
+
// ones), and retry the push.
|
|
4
|
+
//
|
|
5
|
+
// NOTE: instead of recursively healing the data one error-round at a time, we
|
|
6
|
+
// could parse the Actor's `dataset_schema.json` up front and fix every item in
|
|
7
|
+
// a single pass (we'd know each field's expected type / constraints without
|
|
8
|
+
// waiting for the API to report them). That would avoid the multi-round retry
|
|
9
|
+
// loop, but requires heavier code (locating + loading the schema, resolving
|
|
10
|
+
// $refs, walking the schema tree). Something to consider in the future.
|
|
11
|
+
const SCHEMA_ERROR_TYPE = 'schema-validation-error';
|
|
12
|
+
// Cap on how many distinct field issues we spell out in one log line. A
|
|
13
|
+
// pathological batch can fail on hundreds of fields; the overflow is
|
|
14
|
+
// summarised as a count instead of flooding the Actor log.
|
|
15
|
+
const MAX_LOGGED_FIELDS = 20;
|
|
16
|
+
// Shared empty error list. Positions that validated fine in the current round
|
|
17
|
+
// point at this instead of allocating a fresh array each time.
|
|
18
|
+
const NO_ERRORS = [];
|
|
19
|
+
export function isSchemaValidationError(err) {
|
|
20
|
+
if (!err || typeof err !== 'object')
|
|
21
|
+
return false;
|
|
22
|
+
const e = err;
|
|
23
|
+
if (e.type !== SCHEMA_ERROR_TYPE)
|
|
24
|
+
return false;
|
|
25
|
+
if (e.statusCode !== 400)
|
|
26
|
+
return false;
|
|
27
|
+
return Array.isArray(e.data?.invalidItems);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Push `input` via `pushFn`, surviving Apify dataset schema-validation
|
|
31
|
+
* failures by cleaning offending items and retrying.
|
|
32
|
+
*
|
|
33
|
+
* `pushFn` is mandatory and intentionally not bundled — a CI check forbids
|
|
34
|
+
* direct `.pushData()` calls in this library, so the binding lives at the
|
|
35
|
+
* call site: `(b) => Actor.pushData(b)` or
|
|
36
|
+
* `(b) => client.dataset(id).pushItems(b)`.
|
|
37
|
+
*
|
|
38
|
+
* Whatever `pushFn` resolves to is handed back untouched as `pushResult`.
|
|
39
|
+
*/
|
|
40
|
+
export async function pushDataWithSchemaRepair(pushFn, input, options = {}) {
|
|
41
|
+
const items = Array.isArray(input) ? input : [input];
|
|
42
|
+
// Happy path: assume validation will succeed (the overwhelmingly common
|
|
43
|
+
// case). No working copies, no maps, no per-item wrapper objects — just
|
|
44
|
+
// hand the caller's array to pushFn and return on success.
|
|
45
|
+
try {
|
|
46
|
+
const pushResult = await pushFn(items);
|
|
47
|
+
return { pushedCount: items.length, droppedItems: [], attemptCount: 1, pushResult };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (!isSchemaValidationError(err))
|
|
51
|
+
throw err;
|
|
52
|
+
// Clamp to >=1: the initial push above always counts as one attempt,
|
|
53
|
+
// so 0/negative would make the reported `attemptCount` lie about it.
|
|
54
|
+
return cleanAndRetry(pushFn, items, err, Math.max(1, options.maxAttempts ?? 5));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function cleanAndRetry(pushFn, originalItems, initialError, maxAttempts) {
|
|
58
|
+
// working[i] is what we'll send on the next push. We mutate this array
|
|
59
|
+
// in place (splicing drops, replacing cleaned entries); the caller's
|
|
60
|
+
// `originalItems` is never touched.
|
|
61
|
+
const working = originalItems.slice();
|
|
62
|
+
// Parallel to `working`. Holds the original (untouched) reference so a
|
|
63
|
+
// dropped report shows what the caller actually passed in, even if the
|
|
64
|
+
// item was partially cleaned before being dropped on a later round.
|
|
65
|
+
const originalAt = originalItems.slice();
|
|
66
|
+
// Parallel to `working`. Tracks which paths inside each item are
|
|
67
|
+
// placeholders we set (vs. real user data). Needed so we can chase
|
|
68
|
+
// follow-up type/minLength/enum errors on the placeholder field
|
|
69
|
+
// without looping forever; for user-supplied fields the existing
|
|
70
|
+
// "delete the field" behaviour stays in effect.
|
|
71
|
+
const placeholderPaths = originalItems.map(() => new Set());
|
|
72
|
+
// Parallel to `working`. The errors the API reported for this position in
|
|
73
|
+
// the *current* round, reset every round. Non-empty therefore means "still
|
|
74
|
+
// failing right now", which is what decides who gets dropped when we run
|
|
75
|
+
// out of attempts — and it gives that drop a reason to report.
|
|
76
|
+
const roundErrors = originalItems.map(() => NO_ERRORS);
|
|
77
|
+
const dropped = [];
|
|
78
|
+
let attempts = 1;
|
|
79
|
+
let lastError = initialError;
|
|
80
|
+
// Remove position `i` from every parallel array and record why it went.
|
|
81
|
+
const dropAt = (i, errors) => {
|
|
82
|
+
dropped.push({ item: originalAt[i], errors });
|
|
83
|
+
working.splice(i, 1);
|
|
84
|
+
originalAt.splice(i, 1);
|
|
85
|
+
placeholderPaths.splice(i, 1);
|
|
86
|
+
roundErrors.splice(i, 1);
|
|
87
|
+
};
|
|
88
|
+
// Every return path below either follows a successful push or has dropped
|
|
89
|
+
// everything that isn't in it, so "original minus dropped" is exactly what
|
|
90
|
+
// landed. (A rejected push stores nothing at all — not even the items the
|
|
91
|
+
// API found no fault with.)
|
|
92
|
+
const result = (attemptCount, pushResult) => ({
|
|
93
|
+
pushedCount: originalItems.length - dropped.length,
|
|
94
|
+
droppedItems: dropped,
|
|
95
|
+
attemptCount,
|
|
96
|
+
pushResult,
|
|
97
|
+
});
|
|
98
|
+
while (true) {
|
|
99
|
+
// Process this round's errors. Highest position first so the splices
|
|
100
|
+
// below don't shift positions we still need to look at.
|
|
101
|
+
const invalids = lastError.data.invalidItems.slice().sort((a, b) => b.itemPosition - a.itemPosition);
|
|
102
|
+
roundErrors.fill(NO_ERRORS);
|
|
103
|
+
// Which fields went wrong this round, across every failing item. We
|
|
104
|
+
// deliberately don't track which item had which problem — with more
|
|
105
|
+
// than one bad item that detail is noise, and the field set is what
|
|
106
|
+
// actually tells you what to fix in the schema or the scraper.
|
|
107
|
+
const repairedFields = new Set();
|
|
108
|
+
const droppedFields = new Set();
|
|
109
|
+
let droppedThisRound = 0;
|
|
110
|
+
for (const invalid of invalids) {
|
|
111
|
+
const i = invalid.itemPosition;
|
|
112
|
+
// Guard against a malformed/unexpected error payload (e.g. a
|
|
113
|
+
// position outside the batch we actually sent) instead of
|
|
114
|
+
// crashing on `working[i]` being undefined.
|
|
115
|
+
if (i < 0 || i >= working.length) {
|
|
116
|
+
console.log(`pushDataWithSchemaRepair: ignoring out-of-range itemPosition ${i} in validation error response.`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const cleaned = cleanItemFields(working[i], invalid.validationErrors, placeholderPaths[i]);
|
|
120
|
+
if (cleaned === null) {
|
|
121
|
+
collectFieldIssues(invalid.validationErrors, droppedFields);
|
|
122
|
+
droppedThisRound++;
|
|
123
|
+
dropAt(i, invalid.validationErrors);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
working[i] = cleaned;
|
|
127
|
+
roundErrors[i] = invalid.validationErrors;
|
|
128
|
+
collectFieldIssues(invalid.validationErrors, repairedFields);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const report = [
|
|
132
|
+
`pushDataWithSchemaRepair: schema validation failed on attempt ${attempts}: ${lastError.data.invalidItems.length} invalid item(s)`,
|
|
133
|
+
];
|
|
134
|
+
if (repairedFields.size > 0)
|
|
135
|
+
report.push(`repaired fields: ${formatFields(repairedFields)}`);
|
|
136
|
+
if (droppedThisRound > 0) {
|
|
137
|
+
report.push(`dropped ${droppedThisRound} item(s) on unfixable fields: ${formatFields(droppedFields)}`);
|
|
138
|
+
}
|
|
139
|
+
if (working.length === 0)
|
|
140
|
+
report.push('nothing left to retry.');
|
|
141
|
+
else if (attempts < maxAttempts)
|
|
142
|
+
report.push(`retrying with ${working.length} item(s).`);
|
|
143
|
+
else
|
|
144
|
+
report.push(`attempt cap reached with ${working.length} item(s) left.`);
|
|
145
|
+
console.log(report.join('; '));
|
|
146
|
+
if (working.length === 0)
|
|
147
|
+
return result(attempts);
|
|
148
|
+
attempts++;
|
|
149
|
+
if (attempts > maxAttempts) {
|
|
150
|
+
// Out of repair rounds. Drop what's still failing — but a rejected
|
|
151
|
+
// push stores *nothing*, so keeping the rest in the batch would
|
|
152
|
+
// throw away perfectly valid items along with the bad ones. Give
|
|
153
|
+
// the survivors one clean push of their own instead. They were
|
|
154
|
+
// validated in the round above and left untouched since, so this
|
|
155
|
+
// push is expected to go through.
|
|
156
|
+
const unresolvedFields = new Set();
|
|
157
|
+
let unresolved = 0;
|
|
158
|
+
for (let i = working.length - 1; i >= 0; i--) {
|
|
159
|
+
if (roundErrors[i].length === 0)
|
|
160
|
+
continue;
|
|
161
|
+
collectFieldIssues(roundErrors[i], unresolvedFields);
|
|
162
|
+
unresolved++;
|
|
163
|
+
dropAt(i, roundErrors[i]);
|
|
164
|
+
}
|
|
165
|
+
const giveUp = [`pushDataWithSchemaRepair: gave up after ${maxAttempts} attempts`];
|
|
166
|
+
if (unresolved > 0) {
|
|
167
|
+
giveUp.push(`dropped ${unresolved} item(s) still failing on fields: ${formatFields(unresolvedFields)}`);
|
|
168
|
+
}
|
|
169
|
+
giveUp.push(working.length > 0 ? `pushing the ${working.length} valid item(s) left.` : 'nothing to salvage.');
|
|
170
|
+
console.log(giveUp.join('; '));
|
|
171
|
+
if (working.length === 0)
|
|
172
|
+
return result(maxAttempts);
|
|
173
|
+
try {
|
|
174
|
+
const pushResult = await pushFn(working);
|
|
175
|
+
return result(attempts, pushResult);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (!isSchemaValidationError(err))
|
|
179
|
+
throw err;
|
|
180
|
+
// Even the survivors were rejected, so nothing landed. Report
|
|
181
|
+
// each one with whatever the API said about it this time.
|
|
182
|
+
const errorsAt = new Map();
|
|
183
|
+
for (const invalid of err.data.invalidItems) {
|
|
184
|
+
errorsAt.set(invalid.itemPosition, invalid.validationErrors);
|
|
185
|
+
}
|
|
186
|
+
console.log(`pushDataWithSchemaRepair: final push of ${working.length} item(s) was rejected too; dropping them.`);
|
|
187
|
+
for (let i = working.length - 1; i >= 0; i--)
|
|
188
|
+
dropAt(i, errorsAt.get(i) ?? NO_ERRORS);
|
|
189
|
+
return result(attempts);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const pushResult = await pushFn(working);
|
|
194
|
+
return result(attempts, pushResult);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
if (!isSchemaValidationError(err))
|
|
198
|
+
throw err;
|
|
199
|
+
lastError = err;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Add a `path (keyword)` label for every AJV error into `into`. A Set is
|
|
204
|
+
// used on purpose: one bad field usually shows up on many items in the
|
|
205
|
+
// batch, and repeating it once per item makes the log unreadable.
|
|
206
|
+
function collectFieldIssues(validationErrors, into) {
|
|
207
|
+
for (const err of validationErrors)
|
|
208
|
+
into.add(fieldIssueLabel(err));
|
|
209
|
+
}
|
|
210
|
+
// Human-readable "which field is broken, and how" for one AJV error.
|
|
211
|
+
//
|
|
212
|
+
// `required` and `additionalProperties` report the *parent* in instancePath
|
|
213
|
+
// and name the offending key in params, so we re-attach it — otherwise a
|
|
214
|
+
// missing top-level field would log as the useless `(item root)`.
|
|
215
|
+
function fieldIssueLabel(err) {
|
|
216
|
+
const child = offendingKey(err.params);
|
|
217
|
+
const parent = err.instancePath || '';
|
|
218
|
+
const path = child === undefined ? parent : `${parent}/${escapeJsonPointerSegment(child)}`;
|
|
219
|
+
return `${path === '' ? '(item root)' : collapseArrayIndices(path)} (${err.keyword})`;
|
|
220
|
+
}
|
|
221
|
+
// The key an error blames when instancePath points at its parent.
|
|
222
|
+
function offendingKey(params) {
|
|
223
|
+
if (typeof params?.missingProperty === 'string')
|
|
224
|
+
return params.missingProperty;
|
|
225
|
+
if (typeof params?.additionalProperty === 'string')
|
|
226
|
+
return params.additionalProperty;
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
// `/tags/0` and `/tags/7` are the same *field* as far as the log is
|
|
230
|
+
// concerned, so collapse numeric segments into `/tags/[]`. Keeps a batch
|
|
231
|
+
// with a hundred bad array elements down to a single entry.
|
|
232
|
+
function collapseArrayIndices(path) {
|
|
233
|
+
return path.replace(/\/\d+(?=\/|$)/g, '/[]');
|
|
234
|
+
}
|
|
235
|
+
// Render a field-issue set as a stable, bounded, comma-separated list.
|
|
236
|
+
function formatFields(fields) {
|
|
237
|
+
const sorted = [...fields].sort();
|
|
238
|
+
if (sorted.length <= MAX_LOGGED_FIELDS)
|
|
239
|
+
return sorted.join(', ');
|
|
240
|
+
const shown = sorted.slice(0, MAX_LOGGED_FIELDS);
|
|
241
|
+
return `${shown.join(', ')} (+${sorted.length - MAX_LOGGED_FIELDS} more)`;
|
|
242
|
+
}
|
|
243
|
+
// Try to repair a single item given its AJV errors. Returns null when the
|
|
244
|
+
// item can't be salvaged — including when none of its errors turned out to
|
|
245
|
+
// be actionable, since re-pushing an unchanged item would just reproduce the
|
|
246
|
+
// same errors and burn the remaining attempts.
|
|
247
|
+
//
|
|
248
|
+
// Mutates `placeholderPaths` to record any fields we filled in ourselves,
|
|
249
|
+
// so the caller can keep iterating on them across rounds.
|
|
250
|
+
function cleanItemFields(item, validationErrors, placeholderPaths) {
|
|
251
|
+
// structuredClone so we never mutate the caller's data.
|
|
252
|
+
const cloned = structuredClone(item);
|
|
253
|
+
let changed = false;
|
|
254
|
+
for (const err of sortForRepair(validationErrors)) {
|
|
255
|
+
const instancePath = err.instancePath || '';
|
|
256
|
+
const path = parseJsonPointer(instancePath);
|
|
257
|
+
// Missing required field: insert a placeholder and remember we did
|
|
258
|
+
// so. The next round will see a `type` error on this path and we'll
|
|
259
|
+
// upgrade null to a type-appropriate value. The property is named in
|
|
260
|
+
// params — instancePath points at the object that's missing it, which
|
|
261
|
+
// may be the item root or any object nested inside it.
|
|
262
|
+
if (err.keyword === 'required' && typeof err.params?.missingProperty === 'string') {
|
|
263
|
+
const target = [...path, err.params.missingProperty];
|
|
264
|
+
if (setAtPath(cloned, target, null)) {
|
|
265
|
+
placeholderPaths.add(toJsonPointer(target));
|
|
266
|
+
changed = true;
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
// Unknown property: also named in params, with instancePath pointing
|
|
271
|
+
// at its parent. Strip it.
|
|
272
|
+
if (err.keyword === 'additionalProperties' && typeof err.params?.additionalProperty === 'string') {
|
|
273
|
+
if (deleteAtPath(cloned, [...path, err.params.additionalProperty]))
|
|
274
|
+
changed = true;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
// Any other error at the root means the item itself is the wrong
|
|
278
|
+
// shape (wrong type, failed `anyOf`, …) — there's no field to strip.
|
|
279
|
+
if (path.length === 0)
|
|
280
|
+
return null;
|
|
281
|
+
// Errors on a path we placeholder'd: try to satisfy the constraint
|
|
282
|
+
// rather than delete the field (deleting would re-trigger required).
|
|
283
|
+
if (placeholderPaths.has(instancePath)) {
|
|
284
|
+
const fix = placeholderFor(err);
|
|
285
|
+
if (!fix.ok) {
|
|
286
|
+
// Constraint we don't know how to satisfy on a placeholder
|
|
287
|
+
// field (e.g. pattern, custom format). Give up on this item.
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
if (setAtPath(cloned, path, fix.value))
|
|
291
|
+
changed = true;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
// User-supplied field with a violation we don't try to coerce.
|
|
295
|
+
// Strip it; if the schema declares it required, the next push will
|
|
296
|
+
// re-add a placeholder.
|
|
297
|
+
if (deleteAtPath(cloned, path))
|
|
298
|
+
changed = true;
|
|
299
|
+
}
|
|
300
|
+
// Nothing we could act on (paths that no longer exist, keywords with no
|
|
301
|
+
// handler): the next push would fail identically, so drop the item now
|
|
302
|
+
// instead of spending every remaining attempt to learn that.
|
|
303
|
+
return changed ? cloned : null;
|
|
304
|
+
}
|
|
305
|
+
// Order one item's errors so the repairs don't interfere with each other.
|
|
306
|
+
// Deleting `/tags/0` shifts every later element down, so sibling array
|
|
307
|
+
// indices have to be handled highest-first; deeper paths come before their
|
|
308
|
+
// ancestors for the same reason.
|
|
309
|
+
function sortForRepair(validationErrors) {
|
|
310
|
+
return validationErrors
|
|
311
|
+
.map((err) => ({ err, path: parseJsonPointer(err.instancePath || '') }))
|
|
312
|
+
.sort((a, b) => comparePaths(a.path, b.path))
|
|
313
|
+
.map((entry) => entry.err);
|
|
314
|
+
}
|
|
315
|
+
function comparePaths(a, b) {
|
|
316
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
317
|
+
if (a[i] === b[i])
|
|
318
|
+
continue;
|
|
319
|
+
const ai = Number(a[i]);
|
|
320
|
+
const bi = Number(b[i]);
|
|
321
|
+
// Sibling array elements: highest index first.
|
|
322
|
+
if (Number.isInteger(ai) && Number.isInteger(bi))
|
|
323
|
+
return bi - ai;
|
|
324
|
+
return a[i] < b[i] ? -1 : 1;
|
|
325
|
+
}
|
|
326
|
+
// Same prefix: the deeper path first.
|
|
327
|
+
return b.length - a.length;
|
|
328
|
+
}
|
|
329
|
+
// Pick a value that will satisfy `err.keyword` on a placeholder field.
|
|
330
|
+
// Returns ok:false when we don't have a sensible default; the caller drops
|
|
331
|
+
// the item in that case.
|
|
332
|
+
//
|
|
333
|
+
// We deliberately only placeholder the four "empty" values — `''`, `[]`, `{}`,
|
|
334
|
+
// and `null`. These are unambiguously empty and can't be mistaken for real
|
|
335
|
+
// data. We do NOT fabricate values for `enum`, `format`, `minLength`, numeric
|
|
336
|
+
// bounds, etc.: a made-up email, a first-enum-value, or a `'_'.repeat(N)`
|
|
337
|
+
// string all silently poison the customer's dataset with plausible-looking
|
|
338
|
+
// junk. Better to drop the item than to lie about its contents. As a result
|
|
339
|
+
// the only keyword we handle is `type` (only for those four target types) —
|
|
340
|
+
// everything else falls through to ok:false and the item is dropped.
|
|
341
|
+
function placeholderFor(err) {
|
|
342
|
+
const params = err.params ?? {};
|
|
343
|
+
if (err.keyword !== 'type')
|
|
344
|
+
return { ok: false };
|
|
345
|
+
// params.type is the expected type as a string, or an array of strings
|
|
346
|
+
// when the field allows multiple types (e.g. `['string', 'null']`).
|
|
347
|
+
const types = Array.isArray(params.type) ? params.type : [params.type];
|
|
348
|
+
// Union type that permits null: prefer null. It's the cleanest possible
|
|
349
|
+
// placeholder — it commits to no concrete value at all — so whenever the
|
|
350
|
+
// schema allows it, that's what we use.
|
|
351
|
+
if (types.length > 1 && types.includes('null')) {
|
|
352
|
+
return { ok: true, value: null };
|
|
353
|
+
}
|
|
354
|
+
// Otherwise take the first allowed type we have an "empty" default for.
|
|
355
|
+
// integer / number / boolean are intentionally absent: 0 / false read as
|
|
356
|
+
// real data, so a field of only those types is dropped instead.
|
|
357
|
+
for (const t of types) {
|
|
358
|
+
switch (t) {
|
|
359
|
+
case 'null':
|
|
360
|
+
return { ok: true, value: null };
|
|
361
|
+
case 'string':
|
|
362
|
+
return { ok: true, value: '' };
|
|
363
|
+
case 'array':
|
|
364
|
+
return { ok: true, value: [] };
|
|
365
|
+
case 'object':
|
|
366
|
+
return { ok: true, value: {} };
|
|
367
|
+
default:
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { ok: false };
|
|
372
|
+
}
|
|
373
|
+
// Parse a JSON Pointer (RFC 6901) into decoded segments.
|
|
374
|
+
// "" -> []; "/foo/bar" -> ["foo","bar"]; "/tags/0" -> ["tags","0"].
|
|
375
|
+
function parseJsonPointer(pointer) {
|
|
376
|
+
if (!pointer)
|
|
377
|
+
return [];
|
|
378
|
+
return pointer
|
|
379
|
+
.split('/')
|
|
380
|
+
.slice(1)
|
|
381
|
+
.map((seg) => seg.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
382
|
+
}
|
|
383
|
+
// Inverse of parseJsonPointer — used to record placeholder paths in the same
|
|
384
|
+
// encoding the API reports them in, so later errors match by string.
|
|
385
|
+
function toJsonPointer(path) {
|
|
386
|
+
return path.map((seg) => `/${escapeJsonPointerSegment(seg)}`).join('');
|
|
387
|
+
}
|
|
388
|
+
function escapeJsonPointerSegment(segment) {
|
|
389
|
+
return segment.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
390
|
+
}
|
|
391
|
+
// Delete the value at `path` inside `obj`. Arrays splice; `sortForRepair`
|
|
392
|
+
// makes sure sibling indices are processed highest-first so the shift
|
|
393
|
+
// doesn't invalidate the paths we haven't handled yet.
|
|
394
|
+
function deleteAtPath(obj, path) {
|
|
395
|
+
if (path.length === 0)
|
|
396
|
+
return false;
|
|
397
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
398
|
+
let cursor = obj;
|
|
399
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
400
|
+
const key = path[i];
|
|
401
|
+
if (cursor == null || typeof cursor !== 'object')
|
|
402
|
+
return false;
|
|
403
|
+
cursor = Array.isArray(cursor) ? cursor[Number(key)] : cursor[key];
|
|
404
|
+
}
|
|
405
|
+
if (cursor == null || typeof cursor !== 'object')
|
|
406
|
+
return false;
|
|
407
|
+
const last = path[path.length - 1];
|
|
408
|
+
if (Array.isArray(cursor)) {
|
|
409
|
+
const idx = Number(last);
|
|
410
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= cursor.length)
|
|
411
|
+
return false;
|
|
412
|
+
cursor.splice(idx, 1);
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
if (Object.prototype.hasOwnProperty.call(cursor, last)) {
|
|
416
|
+
delete cursor[last];
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
// Set the value at `path` inside `obj`. Only the final segment is created —
|
|
422
|
+
// every parent on the way has to exist already (it does: the API reports the
|
|
423
|
+
// error against an object it found).
|
|
424
|
+
function setAtPath(obj, path, value) {
|
|
425
|
+
if (path.length === 0)
|
|
426
|
+
return false;
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
428
|
+
let cursor = obj;
|
|
429
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
430
|
+
const key = path[i];
|
|
431
|
+
if (cursor == null || typeof cursor !== 'object')
|
|
432
|
+
return false;
|
|
433
|
+
cursor = Array.isArray(cursor) ? cursor[Number(key)] : cursor[key];
|
|
434
|
+
}
|
|
435
|
+
if (cursor == null || typeof cursor !== 'object')
|
|
436
|
+
return false;
|
|
437
|
+
const last = path[path.length - 1];
|
|
438
|
+
if (Array.isArray(cursor)) {
|
|
439
|
+
const idx = Number(last);
|
|
440
|
+
if (!Number.isInteger(idx) || idx < 0)
|
|
441
|
+
return false;
|
|
442
|
+
cursor[idx] = value;
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
cursor[last] = value;
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
//# sourceMappingURL=pushDataWithSchemaRepair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pushDataWithSchemaRepair.js","sourceRoot":"","sources":["../../src/pushDataWithSchemaRepair.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apify/actor-utils",
|
|
3
|
-
"version": "0.1.3-beta.
|
|
3
|
+
"version": "0.1.3-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Wrapper around Actor.pushData that survives dataset schema-validation failures by stripping offending fields and retrying. Callers pass the actual push function as pushFn.",
|
|
6
6
|
"repository": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"safePushData.d.ts","sourceRoot":"","sources":["../../src/safePushData.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,iBAAiB,4BAA4B,CAAC;AAKpD,MAAM,WAAW,eAAe;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,WAAW;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACvC;AAKD,UAAU,qBAAqB;IAC3B,IAAI,EAAE,OAAO,iBAAiB,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,YAAY,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC;CACzC;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,qBAAqB,CAMlF;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAChC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,EACd,OAAO,GAAE,mBAAwB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAehC"}
|
package/dist/src/safePushData.js
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
// safePushData: parse the Apify dataset schema-validation error, repair the
|
|
2
|
-
// offending items (strip bad fields, placeholder missing required ones), and
|
|
3
|
-
// retry the push.
|
|
4
|
-
//
|
|
5
|
-
// NOTE: instead of recursively healing the data one error-round at a time, we
|
|
6
|
-
// could parse the Actor's `dataset_schema.json` up front and fix every item in
|
|
7
|
-
// a single pass (we'd know each field's expected type / constraints without
|
|
8
|
-
// waiting for the API to report them). That would avoid the multi-round retry
|
|
9
|
-
// loop, but requires heavier code (locating + loading the schema, resolving
|
|
10
|
-
// $refs, walking the schema tree). Something to consider in the future.
|
|
11
|
-
const SCHEMA_ERROR_TYPE = 'schema-validation-error';
|
|
12
|
-
export function isSchemaValidationError(err) {
|
|
13
|
-
if (!err || typeof err !== 'object')
|
|
14
|
-
return false;
|
|
15
|
-
const e = err;
|
|
16
|
-
if (e.type !== SCHEMA_ERROR_TYPE)
|
|
17
|
-
return false;
|
|
18
|
-
if (e.statusCode !== 400)
|
|
19
|
-
return false;
|
|
20
|
-
return Array.isArray(e.data?.invalidItems);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Push `input` via `pushFn`, surviving Apify dataset schema-validation
|
|
24
|
-
* failures by cleaning offending items and retrying.
|
|
25
|
-
*
|
|
26
|
-
* `pushFn` is mandatory and intentionally not bundled — a CI check forbids
|
|
27
|
-
* direct `.pushData()` calls in this library, so the binding lives at the
|
|
28
|
-
* call site: `(b) => Actor.pushData(b)` or
|
|
29
|
-
* `(b) => client.dataset(id).pushItems(b)`.
|
|
30
|
-
*/
|
|
31
|
-
export async function safePushData(pushFn, input, options = {}) {
|
|
32
|
-
const items = Array.isArray(input) ? input : [input];
|
|
33
|
-
// Happy path: assume validation will succeed (the overwhelmingly common
|
|
34
|
-
// case). No working copies, no maps, no per-item wrapper objects — just
|
|
35
|
-
// hand the caller's array to pushFn and return on success.
|
|
36
|
-
try {
|
|
37
|
-
await pushFn(items);
|
|
38
|
-
return { pushed: items.length, dropped: [], attempts: 1 };
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
if (!isSchemaValidationError(err))
|
|
42
|
-
throw err;
|
|
43
|
-
// Clamp to >=1: the initial push above always counts as one attempt,
|
|
44
|
-
// so 0/negative would make the reported `attempts` lie about it.
|
|
45
|
-
return cleanAndRetry(pushFn, items, err, Math.max(1, options.maxAttempts ?? 5));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function cleanAndRetry(pushFn, originalItems, initialError, maxAttempts) {
|
|
49
|
-
// working[i] is what we'll send on the next push. We mutate this array
|
|
50
|
-
// in place (splicing drops, replacing cleaned entries); the caller's
|
|
51
|
-
// `originalItems` is never touched.
|
|
52
|
-
const working = originalItems.slice();
|
|
53
|
-
// Parallel to `working`. Holds the original (untouched) reference so a
|
|
54
|
-
// dropped report shows what the caller actually passed in, even if the
|
|
55
|
-
// item was partially cleaned before being dropped on a later round.
|
|
56
|
-
const originalAt = originalItems.slice();
|
|
57
|
-
// Parallel to `working`. Tracks which paths inside each item are
|
|
58
|
-
// placeholders we set (vs. real user data). Needed so we can chase
|
|
59
|
-
// follow-up type/minLength/enum errors on the placeholder field
|
|
60
|
-
// without looping forever; for user-supplied fields the existing
|
|
61
|
-
// "delete the field" behaviour stays in effect.
|
|
62
|
-
const placeholderPaths = originalItems.map(() => new Set());
|
|
63
|
-
// Parallel to `working`. The validation errors that were last reported
|
|
64
|
-
// for this position — kept so a give-up drop (maxAttempts exceeded) can
|
|
65
|
-
// still report *why*, not just that it gave up.
|
|
66
|
-
const lastErrorsAt = originalItems.map(() => []);
|
|
67
|
-
const dropped = [];
|
|
68
|
-
let attempts = 1;
|
|
69
|
-
let lastError = initialError;
|
|
70
|
-
while (true) {
|
|
71
|
-
// Process this round's errors. Highest position first so the splices
|
|
72
|
-
// below don't shift positions we still need to look at.
|
|
73
|
-
const invalids = lastError.data.invalidItems.slice().sort((a, b) => b.itemPosition - a.itemPosition);
|
|
74
|
-
for (const invalid of invalids) {
|
|
75
|
-
const i = invalid.itemPosition;
|
|
76
|
-
// Guard against a malformed/unexpected error payload (e.g. a
|
|
77
|
-
// position outside the batch we actually sent) instead of
|
|
78
|
-
// crashing on `working[i]` being undefined.
|
|
79
|
-
if (i < 0 || i >= working.length) {
|
|
80
|
-
console.log(`safePushData: ignoring out-of-range itemPosition ${i} in validation error response.`);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const cleaned = cleanItemFields(working[i], invalid.validationErrors, placeholderPaths[i]);
|
|
84
|
-
if (cleaned === null) {
|
|
85
|
-
dropped.push({ item: originalAt[i], errors: invalid.validationErrors });
|
|
86
|
-
working.splice(i, 1);
|
|
87
|
-
originalAt.splice(i, 1);
|
|
88
|
-
placeholderPaths.splice(i, 1);
|
|
89
|
-
lastErrorsAt.splice(i, 1);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
working[i] = cleaned;
|
|
93
|
-
lastErrorsAt[i] = invalid.validationErrors;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
console.log(`safePushData: schema validation failed on attempt ${attempts}: ` +
|
|
97
|
-
`${lastError.data.invalidItems.length} invalid item(s); ` +
|
|
98
|
-
`retrying with ${working.length} item(s).`);
|
|
99
|
-
if (working.length === 0) {
|
|
100
|
-
return { pushed: originalItems.length - dropped.length, dropped, attempts };
|
|
101
|
-
}
|
|
102
|
-
attempts++;
|
|
103
|
-
if (attempts > maxAttempts) {
|
|
104
|
-
console.log(`safePushData: gave up after ${maxAttempts} attempts with ${working.length} item(s) still failing.`);
|
|
105
|
-
for (let i = 0; i < working.length; i++) {
|
|
106
|
-
dropped.push({ item: originalAt[i], errors: lastErrorsAt[i] });
|
|
107
|
-
}
|
|
108
|
-
return { pushed: originalItems.length - dropped.length, dropped, attempts: maxAttempts };
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
await pushFn(working);
|
|
112
|
-
return { pushed: originalItems.length - dropped.length, dropped, attempts };
|
|
113
|
-
}
|
|
114
|
-
catch (err) {
|
|
115
|
-
if (!isSchemaValidationError(err))
|
|
116
|
-
throw err;
|
|
117
|
-
lastError = err;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
// Try to repair a single item given its AJV errors. Returns null when the
|
|
122
|
-
// item can't be salvaged.
|
|
123
|
-
//
|
|
124
|
-
// Mutates `placeholderPaths` to record any fields we filled in ourselves,
|
|
125
|
-
// so the caller can keep iterating on them across rounds.
|
|
126
|
-
function cleanItemFields(item, validationErrors, placeholderPaths) {
|
|
127
|
-
// structuredClone so we never mutate the caller's data.
|
|
128
|
-
const cloned = structuredClone(item);
|
|
129
|
-
for (const err of validationErrors) {
|
|
130
|
-
const instancePath = err.instancePath || '';
|
|
131
|
-
const path = parseJsonPointer(instancePath);
|
|
132
|
-
// Root-level errors describe the item as a whole.
|
|
133
|
-
if (path.length === 0) {
|
|
134
|
-
// Missing required field: insert a placeholder and remember we
|
|
135
|
-
// did so. The next round will see a `type` error on this path
|
|
136
|
-
// and we'll upgrade null to a type-appropriate value.
|
|
137
|
-
if (err.keyword === 'required' && typeof err.params?.missingProperty === 'string') {
|
|
138
|
-
const prop = err.params.missingProperty;
|
|
139
|
-
cloned[prop] = null;
|
|
140
|
-
placeholderPaths.add(`/${prop}`);
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
// Strip an unknown property reported at the root.
|
|
144
|
-
if (err.keyword === 'additionalProperties' && typeof err.params?.additionalProperty === 'string') {
|
|
145
|
-
delete cloned[err.params.additionalProperty];
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
// type/format/etc. at the root means the item itself is the wrong shape.
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
// Errors on a path we placeholder'd: try to satisfy the constraint
|
|
152
|
-
// rather than delete the field (deleting would re-trigger required).
|
|
153
|
-
if (placeholderPaths.has(instancePath)) {
|
|
154
|
-
const fix = placeholderFor(err);
|
|
155
|
-
if (fix.ok) {
|
|
156
|
-
setAtPath(cloned, path, fix.value);
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
// Constraint we don't know how to satisfy on a placeholder field
|
|
160
|
-
// (e.g. pattern, custom format). Give up on this item.
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
// additionalProperties errors point at the *parent* object; the
|
|
164
|
-
// offending key is in params, not in instancePath.
|
|
165
|
-
if (err.keyword === 'additionalProperties' && typeof err.params?.additionalProperty === 'string') {
|
|
166
|
-
deleteAtPath(cloned, [...path, err.params.additionalProperty]);
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
// User-supplied field with a violation we don't try to coerce.
|
|
170
|
-
// Strip it; if the schema declares it required, the next push will
|
|
171
|
-
// re-add a placeholder.
|
|
172
|
-
deleteAtPath(cloned, path);
|
|
173
|
-
}
|
|
174
|
-
return cloned;
|
|
175
|
-
}
|
|
176
|
-
// Pick a value that will satisfy `err.keyword` on a placeholder field.
|
|
177
|
-
// Returns ok:false when we don't have a sensible default; the caller drops
|
|
178
|
-
// the item in that case.
|
|
179
|
-
//
|
|
180
|
-
// We deliberately only placeholder the four "empty" values — `''`, `[]`, `{}`,
|
|
181
|
-
// and `null`. These are unambiguously empty and can't be mistaken for real
|
|
182
|
-
// data. We do NOT fabricate values for `enum`, `format`, `minLength`, numeric
|
|
183
|
-
// bounds, etc.: a made-up email, a first-enum-value, or a `'_'.repeat(N)`
|
|
184
|
-
// string all silently poison the customer's dataset with plausible-looking
|
|
185
|
-
// junk. Better to drop the item than to lie about its contents. As a result
|
|
186
|
-
// the only keyword we handle is `type` (only for those four target types) —
|
|
187
|
-
// everything else falls through to ok:false and the item is dropped.
|
|
188
|
-
function placeholderFor(err) {
|
|
189
|
-
const params = err.params ?? {};
|
|
190
|
-
if (err.keyword !== 'type')
|
|
191
|
-
return { ok: false };
|
|
192
|
-
// params.type is the expected type as a string, or an array of strings
|
|
193
|
-
// when the field allows multiple types (e.g. `['string', 'null']`).
|
|
194
|
-
const types = Array.isArray(params.type) ? params.type : [params.type];
|
|
195
|
-
// Union type that permits null: prefer null. It's the cleanest possible
|
|
196
|
-
// placeholder — it commits to no concrete value at all — so whenever the
|
|
197
|
-
// schema allows it, that's what we use.
|
|
198
|
-
if (types.length > 1 && types.includes('null')) {
|
|
199
|
-
return { ok: true, value: null };
|
|
200
|
-
}
|
|
201
|
-
// Otherwise take the first allowed type we have an "empty" default for.
|
|
202
|
-
// integer / number / boolean are intentionally absent: 0 / false read as
|
|
203
|
-
// real data, so a field of only those types is dropped instead.
|
|
204
|
-
for (const t of types) {
|
|
205
|
-
switch (t) {
|
|
206
|
-
case 'null':
|
|
207
|
-
return { ok: true, value: null };
|
|
208
|
-
case 'string':
|
|
209
|
-
return { ok: true, value: '' };
|
|
210
|
-
case 'array':
|
|
211
|
-
return { ok: true, value: [] };
|
|
212
|
-
case 'object':
|
|
213
|
-
return { ok: true, value: {} };
|
|
214
|
-
default:
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return { ok: false };
|
|
219
|
-
}
|
|
220
|
-
// Parse a JSON Pointer (RFC 6901) into decoded segments.
|
|
221
|
-
// "" -> []; "/foo/bar" -> ["foo","bar"]; "/tags/0" -> ["tags","0"].
|
|
222
|
-
function parseJsonPointer(pointer) {
|
|
223
|
-
if (!pointer)
|
|
224
|
-
return [];
|
|
225
|
-
return pointer
|
|
226
|
-
.split('/')
|
|
227
|
-
.slice(1)
|
|
228
|
-
.map((seg) => seg.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
229
|
-
}
|
|
230
|
-
// Delete the value at `path` inside `obj`. Arrays splice (so later
|
|
231
|
-
// `/tags/N` errors in the same round line up with their original indices).
|
|
232
|
-
function deleteAtPath(obj, path) {
|
|
233
|
-
if (path.length === 0)
|
|
234
|
-
return false;
|
|
235
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
236
|
-
let cursor = obj;
|
|
237
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
238
|
-
const key = path[i];
|
|
239
|
-
if (cursor == null || typeof cursor !== 'object')
|
|
240
|
-
return false;
|
|
241
|
-
cursor = Array.isArray(cursor) ? cursor[Number(key)] : cursor[key];
|
|
242
|
-
}
|
|
243
|
-
if (cursor == null || typeof cursor !== 'object')
|
|
244
|
-
return false;
|
|
245
|
-
const last = path[path.length - 1];
|
|
246
|
-
if (Array.isArray(cursor)) {
|
|
247
|
-
const idx = Number(last);
|
|
248
|
-
if (!Number.isInteger(idx) || idx < 0 || idx >= cursor.length)
|
|
249
|
-
return false;
|
|
250
|
-
cursor.splice(idx, 1);
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
if (Object.prototype.hasOwnProperty.call(cursor, last)) {
|
|
254
|
-
delete cursor[last];
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
// Set the value at `path` inside `obj`. Creates nothing — the path must
|
|
260
|
-
// already exist (placeholder fields are created at the root, not nested).
|
|
261
|
-
function setAtPath(obj, path, value) {
|
|
262
|
-
if (path.length === 0)
|
|
263
|
-
return false;
|
|
264
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
265
|
-
let cursor = obj;
|
|
266
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
267
|
-
const key = path[i];
|
|
268
|
-
if (cursor == null || typeof cursor !== 'object')
|
|
269
|
-
return false;
|
|
270
|
-
cursor = Array.isArray(cursor) ? cursor[Number(key)] : cursor[key];
|
|
271
|
-
}
|
|
272
|
-
if (cursor == null || typeof cursor !== 'object')
|
|
273
|
-
return false;
|
|
274
|
-
const last = path[path.length - 1];
|
|
275
|
-
if (Array.isArray(cursor)) {
|
|
276
|
-
const idx = Number(last);
|
|
277
|
-
if (!Number.isInteger(idx) || idx < 0)
|
|
278
|
-
return false;
|
|
279
|
-
cursor[idx] = value;
|
|
280
|
-
return true;
|
|
281
|
-
}
|
|
282
|
-
cursor[last] = value;
|
|
283
|
-
return true;
|
|
284
|
-
}
|
|
285
|
-
//# sourceMappingURL=safePushData.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"safePushData.js","sourceRoot":"","sources":["../../src/safePushData.ts"],"names":[],"mappings":"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"}
|