@apify/actor-utils 0.1.2 → 0.1.3-beta.1

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.
Files changed (38) hide show
  1. package/README.md +73 -27
  2. package/dist/src/qc-logger/assert.d.ts +17 -0
  3. package/dist/src/qc-logger/assert.d.ts.map +1 -0
  4. package/dist/src/qc-logger/assert.js +23 -0
  5. package/dist/src/qc-logger/assert.js.map +1 -0
  6. package/dist/src/qc-logger/checkpoint.d.ts +40 -0
  7. package/dist/src/qc-logger/checkpoint.d.ts.map +1 -0
  8. package/dist/src/qc-logger/checkpoint.js +45 -0
  9. package/dist/src/qc-logger/checkpoint.js.map +1 -0
  10. package/dist/src/qc-logger/index.d.ts +6 -0
  11. package/dist/src/qc-logger/index.d.ts.map +1 -0
  12. package/dist/src/qc-logger/index.js +5 -0
  13. package/dist/src/qc-logger/index.js.map +1 -0
  14. package/dist/src/qc-logger/is.d.ts +116 -0
  15. package/dist/src/qc-logger/is.d.ts.map +1 -0
  16. package/dist/src/qc-logger/is.js +26 -0
  17. package/dist/src/qc-logger/is.js.map +1 -0
  18. package/dist/src/qc-logger/structured-logger.d.ts +9 -0
  19. package/dist/src/qc-logger/structured-logger.d.ts.map +1 -0
  20. package/dist/src/qc-logger/structured-logger.js +38 -0
  21. package/dist/src/qc-logger/structured-logger.js.map +1 -0
  22. package/dist/src/safePushData.d.ts +16 -6
  23. package/dist/src/safePushData.d.ts.map +1 -1
  24. package/dist/src/safePushData.js +267 -131
  25. package/dist/src/safePushData.js.map +1 -1
  26. package/dist/src/type-utils/index.d.ts +3 -0
  27. package/dist/src/type-utils/index.d.ts.map +1 -0
  28. package/dist/src/type-utils/index.js +2 -0
  29. package/dist/src/type-utils/index.js.map +1 -0
  30. package/dist/src/type-utils/types.d.ts +23 -0
  31. package/dist/src/type-utils/types.d.ts.map +1 -0
  32. package/dist/src/type-utils/types.js +2 -0
  33. package/dist/src/type-utils/types.js.map +1 -0
  34. package/dist/src/type-utils/unreachable.d.ts +30 -0
  35. package/dist/src/type-utils/unreachable.d.ts.map +1 -0
  36. package/dist/src/type-utils/unreachable.js +32 -0
  37. package/dist/src/type-utils/unreachable.js.map +1 -0
  38. package/package.json +70 -59
package/README.md CHANGED
@@ -17,13 +17,20 @@ await Actor.init();
17
17
  const result = await safePushData((batch) => Actor.pushData(batch), items);
18
18
 
19
19
  console.log(result);
20
- // { pushed: 2, dropped: [...], attempts: 2 }
20
+ // { pushedCount: 2, droppedItems: [...], attemptCount: 2, pushResult: undefined }
21
21
  ```
22
22
 
23
23
  Accepts a single item or an array. `pushFn` is the first positional arg
24
24
  and is required — the library itself never imports the Apify SDK, and a
25
25
  CI check forbids `.pushData(` from appearing anywhere in the source.
26
26
 
27
+ Whatever `pushFn` resolves to comes back as `pushResult`, so a push
28
+ function with a meaningful return value stays usable:
29
+
30
+ ```ts
31
+ const { pushResult } = await safePushData((batch) => client.dataset(id).pushItems(batch), items);
32
+ ```
33
+
27
34
  ## Performance notes
28
35
 
29
36
  The happy path is a single `try/await pushFn(items)` with **no extra
@@ -38,49 +45,88 @@ every AJV error per item:
38
45
 
39
46
  | Error | Action |
40
47
  | ------------------------------------ | ---------------------------------------------------------------------------------- |
41
- | `required` at the root | Set the missing field to `null` and mark the path as a placeholder. |
42
- | `additionalProperties` at the root | Delete the unknown property. |
48
+ | `required` (at any depth) | Set the missing field to `null` and mark the path as a placeholder. |
49
+ | `additionalProperties` (any depth) | Delete the unknown property. |
43
50
  | `type` / `format` / etc. at the root | Item itself is the wrong shape → dropped. |
44
51
  | Constraint on a **placeholder** path | Replace with a type-aware default (see below). If no default is known → dropped. |
45
52
  | Constraint on **user-supplied** data | Delete the field. If the schema later marks it required, a placeholder takes over. |
53
+ | Nothing on the item was actionable | Item is dropped — an unchanged item would fail identically on the next push. |
46
54
 
47
55
  ### Placeholder defaults
48
56
 
49
57
  When a constraint fires on a path we placeholder'd ourselves, the wrapper
50
- picks a value that should satisfy it:
51
-
52
- | AJV keyword | Placeholder value |
53
- | ------------------------------------------------------- | ------------------------------- |
54
- | `type: string` | `''` |
55
- | `type: integer` / `number` | `0` |
56
- | `type: boolean` | `false` |
57
- | `type: array` | `[]` |
58
- | `type: object` | `{}` |
59
- | `type: null` | `null` |
60
- | `minLength: N` / `maxLength` | `'_'.repeat(N)` / `''` |
61
- | `minimum: N` / `maximum` | `N` |
62
- | `exclusiveMinimum: N` / `exclusiveMaximum` | `N + 1` / `N - 1` |
63
- | `enum` | First allowed value |
64
- | `format: email` / `uri` / `date` / `date-time` / `uuid` | a static valid example for each |
65
- | Anything else (`pattern`, custom formats…) | Item is dropped. |
58
+ picks a value that should satisfy it. We deliberately only fill in the four
59
+ **empty** values below — they're unambiguously empty and can't be mistaken
60
+ for real data:
61
+
62
+ | AJV keyword | Placeholder value |
63
+ | -------------- | ----------------- |
64
+ | `type: string` | `''` |
65
+ | `type: array` | `[]` |
66
+ | `type: object` | `{}` |
67
+ | `type: null` | `null` |
68
+ | Anything else | Item is dropped. |
69
+
70
+ When a field allows **multiple types** (e.g. `['string', 'null']`), the
71
+ wrapper always picks `null` — it's the cleanest placeholder because it
72
+ commits to no concrete value at all.
73
+
74
+ Everything else (`enum`, `format`, `minLength`, numeric bounds, `type:
75
+ integer` / `number` / `boolean`, …) is **not** placeholdered: a made-up
76
+ email, a first-enum-value, or a fabricated number would silently poison the
77
+ customer's dataset with plausible-looking junk, so the item is dropped
78
+ instead.
66
79
 
67
80
  The retry loop chases one layer of errors per round
68
- (`required` → `type` → `minLength` → push) until either the push succeeds
81
+ (`required` → `type` → push) until either the push succeeds
69
82
  or `maxAttempts` (default 5) is hit.
70
83
 
84
+ ### When the attempt cap is hit
85
+
86
+ Items still failing on the last allowed attempt are dropped — but the rest
87
+ of the batch is **not** lost with them. Because a rejected push stores
88
+ nothing at all, the wrapper drops the incurable items and then makes one
89
+ final push with the survivors, which the API already validated in the
90
+ previous round. That final push is counted in `attemptCount`, so a run that
91
+ exhausts `maxAttempts: 5` can report `attemptCount: 6`.
92
+
93
+ ## Logging
94
+
95
+ Every failed round logs which fields went wrong, so you can fix the schema
96
+ (or the scraper) without digging through the returned `droppedItems`:
97
+
98
+ ```
99
+ safePushData: 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).
100
+ safePushData: gave up after 5 attempts; dropped 3 item(s) still failing on fields: /sku (pattern); pushing the 9 valid item(s) left.
101
+ ```
102
+
103
+ The field list is a **set**, not a per-item breakdown — one bad field
104
+ usually shows up on many items in a batch, and knowing which item had which
105
+ problem rarely changes what you do about it. Array indices collapse
106
+ (`/tags/0`, `/tags/7` → `/tags/[]`) for the same reason, and the list is
107
+ capped at 20 entries with the rest reported as `(+N more)`.
108
+
71
109
  ## Options
72
110
 
73
- | Option | Type | Default | Notes |
74
- | ------------- | -------- | ------- | -------------------- |
75
- | `maxAttempts` | `number` | `5` | Hard cap on retries. |
111
+ | Option | Type | Default | Notes |
112
+ | ------------- | -------- | ------- | -------------------------------------------------------------- |
113
+ | `maxAttempts` | `number` | `5` | Cap on repair rounds, plus the final salvage push if it's hit. |
76
114
 
77
115
  ## Return shape
78
116
 
117
+ Names say what they hold: `*Count` is a number, `*Items` is an array of
118
+ objects.
119
+
79
120
  ```ts
80
- interface SafePushDataResult<T> {
81
- pushed: number;
82
- dropped: { item: T; errors: ValidationError[] }[];
83
- attempts: number;
121
+ interface SafePushDataResult<T, R = unknown> {
122
+ /** How many of the caller's items made it into the dataset. */
123
+ pushedCount: number;
124
+ /** The items we couldn't repair, each with the errors that doomed it. */
125
+ droppedItems: { item: T; errors: ValidationError[] }[];
126
+ /** How many times `pushFn` was actually called. */
127
+ attemptCount: number;
128
+ /** What the successful `pushFn` call resolved to; absent if none did. */
129
+ pushResult?: R;
84
130
  }
85
131
  ```
86
132
 
@@ -0,0 +1,17 @@
1
+ import { type Ctx } from './structured-logger.js';
2
+ /**
3
+ * Glorified assertion that also logs at `error` level. Throws a plain `Error`
4
+ * by default; pass e.g. Crawlee's `NonRetryableError` or `CriticalError` to
5
+ * control what the failure means to the crawler.
6
+ *
7
+ *
8
+ * ```ts
9
+ * const someEnvVar = process.env.SOME_ENV_VAR; // string | undefined
10
+ * assert(typeof someEnvVar === "string", "some-env-var");
11
+ * // someEnvVar is now guaranteed to be a `string`, unless the assertion fails.
12
+ * assert(someEnvVar, "some-env-var");
13
+ * // someEnvVar is now guaranteed to be a truthy `string` (non-empty), unless the assertion fails.
14
+ * ```
15
+ */
16
+ export declare function assert(cond: unknown, key: string, ctx?: Ctx, ErrorCtor?: new (message: string) => Error): asserts cond;
17
+ //# sourceMappingURL=assert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../../src/qc-logger/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAuB,MAAM,wBAAwB,CAAC;AAEvE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAClB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,GAAG,EACT,SAAS,GAAE,KAAK,OAAO,EAAE,MAAM,KAAK,KAAa,GAClD,OAAO,CAAC,IAAI,CAKd"}
@@ -0,0 +1,23 @@
1
+ import { emit, formatMessage } from './structured-logger.js';
2
+ /**
3
+ * Glorified assertion that also logs at `error` level. Throws a plain `Error`
4
+ * by default; pass e.g. Crawlee's `NonRetryableError` or `CriticalError` to
5
+ * control what the failure means to the crawler.
6
+ *
7
+ *
8
+ * ```ts
9
+ * const someEnvVar = process.env.SOME_ENV_VAR; // string | undefined
10
+ * assert(typeof someEnvVar === "string", "some-env-var");
11
+ * // someEnvVar is now guaranteed to be a `string`, unless the assertion fails.
12
+ * assert(someEnvVar, "some-env-var");
13
+ * // someEnvVar is now guaranteed to be a truthy `string` (non-empty), unless the assertion fails.
14
+ * ```
15
+ */
16
+ export function assert(cond, key, ctx, ErrorCtor = Error) {
17
+ if (cond)
18
+ return;
19
+ const error = new ErrorCtor(formatMessage('assert', key));
20
+ emit('error', 'assert', key, { ...ctx, name: error.name, stack: error.stack });
21
+ throw error;
22
+ }
23
+ //# sourceMappingURL=assert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert.js","sourceRoot":"","sources":["../../../src/qc-logger/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,IAAI,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,MAAM,CAClB,IAAa,EACb,GAAW,EACX,GAAS,EACT,YAA4C,KAAK;IAEjD,IAAI,IAAI;QAAE,OAAO;IACjB,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/E,MAAM,KAAK,CAAC;AAChB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type Ctx } from './structured-logger.js';
2
+ /**
3
+ * Logs an `info`-level checkpoint marking that a point in the run was reached.
4
+ * Unlike the `is*` predicates or {@link assert}, this has no condition — it
5
+ * always emits — so it's meant for tracing progress through a run.
6
+ *
7
+ * @param key - Identifies the checkpoint; emitted as `qc:checkpoint:<key>`.
8
+ * @param ctx - Optional extra fields to attach to the log line.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * checkpoint("started");
13
+ * // logs `qc:checkpoint:started`
14
+ *
15
+ * await doWork();
16
+ * checkpoint("work-done", { itemsProcessed: 42 });
17
+ * // logs `qc:checkpoint:work-done` with { itemsProcessed: 42 }
18
+ * ```
19
+ */
20
+ export declare function checkpoint(key: string, ctx?: Ctx): void;
21
+ /**
22
+ * Conditionally logs an `info`-level checkpoint — only when `cond` is truthy.
23
+ * A convenience wrapper over {@link checkpoint} for guarding a marker behind a
24
+ * condition without an explicit `if` at the call site.
25
+ *
26
+ * @param cond - The condition; the checkpoint is emitted only when it's truthy.
27
+ * @param key - Identifies the checkpoint; emitted as `qc:checkpoint:<key>`.
28
+ * @param ctx - Optional extra fields to attach to the log line.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * checkpointIf(items.length === 0, "empty-batch");
33
+ * // logs `qc:checkpoint:empty-batch` only when the batch is empty
34
+ *
35
+ * checkpointIf(retries > 3, "many-retries", { retries });
36
+ * // logs `qc:checkpoint:many-retries` with { retries } only past the threshold
37
+ * ```
38
+ */
39
+ export declare function checkpointIf(cond: unknown, key: string, ctx?: Ctx): void;
40
+ //# sourceMappingURL=checkpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../../../src/qc-logger/checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAQ,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,CAEvD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,CAExE"}
@@ -0,0 +1,45 @@
1
+ import { emit } from './structured-logger.js';
2
+ /**
3
+ * Logs an `info`-level checkpoint marking that a point in the run was reached.
4
+ * Unlike the `is*` predicates or {@link assert}, this has no condition — it
5
+ * always emits — so it's meant for tracing progress through a run.
6
+ *
7
+ * @param key - Identifies the checkpoint; emitted as `qc:checkpoint:<key>`.
8
+ * @param ctx - Optional extra fields to attach to the log line.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * checkpoint("started");
13
+ * // logs `qc:checkpoint:started`
14
+ *
15
+ * await doWork();
16
+ * checkpoint("work-done", { itemsProcessed: 42 });
17
+ * // logs `qc:checkpoint:work-done` with { itemsProcessed: 42 }
18
+ * ```
19
+ */
20
+ export function checkpoint(key, ctx) {
21
+ emit('info', 'checkpoint', key, ctx);
22
+ }
23
+ /**
24
+ * Conditionally logs an `info`-level checkpoint — only when `cond` is truthy.
25
+ * A convenience wrapper over {@link checkpoint} for guarding a marker behind a
26
+ * condition without an explicit `if` at the call site.
27
+ *
28
+ * @param cond - The condition; the checkpoint is emitted only when it's truthy.
29
+ * @param key - Identifies the checkpoint; emitted as `qc:checkpoint:<key>`.
30
+ * @param ctx - Optional extra fields to attach to the log line.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * checkpointIf(items.length === 0, "empty-batch");
35
+ * // logs `qc:checkpoint:empty-batch` only when the batch is empty
36
+ *
37
+ * checkpointIf(retries > 3, "many-retries", { retries });
38
+ * // logs `qc:checkpoint:many-retries` with { retries } only past the threshold
39
+ * ```
40
+ */
41
+ export function checkpointIf(cond, key, ctx) {
42
+ if (cond)
43
+ checkpoint(key, ctx);
44
+ }
45
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../../src/qc-logger/checkpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,GAAS;IAC7C,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAAC,IAAa,EAAE,GAAW,EAAE,GAAS;IAC9D,IAAI,IAAI;QAAE,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { checkpoint, checkpointIf } from './checkpoint.js';
2
+ export { assert } from './assert.js';
3
+ export { isFalsy, isTruthy, isNullish, isDefined } from './is.js';
4
+ export { emit, formatMessage } from './structured-logger.js';
5
+ export type { Ctx } from './structured-logger.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/qc-logger/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { checkpoint, checkpointIf } from './checkpoint.js';
2
+ export { assert } from './assert.js';
3
+ export { isFalsy, isTruthy, isNullish, isDefined } from './is.js';
4
+ export { emit, formatMessage } from './structured-logger.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/qc-logger/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,116 @@
1
+ import type { Defined, Falsy, Nullish, Truthy } from '../type-utils/types.js';
2
+ import { type Ctx } from './structured-logger.js';
3
+ /**
4
+ * Checks for falsy values and logs a `warning` when they are.
5
+ *
6
+ * Sample usage:
7
+ * ```ts
8
+ * if (isFalsy(someValue, "my-key-here")) {
9
+ * // someValue is falsy, warning gets logged `qc:is:falsy:my-key-here`
10
+ * return; // do my early return
11
+ * }
12
+ * ```
13
+ *
14
+ * The overloads narrow the return type based on what's statically known about
15
+ * `value`: a {@link Truthy} input returns the literal `false`, a {@link Falsy}
16
+ * input returns the literal `true`, and only genuinely uncertain values get the
17
+ * `value is Falsy & T` type guard. This means a redundant call — e.g.
18
+ * `isFalsy` on something the compiler already knows is truthy — produces a
19
+ * branch whose condition is a constant.
20
+ *
21
+ * Enable the `@typescript-eslint/no-unnecessary-condition` rule to have that
22
+ * surfaced as a lint error instead of a silent dead branch. The rule ships with
23
+ * both [typescript-eslint](https://typescript-eslint.io/rules/no-unnecessary-condition/)
24
+ * and [oxlint](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-condition.html)
25
+ * (note: it requires type-aware linting).
26
+ */
27
+ export declare function isFalsy<T>(value: Truthy<T>, key: string, ctx?: Ctx): false;
28
+ export declare function isFalsy(value: Falsy, key: string, ctx?: Ctx): true;
29
+ export declare function isFalsy<T>(value: T, key: string, ctx?: Ctx): value is Falsy & T;
30
+ /**
31
+ * Checks for truthy values and logs a `warning` when they are — the
32
+ * inverse of {@link isFalsy}.
33
+ *
34
+ * Sample usage:
35
+ * ```ts
36
+ * if (isTruthy(someValue, "my-key-here")) {
37
+ * // someValue is truthy, warning gets logged `qc:is:truthy:my-key-here`
38
+ * return someValue; // do my early return
39
+ * }
40
+ * // fallback behavior here
41
+ * ```
42
+ *
43
+ * Mirroring {@link isFalsy}, the overloads narrow the return type based on what's
44
+ * statically known about `value`: a {@link Falsy} input returns the literal
45
+ * `false`, a {@link Truthy} input returns the literal `true`, and only genuinely
46
+ * uncertain values get the `value is Truthy<T>` type guard. A redundant call —
47
+ * e.g. `isTruthy` on something the compiler already knows is falsy — produces a
48
+ * branch whose condition is a constant.
49
+ *
50
+ * Enable the `@typescript-eslint/no-unnecessary-condition` rule to have that
51
+ * surfaced as a lint error instead of a silent dead branch. The rule ships with
52
+ * both [typescript-eslint](https://typescript-eslint.io/rules/no-unnecessary-condition/)
53
+ * and [oxlint](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-condition.html)
54
+ * (note: it requires type-aware linting).
55
+ */
56
+ export declare function isTruthy(value: Falsy, key: string, ctx?: Ctx): false;
57
+ export declare function isTruthy<T>(value: Truthy<T>, key: string, ctx?: Ctx): true;
58
+ export declare function isTruthy<T>(value: T, key: string, ctx?: Ctx): value is Truthy<T>;
59
+ /**
60
+ * Checks for `null | undefined` values and logs a `warning` when they are.
61
+ * Unlike {@link isFalsy}, `0`, `false`, and `""` are not considered a match.
62
+ *
63
+ * Sample usage:
64
+ * ```ts
65
+ * if (isNullish(someValue, "my-key-here")) {
66
+ * // someValue is null or undefined, warning gets logged `qc:is:nullish:my-key-here`
67
+ * return; // do my early return
68
+ * }
69
+ * // keep on truckin'
70
+ * ```
71
+ *
72
+ * Like {@link isFalsy}, the overloads narrow the return type based on what's
73
+ * statically known about `value`: a {@link Defined} input returns the literal
74
+ * `false`, a {@link Nullish} input returns the literal `true`, and only
75
+ * genuinely uncertain values get the `value is Nullish & T` type guard. A
76
+ * redundant call — e.g. `isNullish` on something the compiler already knows is
77
+ * defined — produces a branch whose condition is a constant.
78
+ *
79
+ * Enable the `@typescript-eslint/no-unnecessary-condition` rule to have that
80
+ * surfaced as a lint error instead of a silent dead branch. The rule ships with
81
+ * both [typescript-eslint](https://typescript-eslint.io/rules/no-unnecessary-condition/)
82
+ * and [oxlint](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-condition.html)
83
+ * (note: it requires type-aware linting).
84
+ */
85
+ export declare function isNullish<T>(value: Defined<T>, key: string, ctx?: Ctx): false;
86
+ export declare function isNullish(value: Nullish, key: string, ctx?: Ctx): true;
87
+ export declare function isNullish<T>(value: T, key: string, ctx?: Ctx): value is Nullish & T;
88
+ /**
89
+ * Checks that a value is neither `null` nor `undefined` and logs a
90
+ * `warning` when that's the case — the inverse of {@link isNullish}.
91
+ *
92
+ * Sample usage:
93
+ * ```ts
94
+ * if (isDefined(someValue, "my-key-here")) {
95
+ * // someValue is neither null nor undefined, warning gets logged `qc:is:defined:my-key-here`
96
+ * return; // do my early return
97
+ * }
98
+ * ```
99
+ *
100
+ * Like {@link isFalsy}, the overloads narrow the return type based on what's
101
+ * statically known about `value`: a {@link Nullish} input returns the literal
102
+ * `false`, a {@link Defined} input returns the literal `true`, and only
103
+ * genuinely uncertain values get the `value is Defined<T>` type guard. A
104
+ * redundant call — e.g. `isDefined` on something the compiler already knows is
105
+ * defined — produces a branch whose condition is a constant.
106
+ *
107
+ * Enable the `@typescript-eslint/no-unnecessary-condition` rule to have that
108
+ * surfaced as a lint error instead of a silent dead branch. The rule ships with
109
+ * both [typescript-eslint](https://typescript-eslint.io/rules/no-unnecessary-condition/)
110
+ * and [oxlint](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-unnecessary-condition.html)
111
+ * (note: it requires type-aware linting).
112
+ */
113
+ export declare function isDefined(value: Nullish, key: string, ctx?: Ctx): false;
114
+ export declare function isDefined<T>(value: Defined<T>, key: string, ctx?: Ctx): true;
115
+ export declare function isDefined<T>(value: T, key: string, ctx?: Ctx): value is Defined<T>;
116
+ //# sourceMappingURL=is.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is.d.ts","sourceRoot":"","sources":["../../../src/qc-logger/is.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,KAAK,GAAG,EAAQ,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC;AAC5E,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;AACpE,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC;AAOjF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC;AACtE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;AAC5E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;AAOlF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC;AAC/E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;AACxE,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC;AAOrF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC;AACzE,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;AAC9E,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { emit } from './structured-logger.js';
2
+ export function isFalsy(value, key, ctx) {
3
+ const result = !value;
4
+ if (result)
5
+ emit('warning', 'is:falsy', key, ctx);
6
+ return result;
7
+ }
8
+ export function isTruthy(value, key, ctx) {
9
+ const result = Boolean(value);
10
+ if (result)
11
+ emit('warning', 'is:truthy', key, ctx);
12
+ return result;
13
+ }
14
+ export function isNullish(value, key, ctx) {
15
+ const result = value === undefined || value === null;
16
+ if (result)
17
+ emit('warning', 'is:nullish', key, ctx);
18
+ return result;
19
+ }
20
+ export function isDefined(value, key, ctx) {
21
+ const result = value !== undefined && value !== null;
22
+ if (result)
23
+ emit('warning', 'is:defined', key, ctx);
24
+ return result;
25
+ }
26
+ //# sourceMappingURL=is.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is.js","sourceRoot":"","sources":["../../../src/qc-logger/is.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA6BxD,MAAM,UAAU,OAAO,CAAI,KAAQ,EAAE,GAAW,EAAE,GAAS;IACvD,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC;IACtB,IAAI,MAAM;QAAE,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC;AAClB,CAAC;AA+BD,MAAM,UAAU,QAAQ,CAAI,KAAQ,EAAE,GAAW,EAAE,GAAS;IACxD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,MAAM;QAAE,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC;AAClB,CAAC;AA+BD,MAAM,UAAU,SAAS,CAAI,KAAQ,EAAE,GAAW,EAAE,GAAS;IACzD,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;IACrD,IAAI,MAAM;QAAE,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC;AAClB,CAAC;AA8BD,MAAM,UAAU,SAAS,CAAI,KAAQ,EAAE,GAAW,EAAE,GAAS;IACzD,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;IACrD,IAAI,MAAM;QAAE,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,9 @@
1
+ export type Ctx = Record<string, unknown>;
2
+ /**
3
+ * Builds the canonical log line (`qc:<verb>:<key>`) without emitting it.
4
+ * Exposed so callers (e.g. `assert`) can reuse the exact same message when
5
+ * constructing an error to throw.
6
+ */
7
+ export declare function formatMessage(qcVerb: string, rawKey: string): string;
8
+ export declare function emit(level: 'debug' | 'info' | 'warning' | 'error', qcVerb: string, rawKey: string, ctx?: Ctx): string;
9
+ //# sourceMappingURL=structured-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-logger.d.ts","sourceRoot":"","sources":["../../../src/qc-logger/structured-logger.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAiB1C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,MAAM,CAcrH"}
@@ -0,0 +1,38 @@
1
+ import log from '@apify/log';
2
+ /**
3
+ * Keys are part of the log line's parse structure (`qc:<verb>:<key>`)
4
+ * Turns any string into a valid qc key.
5
+ *
6
+ * If a key is completely invalid, it is replaced with "invalid-key".
7
+ */
8
+ function normalizeKey(key) {
9
+ return (String(key)
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9-]+/g, '-')
12
+ .replace(/^-|-$/g, '') || 'invalid-key');
13
+ }
14
+ /**
15
+ * Builds the canonical log line (`qc:<verb>:<key>`) without emitting it.
16
+ * Exposed so callers (e.g. `assert`) can reuse the exact same message when
17
+ * constructing an error to throw.
18
+ */
19
+ export function formatMessage(qcVerb, rawKey) {
20
+ return `qc:${qcVerb}:${normalizeKey(rawKey)}`;
21
+ }
22
+ export function emit(level, qcVerb, rawKey, ctx) {
23
+ let message;
24
+ try {
25
+ const qcKey = normalizeKey(rawKey);
26
+ message = formatMessage(qcVerb, rawKey);
27
+ // Caller ctx is spread first so it can never clobber the canonical fields.
28
+ // Run/actor identity is not attached here — Mezmo enriches lines with it.
29
+ log[level](message, { ...ctx, level, qcKey, qcVerb });
30
+ return message;
31
+ }
32
+ catch {
33
+ // Telemetry must never break a run.
34
+ message ??= `qc:unknown:qc-error-generating-key`;
35
+ return message;
36
+ }
37
+ }
38
+ //# sourceMappingURL=structured-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-logger.js","sourceRoot":"","sources":["../../../src/qc-logger/structured-logger.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,YAAY,CAAC;AAI7B;;;;;GAKG;AACH,SAAS,YAAY,CAAC,GAAW;IAC7B,OAAO,CACH,MAAM,CAAC,GAAG,CAAC;SACN,WAAW,EAAE;SACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,aAAa,CAC9C,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,MAAc;IACxD,OAAO,MAAM,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,KAA6C,EAAE,MAAc,EAAE,MAAc,EAAE,GAAS;IACzG,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACxC,2EAA2E;QAC3E,0EAA0E;QAC1E,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACL,oCAAoC;QACpC,OAAO,KAAK,oCAAoC,CAAC;QACjD,OAAO,OAAO,CAAC;IACnB,CAAC;AACL,CAAC"}
@@ -23,15 +23,23 @@ export interface DroppedItem<T> {
23
23
  item: T;
24
24
  errors: ValidationError[];
25
25
  }
26
- export interface SafePushDataResult<T> {
27
- pushed: number;
28
- dropped: DroppedItem<T>[];
29
- attempts: number;
26
+ export interface SafePushDataResult<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
39
  export interface SafePushDataOptions {
32
40
  maxAttempts?: number;
33
41
  }
34
- export type PushFn<T> = (items: T[]) => Promise<unknown>;
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 safePushData<T>(pushFn: PushFn<T>, input: T | T[], options?: SafePushDataOptions): Promise<SafePushDataResult<T>>;
54
+ export declare function safePushData<T, R = unknown>(pushFn: PushFn<T, R>, input: T | T[], options?: SafePushDataOptions): Promise<SafePushDataResult<T, R>>;
45
55
  export {};
46
56
  //# sourceMappingURL=safePushData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safePushData.d.ts","sourceRoot":"","sources":["../../src/safePushData.ts"],"names":[],"mappings":"AAIA,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"}
1
+ {"version":3,"file":"safePushData.d.ts","sourceRoot":"","sources":["../../src/safePushData.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,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO;IAC9C,+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,mBAAmB;IAChC,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,YAAY,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAC7C,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EACpB,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,EACd,OAAO,GAAE,mBAAwB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAenC"}