@opencraw/driver 0.2.0 → 0.4.0
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 +7 -4
- package/dist/index.esm.js +155 -47
- package/dist/main.esm.js +131 -34
- package/dist/src/callout-bridge/client-hooks.client.d.ts +2 -0
- package/dist/src/callout-bridge/index.d.ts +1 -1
- package/dist/src/index.d.ts +5 -3
- package/dist/src/protocol/driver-message.contract.d.ts +139 -1
- package/dist/src/protocol/index.d.ts +2 -2
- package/dist/src/session/index.d.ts +1 -1
- package/dist/src/session/run-session.use-case.d.ts +11 -11
- package/dist/src/work-bridge/client-work-source.client.d.ts +17 -0
- package/dist/src/work-bridge/index.d.ts +2 -0
- package/package.json +3 -3
- package/schemas/driver-protocol.schema.json +335 -1
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ npx opencraw-driver # then write JSON-RPC lines to it
|
|
|
17
17
|
## The conversation
|
|
18
18
|
|
|
19
19
|
```jsonc
|
|
20
|
-
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientName":"opencraw-python","clientVersion":"1.0.0","protocolVersion":"1.
|
|
21
|
-
← {"jsonrpc":"2.0","id":1,"result":{"driverVersion":"0.2.0","protocolVersion":"1.
|
|
20
|
+
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientName":"opencraw-python","clientVersion":"1.0.0","protocolVersion":"1.2.0"}}
|
|
21
|
+
← {"jsonrpc":"2.0","id":1,"result":{"driverVersion":"0.2.0","protocolVersion":"1.2.0","methods":["initialize","validate","run","work","cancel","probe","diff","export","shutdown"]}}
|
|
22
22
|
→ {"jsonrpc":"2.0","id":2,"method":"run","params":{"paths":["recipes/"],"hooks":["tidy"]}}
|
|
23
23
|
← {"jsonrpc":"2.0","id":2,"result":{"runId":"run-1"}}
|
|
24
24
|
← {"jsonrpc":"2.0","method":"event","params":{"runId":"run-1","event":{"type":"recipe:start",…}}}
|
|
@@ -34,6 +34,7 @@ npx opencraw-driver # then write JSON-RPC lines to it
|
|
|
34
34
|
| `initialize` | client | Must come first. The driver refuses a protocol of another major. |
|
|
35
35
|
| `validate` | client | Loads recipes (`paths` or inline `recipes`) without running them: the inputs that would run, every hook they call, and every problem with its path. |
|
|
36
36
|
| `run` | client | Starts a run and answers its `runId` at once; the crawl goes on in the background. `hooks`, `captchaSolvers` and `accessPlugins` name what the client answers; `accessConfig` and `access` pick the access config and profile; `out` writes records to a file instead of sending them. |
|
|
37
|
+
| `work` | client | Starts a worker pool and answers its `runId` at once. Its windows ask the client for items (`work.next`) and keep the steps marked `keep` between them; `windows` is a count or core's `WindowsPolicy`. Takes the same crawler fields as `run`. |
|
|
37
38
|
| `cancel` | client | Stops a run (its crawler is closed); it still ends with `finished`. |
|
|
38
39
|
| `probe` | client | Fetches a page (or renders it, with `browser`) and says where its data lives: the CLI's and MCP's probe, through the same access fields as `run`. |
|
|
39
40
|
| `diff` | client | Compares two runs' JSON Lines files by key: counts, the report a person reads, and the changes up to `limit`. |
|
|
@@ -41,10 +42,12 @@ npx opencraw-driver # then write JSON-RPC lines to it
|
|
|
41
42
|
| `shutdown` | client | Closes every run, answers, then exits. Closing stdin does the same. |
|
|
42
43
|
| `event` | driver | Every crawl event, as core reports it. |
|
|
43
44
|
| `record` | driver | Each record, unless the run has `out`. |
|
|
44
|
-
| `finished` | driver | The run is over: its report, or why it could not run. |
|
|
45
|
+
| `finished` | driver | The run is over: its report (for `work`, the work report), or why it could not run. |
|
|
46
|
+
| `work.next` | driver | A window wants an item: answer `{ "item": { "id", "vars", "recipe"? } }`, holding the answer until there is one, or `{ "item": null }` to end the work. An id already queued or running runs once. |
|
|
47
|
+
| `work.done`, `work.failed` | driver | How an item ended: its report and records, or its outcome (`failure`, `neutral`). |
|
|
45
48
|
| `callout` | driver | A hook, captcha solver or access plugin the client serves (`request.kind`: `hook`, `captcha`, `access`). The request and its answer are core's [callout contract](../core/README.md): `ok`, `error`, or `pending` with `retryAfterMs`, asked again with the same idempotency key. |
|
|
46
49
|
|
|
47
|
-
Every answer comes back before the driver stops, even when a client writes its last requests and closes
|
|
50
|
+
A run's answer (`run`, `work`) always comes before any of its events, records or callouts. Every answer comes back before the driver stops, even when a client writes its last requests and closes
|
|
48
51
|
stdin at once. Answers may arrive out of order; match them by `id`.
|
|
49
52
|
|
|
50
53
|
## For client authors
|
package/dist/index.esm.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/server/serve-driver.use-case.ts
|
|
2
|
-
import { once } from "node:events";
|
|
2
|
+
import { once as once2 } from "node:events";
|
|
3
3
|
|
|
4
4
|
// src/protocol/driver-message.contract.ts
|
|
5
5
|
import { calloutRequestSchema, calloutResponseSchema } from "@opencraw/core";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
var PROTOCOL_VERSION = "1.
|
|
7
|
+
var PROTOCOL_VERSION = "1.2.0";
|
|
8
8
|
var recipeObject = z.record(z.string(), z.unknown());
|
|
9
9
|
var coreObject = z.record(z.string(), z.unknown());
|
|
10
10
|
var accessFields = {
|
|
@@ -54,6 +54,24 @@ var runParamsSchema = z.strictObject({
|
|
|
54
54
|
...accessFields
|
|
55
55
|
});
|
|
56
56
|
var runResultSchema = z.strictObject({ runId: z.string() });
|
|
57
|
+
var windowsSchema = z.union([z.number().int().min(1).max(64), z.record(z.string(), z.unknown())]);
|
|
58
|
+
var workParamsSchema = z.strictObject({
|
|
59
|
+
...recipeSource,
|
|
60
|
+
only: z.array(z.string()).optional(),
|
|
61
|
+
out: z.string().optional(),
|
|
62
|
+
append: z.boolean().optional(),
|
|
63
|
+
browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
|
|
64
|
+
hooks: z.array(z.string()).optional(),
|
|
65
|
+
captchaSolvers: z.array(z.string()).optional(),
|
|
66
|
+
...accessFields,
|
|
67
|
+
windows: windowsSchema.optional()
|
|
68
|
+
});
|
|
69
|
+
var varValue = z.union([z.string(), z.number(), z.boolean()]);
|
|
70
|
+
var workItemSchema = z.strictObject({
|
|
71
|
+
id: z.string().min(1),
|
|
72
|
+
vars: z.record(z.string(), varValue),
|
|
73
|
+
recipe: z.string().optional()
|
|
74
|
+
});
|
|
57
75
|
var cancelParamsSchema = z.strictObject({ runId: z.string() });
|
|
58
76
|
var cancelResultSchema = z.strictObject({ cancelled: z.boolean() });
|
|
59
77
|
var probeParamsSchema = z.strictObject({
|
|
@@ -101,11 +119,16 @@ var exportParamsSchema = z.strictObject({
|
|
|
101
119
|
});
|
|
102
120
|
var exportResultSchema = z.strictObject({ written: z.number(), location: z.string() });
|
|
103
121
|
var shutdownResultSchema = z.strictObject({});
|
|
122
|
+
var workNextParamsSchema = z.strictObject({ runId: z.string() });
|
|
123
|
+
var workNextResultSchema = z.strictObject({ item: workItemSchema.nullable() });
|
|
124
|
+
var workRecordSchema = z.strictObject({ key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
|
|
125
|
+
var workDoneParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, records: z.array(workRecordSchema) });
|
|
126
|
+
var workFailedParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, outcome: z.enum(["failure", "neutral"]) });
|
|
104
127
|
var eventParamsSchema = z.strictObject({ runId: z.string(), event: z.record(z.string(), z.unknown()) });
|
|
105
128
|
var recordParamsSchema = z.strictObject({ runId: z.string(), recipeId: z.string(), key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
|
|
106
129
|
var finishedParamsSchema = z.strictObject({
|
|
107
130
|
runId: z.string(),
|
|
108
|
-
/** The crawl report (`CrawlReport`), absent when the run could not start. */
|
|
131
|
+
/** The crawl report (`CrawlReport`), or for `work` the work report (`WorkReport`); absent when the run could not start. */
|
|
109
132
|
report: z.record(z.string(), z.unknown()).optional(),
|
|
110
133
|
/** Why the run could not start, or stopped as a whole. */
|
|
111
134
|
error: z.string().optional()
|
|
@@ -113,18 +136,22 @@ var finishedParamsSchema = z.strictObject({
|
|
|
113
136
|
var calloutParamsSchema = z.strictObject({ runId: z.string(), request: calloutRequestSchema });
|
|
114
137
|
var calloutResultSchema = calloutResponseSchema;
|
|
115
138
|
var MESSAGES = {
|
|
116
|
-
initialize: { direction: "client-to-driver", params: initializeParamsSchema, result: initializeResultSchema },
|
|
117
|
-
validate: { direction: "client-to-driver", params: validateParamsSchema, result: validateResultSchema },
|
|
118
|
-
run: { direction: "client-to-driver", params: runParamsSchema, result: runResultSchema },
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
"initialize": { direction: "client-to-driver", params: initializeParamsSchema, result: initializeResultSchema },
|
|
140
|
+
"validate": { direction: "client-to-driver", params: validateParamsSchema, result: validateResultSchema },
|
|
141
|
+
"run": { direction: "client-to-driver", params: runParamsSchema, result: runResultSchema },
|
|
142
|
+
"work": { direction: "client-to-driver", params: workParamsSchema, result: runResultSchema },
|
|
143
|
+
"cancel": { direction: "client-to-driver", params: cancelParamsSchema, result: cancelResultSchema },
|
|
144
|
+
"probe": { direction: "client-to-driver", params: probeParamsSchema, result: probeResultSchema },
|
|
145
|
+
"diff": { direction: "client-to-driver", params: diffParamsSchema, result: diffResultSchema },
|
|
146
|
+
"export": { direction: "client-to-driver", params: exportParamsSchema, result: exportResultSchema },
|
|
147
|
+
"shutdown": { direction: "client-to-driver", params: void 0, result: shutdownResultSchema },
|
|
148
|
+
"callout": { direction: "driver-to-client", params: calloutParamsSchema, result: calloutResultSchema },
|
|
149
|
+
"work.next": { direction: "driver-to-client", params: workNextParamsSchema, result: workNextResultSchema },
|
|
150
|
+
"work.done": { direction: "driver-to-client", params: workDoneParamsSchema, result: void 0 },
|
|
151
|
+
"work.failed": { direction: "driver-to-client", params: workFailedParamsSchema, result: void 0 },
|
|
152
|
+
"event": { direction: "driver-to-client", params: eventParamsSchema, result: void 0 },
|
|
153
|
+
"record": { direction: "driver-to-client", params: recordParamsSchema, result: void 0 },
|
|
154
|
+
"finished": { direction: "driver-to-client", params: finishedParamsSchema, result: void 0 }
|
|
128
155
|
};
|
|
129
156
|
|
|
130
157
|
// src/protocol/driver-json-schema.mapper.ts
|
|
@@ -219,6 +246,66 @@ async function resolveSiteAccess(params, runId, ask, env = process.env) {
|
|
|
219
246
|
return { access, accessPlugins: clientAccessPlugins(params.accessPlugins ?? [], runId, ask) };
|
|
220
247
|
}
|
|
221
248
|
|
|
249
|
+
// src/work-bridge/client-work-source.client.ts
|
|
250
|
+
import { once } from "node:events";
|
|
251
|
+
function clientWorkSource(runId, ask, notify) {
|
|
252
|
+
const kept = [];
|
|
253
|
+
let ended = false;
|
|
254
|
+
const askClient = async () => {
|
|
255
|
+
const { item } = workNextResultSchema.parse(await ask("work.next", { runId }));
|
|
256
|
+
if (item === null) {
|
|
257
|
+
ended = true;
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
return item;
|
|
261
|
+
};
|
|
262
|
+
const keepLate = async (answer) => {
|
|
263
|
+
try {
|
|
264
|
+
const item = await answer;
|
|
265
|
+
if (item !== void 0) kept.push(item);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
next: async (options) => {
|
|
271
|
+
const waiting = kept.shift();
|
|
272
|
+
if (waiting !== void 0) return waiting;
|
|
273
|
+
if (ended) return;
|
|
274
|
+
const answer = askClient();
|
|
275
|
+
const signal = options?.signal;
|
|
276
|
+
if (signal === void 0) return await answer;
|
|
277
|
+
signal.throwIfAborted();
|
|
278
|
+
const settled = new AbortController();
|
|
279
|
+
const answered = (async () => {
|
|
280
|
+
const item = await answer;
|
|
281
|
+
return { item };
|
|
282
|
+
})();
|
|
283
|
+
const retired = (async () => {
|
|
284
|
+
try {
|
|
285
|
+
await once(signal, "abort", { signal: settled.signal });
|
|
286
|
+
return "retired";
|
|
287
|
+
} catch {
|
|
288
|
+
return "answered";
|
|
289
|
+
}
|
|
290
|
+
})();
|
|
291
|
+
try {
|
|
292
|
+
const first = await Promise.race([answered, retired]);
|
|
293
|
+
if (first !== "retired" && first !== "answered") return first.item;
|
|
294
|
+
void keepLate(answer);
|
|
295
|
+
throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
|
|
296
|
+
} finally {
|
|
297
|
+
settled.abort();
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
done: (item, report, records) => {
|
|
301
|
+
notify("work.done", { runId, item, report, records: records.map((record) => ({ key: record.key, data: record.data })) });
|
|
302
|
+
},
|
|
303
|
+
failed: (item, report, outcome) => {
|
|
304
|
+
notify("work.failed", { runId, item, report, outcome });
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
222
309
|
// src/session/recipe-loading.use-case.ts
|
|
223
310
|
import { loadForRun } from "@opencraw/cli";
|
|
224
311
|
import { hookUses, RecipeBindingError, RecipeValidationError } from "@opencraw/core";
|
|
@@ -260,41 +347,54 @@ function streamingSink() {
|
|
|
260
347
|
function runSessions(notify, ask) {
|
|
261
348
|
const running = /* @__PURE__ */ new Map();
|
|
262
349
|
let next = 1;
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
350
|
+
const crawlerFor = async (runId, params) => {
|
|
351
|
+
const { access, accessPlugins } = await resolveSiteAccess(params, runId, ask);
|
|
352
|
+
return createCrawler({
|
|
353
|
+
sink: params.out === void 0 ? streamingSink() : fileSink2(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
|
|
354
|
+
hooks: clientHooks(params.hooks ?? [], runId, ask),
|
|
355
|
+
captchaSolvers: clientCaptchaSolvers(params.captchaSolvers ?? [], runId, ask),
|
|
356
|
+
access,
|
|
357
|
+
accessPlugins,
|
|
358
|
+
profilesDir: process.env.OPENCRAW_PROFILES,
|
|
359
|
+
parallel: params.parallel,
|
|
360
|
+
resume: params.resume,
|
|
361
|
+
debug: params.dryRun,
|
|
362
|
+
browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
|
|
363
|
+
onEvent: (event) => {
|
|
364
|
+
notify("event", { runId, event });
|
|
365
|
+
if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
const track = (runId, crawler, crawling) => {
|
|
370
|
+
running.set(runId, crawler);
|
|
371
|
+
void (async () => {
|
|
372
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
373
|
+
try {
|
|
374
|
+
notify("finished", { runId, report: await crawling() });
|
|
375
|
+
} catch (error) {
|
|
376
|
+
notify("finished", { runId, error: error instanceof Error ? error.message : String(error) });
|
|
377
|
+
} finally {
|
|
378
|
+
running.delete(runId);
|
|
379
|
+
await crawler.close();
|
|
380
|
+
}
|
|
381
|
+
})();
|
|
273
382
|
};
|
|
274
383
|
return {
|
|
275
384
|
start: async (params) => {
|
|
276
385
|
const loaded = await loadRecipes(params);
|
|
277
386
|
const set = params.dryRun === true ? new RecipeSet(loaded.output, loaded.inputs.map((input) => ({ ...input, limits: { ...input.limits, maxRecords: 1 } }))) : loaded;
|
|
278
387
|
const runId = `run-${next++}`;
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
debug: params.dryRun,
|
|
290
|
-
browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
|
|
291
|
-
onEvent: (event) => {
|
|
292
|
-
notify("event", { runId, event });
|
|
293
|
-
if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
running.set(runId, crawler);
|
|
297
|
-
void crawl(runId, crawler, set);
|
|
388
|
+
const crawler = await crawlerFor(runId, params);
|
|
389
|
+
track(runId, crawler, async () => await crawler.run(set));
|
|
390
|
+
return runId;
|
|
391
|
+
},
|
|
392
|
+
startWork: async (params) => {
|
|
393
|
+
const set = await loadRecipes(params);
|
|
394
|
+
const runId = `run-${next++}`;
|
|
395
|
+
const crawler = await crawlerFor(runId, params);
|
|
396
|
+
const windows = params.windows;
|
|
397
|
+
track(runId, crawler, async () => await crawler.work(set, clientWorkSource(runId, ask, notify), { windows }));
|
|
298
398
|
return runId;
|
|
299
399
|
},
|
|
300
400
|
cancel: async (runId) => {
|
|
@@ -435,7 +535,7 @@ function openLineChannel(input, output, handle) {
|
|
|
435
535
|
}
|
|
436
536
|
|
|
437
537
|
// src/server/serve-driver.use-case.ts
|
|
438
|
-
var METHODS = ["initialize", "validate", "run", "cancel", "probe", "diff", "export", "shutdown"];
|
|
538
|
+
var METHODS = ["initialize", "validate", "run", "work", "cancel", "probe", "diff", "export", "shutdown"];
|
|
439
539
|
var major = (version) => version.split(".", 1)[0];
|
|
440
540
|
function parsed(schema, params) {
|
|
441
541
|
const result = schema.safeParse(params ?? {});
|
|
@@ -458,6 +558,7 @@ async function serveDriver(input, output, identity) {
|
|
|
458
558
|
if (!initialized) throw new RpcFailure(RPC_ERRORS.server, "call initialize first");
|
|
459
559
|
if (method === "validate") return await validateRecipes(parsed(validateParamsSchema, params));
|
|
460
560
|
if (method === "run") return { runId: await sessions.start(parsed(runParamsSchema, params)) };
|
|
561
|
+
if (method === "work") return { runId: await sessions.startWork(parsed(workParamsSchema, params)) };
|
|
461
562
|
if (method === "cancel") return { cancelled: await sessions.cancel(parsed(cancelParamsSchema, params).runId) };
|
|
462
563
|
if (method === "probe") return await probeSite(parsed(probeParamsSchema, params), `probe-${++probes}`, channel.request);
|
|
463
564
|
if (method === "diff") return await diffRecordFiles(parsed(diffParamsSchema, params));
|
|
@@ -469,7 +570,7 @@ async function serveDriver(input, output, identity) {
|
|
|
469
570
|
return {};
|
|
470
571
|
});
|
|
471
572
|
const sessions = runSessions(channel.notify, channel.request);
|
|
472
|
-
await Promise.race([channel.closed,
|
|
573
|
+
await Promise.race([channel.closed, once2(stopping.signal, "abort")]);
|
|
473
574
|
await channel.drained();
|
|
474
575
|
await sessions.closeAll();
|
|
475
576
|
}
|
|
@@ -485,6 +586,7 @@ export {
|
|
|
485
586
|
clientAccessPlugins,
|
|
486
587
|
clientCaptchaSolvers,
|
|
487
588
|
clientHooks,
|
|
589
|
+
clientWorkSource,
|
|
488
590
|
diffParamsSchema,
|
|
489
591
|
diffRecordFiles,
|
|
490
592
|
diffResultSchema,
|
|
@@ -508,6 +610,12 @@ export {
|
|
|
508
610
|
streamingSink,
|
|
509
611
|
validateParamsSchema,
|
|
510
612
|
validateRecipes,
|
|
511
|
-
validateResultSchema
|
|
613
|
+
validateResultSchema,
|
|
614
|
+
workDoneParamsSchema,
|
|
615
|
+
workFailedParamsSchema,
|
|
616
|
+
workItemSchema,
|
|
617
|
+
workNextParamsSchema,
|
|
618
|
+
workNextResultSchema,
|
|
619
|
+
workParamsSchema
|
|
512
620
|
};
|
|
513
621
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/main.esm.js
CHANGED
|
@@ -6,12 +6,12 @@ import { dirname, join } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
// src/server/serve-driver.use-case.ts
|
|
9
|
-
import { once } from "node:events";
|
|
9
|
+
import { once as once2 } from "node:events";
|
|
10
10
|
|
|
11
11
|
// src/protocol/driver-message.contract.ts
|
|
12
12
|
import { calloutRequestSchema, calloutResponseSchema } from "@opencraw/core";
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
var PROTOCOL_VERSION = "1.
|
|
14
|
+
var PROTOCOL_VERSION = "1.2.0";
|
|
15
15
|
var recipeObject = z.record(z.string(), z.unknown());
|
|
16
16
|
var coreObject = z.record(z.string(), z.unknown());
|
|
17
17
|
var accessFields = {
|
|
@@ -61,6 +61,24 @@ var runParamsSchema = z.strictObject({
|
|
|
61
61
|
...accessFields
|
|
62
62
|
});
|
|
63
63
|
var runResultSchema = z.strictObject({ runId: z.string() });
|
|
64
|
+
var windowsSchema = z.union([z.number().int().min(1).max(64), z.record(z.string(), z.unknown())]);
|
|
65
|
+
var workParamsSchema = z.strictObject({
|
|
66
|
+
...recipeSource,
|
|
67
|
+
only: z.array(z.string()).optional(),
|
|
68
|
+
out: z.string().optional(),
|
|
69
|
+
append: z.boolean().optional(),
|
|
70
|
+
browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
|
|
71
|
+
hooks: z.array(z.string()).optional(),
|
|
72
|
+
captchaSolvers: z.array(z.string()).optional(),
|
|
73
|
+
...accessFields,
|
|
74
|
+
windows: windowsSchema.optional()
|
|
75
|
+
});
|
|
76
|
+
var varValue = z.union([z.string(), z.number(), z.boolean()]);
|
|
77
|
+
var workItemSchema = z.strictObject({
|
|
78
|
+
id: z.string().min(1),
|
|
79
|
+
vars: z.record(z.string(), varValue),
|
|
80
|
+
recipe: z.string().optional()
|
|
81
|
+
});
|
|
64
82
|
var cancelParamsSchema = z.strictObject({ runId: z.string() });
|
|
65
83
|
var cancelResultSchema = z.strictObject({ cancelled: z.boolean() });
|
|
66
84
|
var probeParamsSchema = z.strictObject({
|
|
@@ -107,11 +125,16 @@ var exportParamsSchema = z.strictObject({
|
|
|
107
125
|
});
|
|
108
126
|
var exportResultSchema = z.strictObject({ written: z.number(), location: z.string() });
|
|
109
127
|
var shutdownResultSchema = z.strictObject({});
|
|
128
|
+
var workNextParamsSchema = z.strictObject({ runId: z.string() });
|
|
129
|
+
var workNextResultSchema = z.strictObject({ item: workItemSchema.nullable() });
|
|
130
|
+
var workRecordSchema = z.strictObject({ key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
|
|
131
|
+
var workDoneParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, records: z.array(workRecordSchema) });
|
|
132
|
+
var workFailedParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, outcome: z.enum(["failure", "neutral"]) });
|
|
110
133
|
var eventParamsSchema = z.strictObject({ runId: z.string(), event: z.record(z.string(), z.unknown()) });
|
|
111
134
|
var recordParamsSchema = z.strictObject({ runId: z.string(), recipeId: z.string(), key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
|
|
112
135
|
var finishedParamsSchema = z.strictObject({
|
|
113
136
|
runId: z.string(),
|
|
114
|
-
/** The crawl report (`CrawlReport`), absent when the run could not start. */
|
|
137
|
+
/** The crawl report (`CrawlReport`), or for `work` the work report (`WorkReport`); absent when the run could not start. */
|
|
115
138
|
report: z.record(z.string(), z.unknown()).optional(),
|
|
116
139
|
/** Why the run could not start, or stopped as a whole. */
|
|
117
140
|
error: z.string().optional()
|
|
@@ -184,6 +207,66 @@ async function resolveSiteAccess(params, runId, ask, env = process.env) {
|
|
|
184
207
|
return { access, accessPlugins: clientAccessPlugins(params.accessPlugins ?? [], runId, ask) };
|
|
185
208
|
}
|
|
186
209
|
|
|
210
|
+
// src/work-bridge/client-work-source.client.ts
|
|
211
|
+
import { once } from "node:events";
|
|
212
|
+
function clientWorkSource(runId, ask, notify) {
|
|
213
|
+
const kept = [];
|
|
214
|
+
let ended = false;
|
|
215
|
+
const askClient = async () => {
|
|
216
|
+
const { item } = workNextResultSchema.parse(await ask("work.next", { runId }));
|
|
217
|
+
if (item === null) {
|
|
218
|
+
ended = true;
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
return item;
|
|
222
|
+
};
|
|
223
|
+
const keepLate = async (answer) => {
|
|
224
|
+
try {
|
|
225
|
+
const item = await answer;
|
|
226
|
+
if (item !== void 0) kept.push(item);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
next: async (options) => {
|
|
232
|
+
const waiting = kept.shift();
|
|
233
|
+
if (waiting !== void 0) return waiting;
|
|
234
|
+
if (ended) return;
|
|
235
|
+
const answer = askClient();
|
|
236
|
+
const signal = options?.signal;
|
|
237
|
+
if (signal === void 0) return await answer;
|
|
238
|
+
signal.throwIfAborted();
|
|
239
|
+
const settled = new AbortController();
|
|
240
|
+
const answered = (async () => {
|
|
241
|
+
const item = await answer;
|
|
242
|
+
return { item };
|
|
243
|
+
})();
|
|
244
|
+
const retired = (async () => {
|
|
245
|
+
try {
|
|
246
|
+
await once(signal, "abort", { signal: settled.signal });
|
|
247
|
+
return "retired";
|
|
248
|
+
} catch {
|
|
249
|
+
return "answered";
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
try {
|
|
253
|
+
const first = await Promise.race([answered, retired]);
|
|
254
|
+
if (first !== "retired" && first !== "answered") return first.item;
|
|
255
|
+
void keepLate(answer);
|
|
256
|
+
throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
|
|
257
|
+
} finally {
|
|
258
|
+
settled.abort();
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
done: (item, report, records) => {
|
|
262
|
+
notify("work.done", { runId, item, report, records: records.map((record) => ({ key: record.key, data: record.data })) });
|
|
263
|
+
},
|
|
264
|
+
failed: (item, report, outcome) => {
|
|
265
|
+
notify("work.failed", { runId, item, report, outcome });
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
187
270
|
// src/session/recipe-loading.use-case.ts
|
|
188
271
|
import { loadForRun } from "@opencraw/cli";
|
|
189
272
|
import { hookUses, RecipeBindingError, RecipeValidationError } from "@opencraw/core";
|
|
@@ -225,41 +308,54 @@ function streamingSink() {
|
|
|
225
308
|
function runSessions(notify, ask) {
|
|
226
309
|
const running = /* @__PURE__ */ new Map();
|
|
227
310
|
let next = 1;
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
311
|
+
const crawlerFor = async (runId, params) => {
|
|
312
|
+
const { access, accessPlugins } = await resolveSiteAccess(params, runId, ask);
|
|
313
|
+
return createCrawler({
|
|
314
|
+
sink: params.out === void 0 ? streamingSink() : fileSink2(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
|
|
315
|
+
hooks: clientHooks(params.hooks ?? [], runId, ask),
|
|
316
|
+
captchaSolvers: clientCaptchaSolvers(params.captchaSolvers ?? [], runId, ask),
|
|
317
|
+
access,
|
|
318
|
+
accessPlugins,
|
|
319
|
+
profilesDir: process.env.OPENCRAW_PROFILES,
|
|
320
|
+
parallel: params.parallel,
|
|
321
|
+
resume: params.resume,
|
|
322
|
+
debug: params.dryRun,
|
|
323
|
+
browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
|
|
324
|
+
onEvent: (event) => {
|
|
325
|
+
notify("event", { runId, event });
|
|
326
|
+
if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
const track = (runId, crawler, crawling) => {
|
|
331
|
+
running.set(runId, crawler);
|
|
332
|
+
void (async () => {
|
|
333
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
334
|
+
try {
|
|
335
|
+
notify("finished", { runId, report: await crawling() });
|
|
336
|
+
} catch (error) {
|
|
337
|
+
notify("finished", { runId, error: error instanceof Error ? error.message : String(error) });
|
|
338
|
+
} finally {
|
|
339
|
+
running.delete(runId);
|
|
340
|
+
await crawler.close();
|
|
341
|
+
}
|
|
342
|
+
})();
|
|
238
343
|
};
|
|
239
344
|
return {
|
|
240
345
|
start: async (params) => {
|
|
241
346
|
const loaded = await loadRecipes(params);
|
|
242
347
|
const set = params.dryRun === true ? new RecipeSet(loaded.output, loaded.inputs.map((input) => ({ ...input, limits: { ...input.limits, maxRecords: 1 } }))) : loaded;
|
|
243
348
|
const runId = `run-${next++}`;
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
debug: params.dryRun,
|
|
255
|
-
browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
|
|
256
|
-
onEvent: (event) => {
|
|
257
|
-
notify("event", { runId, event });
|
|
258
|
-
if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
running.set(runId, crawler);
|
|
262
|
-
void crawl(runId, crawler, set);
|
|
349
|
+
const crawler = await crawlerFor(runId, params);
|
|
350
|
+
track(runId, crawler, async () => await crawler.run(set));
|
|
351
|
+
return runId;
|
|
352
|
+
},
|
|
353
|
+
startWork: async (params) => {
|
|
354
|
+
const set = await loadRecipes(params);
|
|
355
|
+
const runId = `run-${next++}`;
|
|
356
|
+
const crawler = await crawlerFor(runId, params);
|
|
357
|
+
const windows = params.windows;
|
|
358
|
+
track(runId, crawler, async () => await crawler.work(set, clientWorkSource(runId, ask, notify), { windows }));
|
|
263
359
|
return runId;
|
|
264
360
|
},
|
|
265
361
|
cancel: async (runId) => {
|
|
@@ -400,7 +496,7 @@ function openLineChannel(input, output, handle) {
|
|
|
400
496
|
}
|
|
401
497
|
|
|
402
498
|
// src/server/serve-driver.use-case.ts
|
|
403
|
-
var METHODS = ["initialize", "validate", "run", "cancel", "probe", "diff", "export", "shutdown"];
|
|
499
|
+
var METHODS = ["initialize", "validate", "run", "work", "cancel", "probe", "diff", "export", "shutdown"];
|
|
404
500
|
var major = (version) => version.split(".", 1)[0];
|
|
405
501
|
function parsed(schema, params) {
|
|
406
502
|
const result = schema.safeParse(params ?? {});
|
|
@@ -423,6 +519,7 @@ async function serveDriver(input, output, identity) {
|
|
|
423
519
|
if (!initialized) throw new RpcFailure(RPC_ERRORS.server, "call initialize first");
|
|
424
520
|
if (method === "validate") return await validateRecipes(parsed(validateParamsSchema, params));
|
|
425
521
|
if (method === "run") return { runId: await sessions.start(parsed(runParamsSchema, params)) };
|
|
522
|
+
if (method === "work") return { runId: await sessions.startWork(parsed(workParamsSchema, params)) };
|
|
426
523
|
if (method === "cancel") return { cancelled: await sessions.cancel(parsed(cancelParamsSchema, params).runId) };
|
|
427
524
|
if (method === "probe") return await probeSite(parsed(probeParamsSchema, params), `probe-${++probes}`, channel.request);
|
|
428
525
|
if (method === "diff") return await diffRecordFiles(parsed(diffParamsSchema, params));
|
|
@@ -434,7 +531,7 @@ async function serveDriver(input, output, identity) {
|
|
|
434
531
|
return {};
|
|
435
532
|
});
|
|
436
533
|
const sessions = runSessions(channel.notify, channel.request);
|
|
437
|
-
await Promise.race([channel.closed,
|
|
534
|
+
await Promise.race([channel.closed, once2(stopping.signal, "abort")]);
|
|
438
535
|
await channel.drained();
|
|
439
536
|
await sessions.closeAll();
|
|
440
537
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AccessPlugin, CalloutTransport, CaptchaSolver, HookMap } from '@opencraw/core';
|
|
2
2
|
/** Sends one request to the client and waits for its answer: the channel's `request`. */
|
|
3
3
|
export type AskClient = (method: string, params: unknown) => Promise<unknown>;
|
|
4
|
+
/** Sends a notification to the client: the channel's `notify`. */
|
|
5
|
+
export type NotifyClient = (method: string, params: unknown) => void;
|
|
4
6
|
/**
|
|
5
7
|
* A transport whose handler is the client: each call is a `callout` request carrying core's own
|
|
6
8
|
* `CalloutRequest`, and the client answers with a `CalloutResponse`, so `pending` and idempotency keys
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { clientAccessPlugins, clientCaptchaSolvers, clientHooks, clientTransport } from './client-hooks.client.js';
|
|
2
|
-
export type { AskClient } from './client-hooks.client.js';
|
|
2
|
+
export type { AskClient, NotifyClient } from './client-hooks.client.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { serveDriver } from './server/index.js';
|
|
2
2
|
export type { DriverIdentity } from './server/index.js';
|
|
3
|
-
export { PROTOCOL_VERSION, MESSAGES, driverProtocolJsonSchema, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, } from './protocol/index.js';
|
|
4
|
-
export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, } from './protocol/index.js';
|
|
3
|
+
export { PROTOCOL_VERSION, MESSAGES, driverProtocolJsonSchema, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, workParamsSchema, workItemSchema, workNextParamsSchema, workNextResultSchema, workDoneParamsSchema, workFailedParamsSchema, } from './protocol/index.js';
|
|
4
|
+
export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, WorkParams, WorkItemMessage, WorkNextResult, WorkDoneParams, WorkFailedParams, } from './protocol/index.js';
|
|
5
5
|
export { openLineChannel, RPC_ERRORS, RpcFailure } from './transport/index.js';
|
|
6
6
|
export type { LineChannel, RequestHandler, RpcError, RpcMessage } from './transport/index.js';
|
|
7
7
|
export { runSessions, validateRecipes, streamingSink } from './session/index.js';
|
|
8
|
-
export type { RunSessions
|
|
8
|
+
export type { RunSessions } from './session/index.js';
|
|
9
9
|
export { clientHooks, clientCaptchaSolvers, clientAccessPlugins } from './callout-bridge/index.js';
|
|
10
|
+
export type { AskClient, NotifyClient } from './callout-bridge/index.js';
|
|
11
|
+
export { clientWorkSource } from './work-bridge/index.js';
|
|
10
12
|
export { probeSite } from './site-probe/index.js';
|
|
11
13
|
export { diffRecordFiles, exportRecordFiles } from './record-files/index.js';
|
|
12
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
* The driver protocol's version, separate from the package's: a client pins a major and the driver refuses
|
|
4
4
|
* another. Additions (methods, optional fields) raise the minor; anything a client could break on, the major.
|
|
5
5
|
*/
|
|
6
|
-
export declare const PROTOCOL_VERSION = "1.
|
|
6
|
+
export declare const PROTOCOL_VERSION = "1.2.0";
|
|
7
7
|
export declare const initializeParamsSchema: z.ZodObject<{
|
|
8
8
|
clientName: z.ZodString;
|
|
9
9
|
clientVersion: z.ZodString;
|
|
@@ -58,6 +58,31 @@ export type RunParams = z.infer<typeof runParamsSchema>;
|
|
|
58
58
|
export declare const runResultSchema: z.ZodObject<{
|
|
59
59
|
runId: z.ZodString;
|
|
60
60
|
}, z.core.$strict>;
|
|
61
|
+
export declare const workParamsSchema: z.ZodObject<{
|
|
62
|
+
accessConfig: z.ZodOptional<z.ZodString>;
|
|
63
|
+
access: z.ZodOptional<z.ZodString>;
|
|
64
|
+
accessPlugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
65
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
66
|
+
recipes: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
67
|
+
only: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
68
|
+
out: z.ZodOptional<z.ZodString>;
|
|
69
|
+
append: z.ZodOptional<z.ZodBoolean>;
|
|
70
|
+
browser: z.ZodOptional<z.ZodObject<{
|
|
71
|
+
headless: z.ZodOptional<z.ZodBoolean>;
|
|
72
|
+
executablePath: z.ZodOptional<z.ZodString>;
|
|
73
|
+
ignoreHTTPSErrors: z.ZodOptional<z.ZodBoolean>;
|
|
74
|
+
}, z.core.$strict>>;
|
|
75
|
+
hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
76
|
+
captchaSolvers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
77
|
+
windows: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
78
|
+
}, z.core.$strict>;
|
|
79
|
+
export type WorkParams = z.infer<typeof workParamsSchema>;
|
|
80
|
+
export declare const workItemSchema: z.ZodObject<{
|
|
81
|
+
id: z.ZodString;
|
|
82
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
83
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
export type WorkItemMessage = z.infer<typeof workItemSchema>;
|
|
61
86
|
export type RunResult = z.infer<typeof runResultSchema>;
|
|
62
87
|
export declare const cancelParamsSchema: z.ZodObject<{
|
|
63
88
|
runId: z.ZodString;
|
|
@@ -114,6 +139,48 @@ export declare const exportResultSchema: z.ZodObject<{
|
|
|
114
139
|
}, z.core.$strict>;
|
|
115
140
|
export type ExportResult = z.infer<typeof exportResultSchema>;
|
|
116
141
|
export declare const shutdownResultSchema: z.ZodObject<{}, z.core.$strict>;
|
|
142
|
+
/** A window wants its next item. The client may hold the answer until it has one; `null` ends the work. */
|
|
143
|
+
export declare const workNextParamsSchema: z.ZodObject<{
|
|
144
|
+
runId: z.ZodString;
|
|
145
|
+
}, z.core.$strict>;
|
|
146
|
+
export declare const workNextResultSchema: z.ZodObject<{
|
|
147
|
+
item: z.ZodNullable<z.ZodObject<{
|
|
148
|
+
id: z.ZodString;
|
|
149
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
150
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
151
|
+
}, z.core.$strict>>;
|
|
152
|
+
}, z.core.$strict>;
|
|
153
|
+
export type WorkNextResult = z.infer<typeof workNextResultSchema>;
|
|
154
|
+
/** An item succeeded: its report (`RecipeReport`) and its records. */
|
|
155
|
+
export declare const workDoneParamsSchema: z.ZodObject<{
|
|
156
|
+
runId: z.ZodString;
|
|
157
|
+
item: z.ZodObject<{
|
|
158
|
+
id: z.ZodString;
|
|
159
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
160
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
161
|
+
}, z.core.$strict>;
|
|
162
|
+
report: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
163
|
+
records: z.ZodArray<z.ZodObject<{
|
|
164
|
+
key: z.ZodNullable<z.ZodString>;
|
|
165
|
+
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
166
|
+
}, z.core.$strict>>;
|
|
167
|
+
}, z.core.$strict>;
|
|
168
|
+
export type WorkDoneParams = z.infer<typeof workDoneParamsSchema>;
|
|
169
|
+
/** An item failed (`failure`) or came out neither way (`neutral`, such as a captcha it could not pass). */
|
|
170
|
+
export declare const workFailedParamsSchema: z.ZodObject<{
|
|
171
|
+
runId: z.ZodString;
|
|
172
|
+
item: z.ZodObject<{
|
|
173
|
+
id: z.ZodString;
|
|
174
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
175
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
176
|
+
}, z.core.$strict>;
|
|
177
|
+
report: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
178
|
+
outcome: z.ZodEnum<{
|
|
179
|
+
failure: "failure";
|
|
180
|
+
neutral: "neutral";
|
|
181
|
+
}>;
|
|
182
|
+
}, z.core.$strict>;
|
|
183
|
+
export type WorkFailedParams = z.infer<typeof workFailedParamsSchema>;
|
|
117
184
|
/** Every crawl event, as core reports it (`CrawlEvent`); its shape is core's, versioned with core. */
|
|
118
185
|
export declare const eventParamsSchema: z.ZodObject<{
|
|
119
186
|
runId: z.ZodString;
|
|
@@ -226,6 +293,30 @@ export declare const MESSAGES: {
|
|
|
226
293
|
runId: z.ZodString;
|
|
227
294
|
}, z.core.$strict>;
|
|
228
295
|
};
|
|
296
|
+
readonly work: {
|
|
297
|
+
readonly direction: 'client-to-driver';
|
|
298
|
+
readonly params: z.ZodObject<{
|
|
299
|
+
accessConfig: z.ZodOptional<z.ZodString>;
|
|
300
|
+
access: z.ZodOptional<z.ZodString>;
|
|
301
|
+
accessPlugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
302
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
303
|
+
recipes: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
304
|
+
only: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
305
|
+
out: z.ZodOptional<z.ZodString>;
|
|
306
|
+
append: z.ZodOptional<z.ZodBoolean>;
|
|
307
|
+
browser: z.ZodOptional<z.ZodObject<{
|
|
308
|
+
headless: z.ZodOptional<z.ZodBoolean>;
|
|
309
|
+
executablePath: z.ZodOptional<z.ZodString>;
|
|
310
|
+
ignoreHTTPSErrors: z.ZodOptional<z.ZodBoolean>;
|
|
311
|
+
}, z.core.$strict>>;
|
|
312
|
+
hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
313
|
+
captchaSolvers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
314
|
+
windows: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
315
|
+
}, z.core.$strict>;
|
|
316
|
+
readonly result: z.ZodObject<{
|
|
317
|
+
runId: z.ZodString;
|
|
318
|
+
}, z.core.$strict>;
|
|
319
|
+
};
|
|
229
320
|
readonly cancel: {
|
|
230
321
|
readonly direction: 'client-to-driver';
|
|
231
322
|
readonly params: z.ZodObject<{
|
|
@@ -323,6 +414,53 @@ export declare const MESSAGES: {
|
|
|
323
414
|
retryAfterMs: z.ZodOptional<z.ZodNumber>;
|
|
324
415
|
}, z.core.$strict>], "status">;
|
|
325
416
|
};
|
|
417
|
+
readonly 'work.next': {
|
|
418
|
+
readonly direction: 'driver-to-client';
|
|
419
|
+
readonly params: z.ZodObject<{
|
|
420
|
+
runId: z.ZodString;
|
|
421
|
+
}, z.core.$strict>;
|
|
422
|
+
readonly result: z.ZodObject<{
|
|
423
|
+
item: z.ZodNullable<z.ZodObject<{
|
|
424
|
+
id: z.ZodString;
|
|
425
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
426
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
427
|
+
}, z.core.$strict>>;
|
|
428
|
+
}, z.core.$strict>;
|
|
429
|
+
};
|
|
430
|
+
readonly 'work.done': {
|
|
431
|
+
readonly direction: 'driver-to-client';
|
|
432
|
+
readonly params: z.ZodObject<{
|
|
433
|
+
runId: z.ZodString;
|
|
434
|
+
item: z.ZodObject<{
|
|
435
|
+
id: z.ZodString;
|
|
436
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
437
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
438
|
+
}, z.core.$strict>;
|
|
439
|
+
report: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
440
|
+
records: z.ZodArray<z.ZodObject<{
|
|
441
|
+
key: z.ZodNullable<z.ZodString>;
|
|
442
|
+
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
443
|
+
}, z.core.$strict>>;
|
|
444
|
+
}, z.core.$strict>;
|
|
445
|
+
readonly result: undefined;
|
|
446
|
+
};
|
|
447
|
+
readonly 'work.failed': {
|
|
448
|
+
readonly direction: 'driver-to-client';
|
|
449
|
+
readonly params: z.ZodObject<{
|
|
450
|
+
runId: z.ZodString;
|
|
451
|
+
item: z.ZodObject<{
|
|
452
|
+
id: z.ZodString;
|
|
453
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
454
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
455
|
+
}, z.core.$strict>;
|
|
456
|
+
report: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
457
|
+
outcome: z.ZodEnum<{
|
|
458
|
+
failure: "failure";
|
|
459
|
+
neutral: "neutral";
|
|
460
|
+
}>;
|
|
461
|
+
}, z.core.$strict>;
|
|
462
|
+
readonly result: undefined;
|
|
463
|
+
};
|
|
326
464
|
readonly event: {
|
|
327
465
|
readonly direction: 'driver-to-client';
|
|
328
466
|
readonly params: z.ZodObject<{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { PROTOCOL_VERSION, MESSAGES, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, shutdownResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, } from './driver-message.contract.js';
|
|
2
|
-
export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, } from './driver-message.contract.js';
|
|
1
|
+
export { PROTOCOL_VERSION, MESSAGES, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, shutdownResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, workParamsSchema, workItemSchema, workNextParamsSchema, workNextResultSchema, workDoneParamsSchema, workFailedParamsSchema, } from './driver-message.contract.js';
|
|
2
|
+
export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, WorkParams, WorkItemMessage, WorkNextResult, WorkDoneParams, WorkFailedParams, } from './driver-message.contract.js';
|
|
3
3
|
export { driverProtocolJsonSchema } from './driver-json-schema.mapper.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { runSessions } from './run-session.use-case.js';
|
|
2
|
-
export type {
|
|
2
|
+
export type { RunSessions } from './run-session.use-case.js';
|
|
3
3
|
export { validateRecipes, hooksOf, loadRecipes, sourceOf } from './recipe-loading.use-case.js';
|
|
4
4
|
export { streamingSink } from './streaming-sink.repository.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type { AskClient } from '../callout-bridge/index.js';
|
|
2
|
-
import type { RunParams } from '../protocol/index.js';
|
|
3
|
-
/** Sends a notification to the client. */
|
|
4
|
-
export type NotifyClient = (method: string, params: unknown) => void;
|
|
1
|
+
import type { AskClient, NotifyClient } from '../callout-bridge/index.js';
|
|
2
|
+
import type { RunParams, WorkParams } from '../protocol/index.js';
|
|
5
3
|
/** The runs a driver has going, by id, so `cancel` can reach them and `shutdown` close them all. */
|
|
6
4
|
export interface RunSessions {
|
|
7
5
|
start: (params: RunParams) => Promise<string>;
|
|
6
|
+
startWork: (params: WorkParams) => Promise<string>;
|
|
8
7
|
cancel: (runId: string) => Promise<boolean>;
|
|
9
8
|
closeAll: () => Promise<void>;
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
|
-
* Starts and tracks runs. `start`
|
|
13
|
-
* itself, with its problems),
|
|
14
|
-
* event goes to the client as an `event` notification, every record as a
|
|
15
|
-
* names a file, and the end as `finished` with the report. A hook the client
|
|
16
|
-
* a `callout` request, and so are the captcha solvers and access plugins it
|
|
17
|
-
*
|
|
11
|
+
* Starts and tracks runs and worker pools. `start` and `startWork` load and bind the recipes (a bad
|
|
12
|
+
* recipe fails the request itself, with its problems), answer with a run id at once, and crawl in the
|
|
13
|
+
* background: every crawl event goes to the client as an `event` notification, every record as a
|
|
14
|
+
* `record` one unless `out` names a file, and the end as `finished` with the report. A hook the client
|
|
15
|
+
* serves is asked back with a `callout` request, and so are the captcha solvers and access plugins it
|
|
16
|
+
* serves; a worker pool asks the client for its items (`work.next`). `cancel` closes the run's crawler,
|
|
17
|
+
* which stops its recipes; the run still ends with `finished`.
|
|
18
18
|
*
|
|
19
19
|
* @param notify - The channel's notify.
|
|
20
|
-
* @param ask - The channel's request, for callouts.
|
|
20
|
+
* @param ask - The channel's request, for callouts and work.
|
|
21
21
|
* @returns The sessions.
|
|
22
22
|
*/
|
|
23
23
|
export declare function runSessions(notify: NotifyClient, ask: AskClient): RunSessions;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WorkSource } from '@opencraw/core';
|
|
2
|
+
import type { AskClient, NotifyClient } from '../callout-bridge/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* A `WorkSource` whose items come from the client: each window that wants work sends a `work.next`
|
|
5
|
+
* request, which the client may hold until it has an item, and an answer of `null` ends the work. Each
|
|
6
|
+
* item's end goes back as `work.done` (with its records) or `work.failed`.
|
|
7
|
+
*
|
|
8
|
+
* A window that retires while its request is out (core aborts its `next`) still gets an answer later;
|
|
9
|
+
* that item is kept and handed to the next window that asks, so nothing the client sent is lost.
|
|
10
|
+
*
|
|
11
|
+
* @param runId - The work's run id, sent with every message.
|
|
12
|
+
* @param ask - The channel's request.
|
|
13
|
+
* @param notify - The channel's notify.
|
|
14
|
+
* @returns The source for `crawler.work`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function clientWorkSource(runId: string, ask: AskClient, notify: NotifyClient): WorkSource;
|
|
17
|
+
//# sourceMappingURL=client-work-source.client.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencraw/driver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "OpenCraw's driver: the engine over JSON-RPC on stdio, for clients in other languages",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"schemas": "node tools/emit-protocol-schema.mjs"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@opencraw/cli": "^0.1
|
|
44
|
-
"@opencraw/core": "^0.
|
|
43
|
+
"@opencraw/cli": "^0.0.1",
|
|
44
|
+
"@opencraw/core": "^0.0.1",
|
|
45
45
|
"zod": "^4.6.5"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://opencraw/schemas/driver-protocol.schema.json",
|
|
4
4
|
"title": "OpenCraw driver protocol",
|
|
5
5
|
"description": "JSON-RPC 2.0 over stdio, one message per line. Methods with a result are requests; the rest are notifications.",
|
|
6
|
-
"protocolVersion": "1.
|
|
6
|
+
"protocolVersion": "1.2.0",
|
|
7
7
|
"methods": {
|
|
8
8
|
"initialize": {
|
|
9
9
|
"direction": "client-to-driver",
|
|
@@ -32,6 +32,15 @@
|
|
|
32
32
|
"$ref": "#/$defs/run.result"
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
|
+
"work": {
|
|
36
|
+
"direction": "client-to-driver",
|
|
37
|
+
"params": {
|
|
38
|
+
"$ref": "#/$defs/work.params"
|
|
39
|
+
},
|
|
40
|
+
"result": {
|
|
41
|
+
"$ref": "#/$defs/work.result"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
35
44
|
"cancel": {
|
|
36
45
|
"direction": "client-to-driver",
|
|
37
46
|
"params": {
|
|
@@ -83,6 +92,27 @@
|
|
|
83
92
|
"$ref": "#/$defs/callout.result"
|
|
84
93
|
}
|
|
85
94
|
},
|
|
95
|
+
"work.next": {
|
|
96
|
+
"direction": "driver-to-client",
|
|
97
|
+
"params": {
|
|
98
|
+
"$ref": "#/$defs/work.next.params"
|
|
99
|
+
},
|
|
100
|
+
"result": {
|
|
101
|
+
"$ref": "#/$defs/work.next.result"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"work.done": {
|
|
105
|
+
"direction": "driver-to-client",
|
|
106
|
+
"params": {
|
|
107
|
+
"$ref": "#/$defs/work.done.params"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"work.failed": {
|
|
111
|
+
"direction": "driver-to-client",
|
|
112
|
+
"params": {
|
|
113
|
+
"$ref": "#/$defs/work.failed.params"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
86
116
|
"event": {
|
|
87
117
|
"direction": "driver-to-client",
|
|
88
118
|
"params": {
|
|
@@ -335,6 +365,111 @@
|
|
|
335
365
|
],
|
|
336
366
|
"additionalProperties": false
|
|
337
367
|
},
|
|
368
|
+
"work.params": {
|
|
369
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
370
|
+
"type": "object",
|
|
371
|
+
"properties": {
|
|
372
|
+
"paths": {
|
|
373
|
+
"minItems": 1,
|
|
374
|
+
"type": "array",
|
|
375
|
+
"items": {
|
|
376
|
+
"type": "string"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"recipes": {
|
|
380
|
+
"minItems": 1,
|
|
381
|
+
"type": "array",
|
|
382
|
+
"items": {
|
|
383
|
+
"type": "object",
|
|
384
|
+
"propertyNames": {
|
|
385
|
+
"type": "string"
|
|
386
|
+
},
|
|
387
|
+
"additionalProperties": {}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
"only": {
|
|
391
|
+
"type": "array",
|
|
392
|
+
"items": {
|
|
393
|
+
"type": "string"
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
"out": {
|
|
397
|
+
"type": "string"
|
|
398
|
+
},
|
|
399
|
+
"append": {
|
|
400
|
+
"type": "boolean"
|
|
401
|
+
},
|
|
402
|
+
"browser": {
|
|
403
|
+
"type": "object",
|
|
404
|
+
"properties": {
|
|
405
|
+
"headless": {
|
|
406
|
+
"type": "boolean"
|
|
407
|
+
},
|
|
408
|
+
"executablePath": {
|
|
409
|
+
"type": "string"
|
|
410
|
+
},
|
|
411
|
+
"ignoreHTTPSErrors": {
|
|
412
|
+
"type": "boolean"
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
"additionalProperties": false
|
|
416
|
+
},
|
|
417
|
+
"hooks": {
|
|
418
|
+
"type": "array",
|
|
419
|
+
"items": {
|
|
420
|
+
"type": "string"
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
"captchaSolvers": {
|
|
424
|
+
"type": "array",
|
|
425
|
+
"items": {
|
|
426
|
+
"type": "string"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"accessConfig": {
|
|
430
|
+
"type": "string"
|
|
431
|
+
},
|
|
432
|
+
"access": {
|
|
433
|
+
"type": "string"
|
|
434
|
+
},
|
|
435
|
+
"accessPlugins": {
|
|
436
|
+
"type": "array",
|
|
437
|
+
"items": {
|
|
438
|
+
"type": "string"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
"windows": {
|
|
442
|
+
"anyOf": [
|
|
443
|
+
{
|
|
444
|
+
"type": "integer",
|
|
445
|
+
"minimum": 1,
|
|
446
|
+
"maximum": 64
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"type": "object",
|
|
450
|
+
"propertyNames": {
|
|
451
|
+
"type": "string"
|
|
452
|
+
},
|
|
453
|
+
"additionalProperties": {}
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
"additionalProperties": false
|
|
459
|
+
},
|
|
460
|
+
"work.result": {
|
|
461
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
462
|
+
"type": "object",
|
|
463
|
+
"properties": {
|
|
464
|
+
"runId": {
|
|
465
|
+
"type": "string"
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
"required": [
|
|
469
|
+
"runId"
|
|
470
|
+
],
|
|
471
|
+
"additionalProperties": false
|
|
472
|
+
},
|
|
338
473
|
"cancel.params": {
|
|
339
474
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
340
475
|
"type": "object",
|
|
@@ -690,6 +825,205 @@
|
|
|
690
825
|
}
|
|
691
826
|
]
|
|
692
827
|
},
|
|
828
|
+
"work.next.params": {
|
|
829
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
830
|
+
"type": "object",
|
|
831
|
+
"properties": {
|
|
832
|
+
"runId": {
|
|
833
|
+
"type": "string"
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
"required": [
|
|
837
|
+
"runId"
|
|
838
|
+
],
|
|
839
|
+
"additionalProperties": false
|
|
840
|
+
},
|
|
841
|
+
"work.next.result": {
|
|
842
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
843
|
+
"type": "object",
|
|
844
|
+
"properties": {
|
|
845
|
+
"item": {
|
|
846
|
+
"anyOf": [
|
|
847
|
+
{
|
|
848
|
+
"type": "object",
|
|
849
|
+
"properties": {
|
|
850
|
+
"id": {
|
|
851
|
+
"type": "string",
|
|
852
|
+
"minLength": 1
|
|
853
|
+
},
|
|
854
|
+
"vars": {
|
|
855
|
+
"type": "object",
|
|
856
|
+
"propertyNames": {
|
|
857
|
+
"type": "string"
|
|
858
|
+
},
|
|
859
|
+
"additionalProperties": {
|
|
860
|
+
"type": [
|
|
861
|
+
"string",
|
|
862
|
+
"number",
|
|
863
|
+
"boolean"
|
|
864
|
+
]
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
"recipe": {
|
|
868
|
+
"type": "string"
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
"required": [
|
|
872
|
+
"id",
|
|
873
|
+
"vars"
|
|
874
|
+
],
|
|
875
|
+
"additionalProperties": false
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"type": "null"
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
"required": [
|
|
884
|
+
"item"
|
|
885
|
+
],
|
|
886
|
+
"additionalProperties": false
|
|
887
|
+
},
|
|
888
|
+
"work.done.params": {
|
|
889
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
890
|
+
"type": "object",
|
|
891
|
+
"properties": {
|
|
892
|
+
"runId": {
|
|
893
|
+
"type": "string"
|
|
894
|
+
},
|
|
895
|
+
"item": {
|
|
896
|
+
"type": "object",
|
|
897
|
+
"properties": {
|
|
898
|
+
"id": {
|
|
899
|
+
"type": "string",
|
|
900
|
+
"minLength": 1
|
|
901
|
+
},
|
|
902
|
+
"vars": {
|
|
903
|
+
"type": "object",
|
|
904
|
+
"propertyNames": {
|
|
905
|
+
"type": "string"
|
|
906
|
+
},
|
|
907
|
+
"additionalProperties": {
|
|
908
|
+
"type": [
|
|
909
|
+
"string",
|
|
910
|
+
"number",
|
|
911
|
+
"boolean"
|
|
912
|
+
]
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
"recipe": {
|
|
916
|
+
"type": "string"
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
"required": [
|
|
920
|
+
"id",
|
|
921
|
+
"vars"
|
|
922
|
+
],
|
|
923
|
+
"additionalProperties": false
|
|
924
|
+
},
|
|
925
|
+
"report": {
|
|
926
|
+
"type": "object",
|
|
927
|
+
"propertyNames": {
|
|
928
|
+
"type": "string"
|
|
929
|
+
},
|
|
930
|
+
"additionalProperties": {}
|
|
931
|
+
},
|
|
932
|
+
"records": {
|
|
933
|
+
"type": "array",
|
|
934
|
+
"items": {
|
|
935
|
+
"type": "object",
|
|
936
|
+
"properties": {
|
|
937
|
+
"key": {
|
|
938
|
+
"type": [
|
|
939
|
+
"string",
|
|
940
|
+
"null"
|
|
941
|
+
]
|
|
942
|
+
},
|
|
943
|
+
"data": {
|
|
944
|
+
"type": "object",
|
|
945
|
+
"propertyNames": {
|
|
946
|
+
"type": "string"
|
|
947
|
+
},
|
|
948
|
+
"additionalProperties": {}
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
"required": [
|
|
952
|
+
"key",
|
|
953
|
+
"data"
|
|
954
|
+
],
|
|
955
|
+
"additionalProperties": false
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
"required": [
|
|
960
|
+
"runId",
|
|
961
|
+
"item",
|
|
962
|
+
"report",
|
|
963
|
+
"records"
|
|
964
|
+
],
|
|
965
|
+
"additionalProperties": false
|
|
966
|
+
},
|
|
967
|
+
"work.failed.params": {
|
|
968
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
969
|
+
"type": "object",
|
|
970
|
+
"properties": {
|
|
971
|
+
"runId": {
|
|
972
|
+
"type": "string"
|
|
973
|
+
},
|
|
974
|
+
"item": {
|
|
975
|
+
"type": "object",
|
|
976
|
+
"properties": {
|
|
977
|
+
"id": {
|
|
978
|
+
"type": "string",
|
|
979
|
+
"minLength": 1
|
|
980
|
+
},
|
|
981
|
+
"vars": {
|
|
982
|
+
"type": "object",
|
|
983
|
+
"propertyNames": {
|
|
984
|
+
"type": "string"
|
|
985
|
+
},
|
|
986
|
+
"additionalProperties": {
|
|
987
|
+
"type": [
|
|
988
|
+
"string",
|
|
989
|
+
"number",
|
|
990
|
+
"boolean"
|
|
991
|
+
]
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
"recipe": {
|
|
995
|
+
"type": "string"
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
"required": [
|
|
999
|
+
"id",
|
|
1000
|
+
"vars"
|
|
1001
|
+
],
|
|
1002
|
+
"additionalProperties": false
|
|
1003
|
+
},
|
|
1004
|
+
"report": {
|
|
1005
|
+
"type": "object",
|
|
1006
|
+
"propertyNames": {
|
|
1007
|
+
"type": "string"
|
|
1008
|
+
},
|
|
1009
|
+
"additionalProperties": {}
|
|
1010
|
+
},
|
|
1011
|
+
"outcome": {
|
|
1012
|
+
"type": "string",
|
|
1013
|
+
"enum": [
|
|
1014
|
+
"failure",
|
|
1015
|
+
"neutral"
|
|
1016
|
+
]
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
"required": [
|
|
1020
|
+
"runId",
|
|
1021
|
+
"item",
|
|
1022
|
+
"report",
|
|
1023
|
+
"outcome"
|
|
1024
|
+
],
|
|
1025
|
+
"additionalProperties": false
|
|
1026
|
+
},
|
|
693
1027
|
"event.params": {
|
|
694
1028
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
695
1029
|
"type": "object",
|