@opencraw/azure-durable 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +1451 -0
- package/dist/src/caller-access/caller-access.policy.d.ts +28 -0
- package/dist/src/caller-access/index.d.ts +3 -0
- package/dist/src/crawl-run/crawl-job.model.d.ts +40 -0
- package/dist/src/crawl-run/crawl-job.validator.d.ts +11 -0
- package/dist/src/crawl-run/crawl-orchestration.use-case.d.ts +12 -0
- package/dist/src/crawl-run/crawl-request.validator.d.ts +25 -0
- package/dist/src/crawl-run/index.d.ts +8 -0
- package/dist/src/crawl-run/run-recipe.use-case.d.ts +13 -0
- package/dist/src/crawl-run/start-crawl.handler.d.ts +18 -0
- package/dist/src/host-options/host-options.config.d.ts +85 -0
- package/dist/src/host-options/index.d.ts +3 -0
- package/dist/src/http-reply/http-reply.mapper.d.ts +24 -0
- package/dist/src/http-reply/index.d.ts +2 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/open-craw-host/host-json.validator.d.ts +11 -0
- package/dist/src/open-craw-host/index.d.ts +4 -0
- package/dist/src/open-craw-host/register-open-craw.use-case.d.ts +45 -0
- package/dist/src/recipe-authoring/authoring-server.use-case.d.ts +13 -0
- package/dist/src/recipe-authoring/authoring-tools.use-case.d.ts +96 -0
- package/dist/src/recipe-authoring/index.d.ts +5 -0
- package/dist/src/recipe-authoring/mcp-endpoint.handler.d.ts +18 -0
- package/dist/src/recipe-authoring/promote.handler.d.ts +13 -0
- package/dist/src/recipe-store/blob-recipe.store.d.ts +20 -0
- package/dist/src/recipe-store/find-recipes.use-case.d.ts +13 -0
- package/dist/src/recipe-store/index.d.ts +7 -0
- package/dist/src/recipe-store/memory-recipe.store.d.ts +12 -0
- package/dist/src/recipe-store/recipe-store.contract.d.ts +28 -0
- package/dist/src/recipe-store/recipe-store.error.d.ts +6 -0
- package/dist/src/result-store/blob-result.store.d.ts +18 -0
- package/dist/src/result-store/index.d.ts +6 -0
- package/dist/src/result-store/result-packing.policy.d.ts +22 -0
- package/dist/src/result-store/result-store.contract.d.ts +13 -0
- package/dist/src/worker-pools/index.d.ts +10 -0
- package/dist/src/worker-pools/item-job.validator.d.ts +10 -0
- package/dist/src/worker-pools/item-orchestration.use-case.d.ts +14 -0
- package/dist/src/worker-pools/item-request.validator.d.ts +35 -0
- package/dist/src/worker-pools/job.handler.d.ts +14 -0
- package/dist/src/worker-pools/pool-job.model.d.ts +43 -0
- package/dist/src/worker-pools/submit-item.handler.d.ts +20 -0
- package/dist/src/worker-pools/windows-cap.policy.d.ts +12 -0
- package/dist/src/worker-pools/worker-pools.store.d.ts +58 -0
- package/package.json +93 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { OutputRecord } from '@opencraw/core';
|
|
2
|
+
import type { ResultStore } from './result-store.contract.js';
|
|
3
|
+
/** Records returned in the Durable output, or where to read them when they were too large. */
|
|
4
|
+
export interface PackedRecords {
|
|
5
|
+
records?: OutputRecord[];
|
|
6
|
+
resultUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Returns records inline when small, else saves them and returns a link: an
|
|
10
|
+
* orchestration's history holds every activity output, so a large result
|
|
11
|
+
* belongs in storage, not in the task hub.
|
|
12
|
+
*
|
|
13
|
+
* @param records - The records.
|
|
14
|
+
* @param options - The store (without one, records are always inline), the limit, the name to save under.
|
|
15
|
+
* @returns The records or their link.
|
|
16
|
+
*/
|
|
17
|
+
export declare function packRecords(records: readonly OutputRecord[], options: {
|
|
18
|
+
store?: ResultStore;
|
|
19
|
+
inlineLimitBytes: number;
|
|
20
|
+
name: string;
|
|
21
|
+
}): Promise<PackedRecords>;
|
|
22
|
+
//# sourceMappingURL=result-packing.policy.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OutputRecord } from '@opencraw/core';
|
|
2
|
+
/** Where results too large to return inline go. */
|
|
3
|
+
export interface ResultStore {
|
|
4
|
+
/**
|
|
5
|
+
* Saves records as JSON Lines.
|
|
6
|
+
*
|
|
7
|
+
* @param name - A path-like name, unique per result: `<instance>/<recipe>.jsonl`.
|
|
8
|
+
* @param records - The records.
|
|
9
|
+
* @returns A URL the caller can read them from.
|
|
10
|
+
*/
|
|
11
|
+
save(name: string, records: readonly OutputRecord[]): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=result-store.contract.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { WorkerPools } from './worker-pools.store.js';
|
|
2
|
+
export { itemOrchestrator } from './item-orchestration.use-case.js';
|
|
3
|
+
export { submitItem } from './submit-item.handler.js';
|
|
4
|
+
export { jobRequest } from './job.handler.js';
|
|
5
|
+
export { capWindows } from './windows-cap.policy.js';
|
|
6
|
+
export { itemRequestSchema, crawlIdSchema } from './item-request.validator.js';
|
|
7
|
+
export type { ItemRequest } from './item-request.validator.js';
|
|
8
|
+
export type { ItemJob, ItemOutcome, PoolState } from './pool-job.model.js';
|
|
9
|
+
export { parseItemJob } from './item-job.validator.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ItemJob } from './pool-job.model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks a pooled item's orchestration input as it comes back out of the task hub.
|
|
4
|
+
*
|
|
5
|
+
* @param raw - The stored input.
|
|
6
|
+
* @returns The job.
|
|
7
|
+
* @throws ZodError when it is not one.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseItemJob(raw: unknown): ItemJob;
|
|
10
|
+
//# sourceMappingURL=item-job.validator.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TypedActivity } from '@mnci/az-durable';
|
|
2
|
+
import type { OrchestrationContext, Task } from 'durable-functions';
|
|
3
|
+
import type { ItemJob, ItemOutcome } from './pool-job.model.js';
|
|
4
|
+
/**
|
|
5
|
+
* A pooled item's orchestration: one activity, which hands the item to the
|
|
6
|
+
* warm pool and waits for its result. Durable adds what a plain HTTP call
|
|
7
|
+
* lacks: a status URL to poll past the HTTP time limit, and an instance id
|
|
8
|
+
* that refuses a duplicate while the item runs.
|
|
9
|
+
*
|
|
10
|
+
* @param runItem - The activity.
|
|
11
|
+
* @returns The orchestrator body.
|
|
12
|
+
*/
|
|
13
|
+
export declare function itemOrchestrator(runItem: TypedActivity<ItemJob, ItemOutcome>): (context: OrchestrationContext, job: ItemJob) => Generator<Task, ItemOutcome, unknown>;
|
|
14
|
+
//# sourceMappingURL=item-orchestration.use-case.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const crawlIdSchema: z.ZodString;
|
|
3
|
+
/** A `/jobs/{crawlId}/items` body. */
|
|
4
|
+
export declare const itemRequestSchema: z.ZodObject<{
|
|
5
|
+
recipe: z.ZodObject<{
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
version: z.ZodString;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
item: z.ZodObject<{
|
|
10
|
+
id: z.ZodString;
|
|
11
|
+
vars: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
12
|
+
recipe: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
jobSize: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
windows: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodObject<{
|
|
16
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
start: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
grow: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
after: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
}, z.core.$strict>>;
|
|
22
|
+
shrink: z.ZodOptional<z.ZodEnum<{
|
|
23
|
+
one: "one";
|
|
24
|
+
half: "half";
|
|
25
|
+
}>>;
|
|
26
|
+
restart: z.ZodOptional<z.ZodObject<{
|
|
27
|
+
after: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
}, z.core.$strict>>;
|
|
29
|
+
idle: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
afterMs: z.ZodNumber;
|
|
31
|
+
}, z.core.$strict>>;
|
|
32
|
+
}, z.core.$strict>]>>;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
export type ItemRequest = z.infer<typeof itemRequestSchema>;
|
|
35
|
+
//# sourceMappingURL=item-request.validator.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HttpRequest, HttpResponseInit } from '@azure/functions';
|
|
2
|
+
import type { HostSettings } from '../host-options/index.js';
|
|
3
|
+
import type { WorkerPools } from './worker-pools.store.js';
|
|
4
|
+
/**
|
|
5
|
+
* `GET /jobs/{crawlId}`: the pool's state. `DELETE /jobs/{crawlId}`: closes
|
|
6
|
+
* it; queued and running items finish first.
|
|
7
|
+
*
|
|
8
|
+
* @param request - The request.
|
|
9
|
+
* @param settings - The host settings.
|
|
10
|
+
* @param pools - This process's pools.
|
|
11
|
+
* @returns The state, 202 when closing, 404 when there is no pool.
|
|
12
|
+
*/
|
|
13
|
+
export declare function jobRequest(request: HttpRequest, settings: HostSettings, pools: WorkerPools): Promise<HttpResponseInit>;
|
|
14
|
+
//# sourceMappingURL=job.handler.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { OutputRecord, RecipeReport, WindowsPolicy, WorkItem, WorkOutcome } from '@opencraw/core';
|
|
2
|
+
/** What a pooled item's orchestration runs: written by the starter, never by the caller. */
|
|
3
|
+
export interface ItemJob {
|
|
4
|
+
/** The caller and crawl id: one pool per key. */
|
|
5
|
+
key: string;
|
|
6
|
+
crawlId: string;
|
|
7
|
+
allowedHosts: string[];
|
|
8
|
+
recipe: {
|
|
9
|
+
name: string;
|
|
10
|
+
version: string;
|
|
11
|
+
};
|
|
12
|
+
item: WorkItem;
|
|
13
|
+
jobSize?: number;
|
|
14
|
+
windows?: WindowsPolicy | number;
|
|
15
|
+
/** Where the item's records go when too large to return. */
|
|
16
|
+
resultName: string;
|
|
17
|
+
}
|
|
18
|
+
/** A pool as a caller sees it. */
|
|
19
|
+
export interface PoolState {
|
|
20
|
+
crawlId: string;
|
|
21
|
+
recipe: {
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
};
|
|
25
|
+
/** The windows the pool aims for. */
|
|
26
|
+
windows: number;
|
|
27
|
+
/** Windows waiting for an item: while there are any, the caller could send more. */
|
|
28
|
+
idle: number;
|
|
29
|
+
queued: number;
|
|
30
|
+
running: number;
|
|
31
|
+
ended: Record<WorkOutcome, number>;
|
|
32
|
+
closing: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** How a pooled item ended. `refused`: the pool would not take it (another recipe version, a missing or draft recipe set). */
|
|
35
|
+
export interface ItemOutcome {
|
|
36
|
+
outcome: WorkOutcome | 'refused';
|
|
37
|
+
report?: RecipeReport;
|
|
38
|
+
records?: OutputRecord[];
|
|
39
|
+
resultUrl?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
pool?: PoolState;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=pool-job.model.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HttpRequest, HttpResponse, HttpResponseInit } from '@azure/functions';
|
|
2
|
+
import type { TypedOrchestration } from '@mnci/az-durable';
|
|
3
|
+
import type { DurableClient } from 'durable-functions';
|
|
4
|
+
import type { HostSettings } from '../host-options/index.js';
|
|
5
|
+
import type { ItemJob, ItemOutcome } from './pool-job.model.js';
|
|
6
|
+
import type { WorkerPools } from './worker-pools.store.js';
|
|
7
|
+
/**
|
|
8
|
+
* `POST /jobs/{crawlId}/items`: one item for the caller's warm pool. The
|
|
9
|
+
* Durable instance id is the caller, crawl id and item id, so the same item
|
|
10
|
+
* sent again while it runs returns the running instance's status.
|
|
11
|
+
*
|
|
12
|
+
* @param request - The request.
|
|
13
|
+
* @param client - The Durable client.
|
|
14
|
+
* @param settings - The host settings.
|
|
15
|
+
* @param pools - This process's pools.
|
|
16
|
+
* @param orchestration - The item orchestration.
|
|
17
|
+
* @returns 202 with the Durable status URLs, or the problem.
|
|
18
|
+
*/
|
|
19
|
+
export declare function submitItem(request: HttpRequest, client: DurableClient, settings: HostSettings, pools: WorkerPools, orchestration: TypedOrchestration<ItemJob, ItemOutcome>): Promise<HttpResponse | HttpResponseInit>;
|
|
20
|
+
//# sourceMappingURL=submit-item.handler.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WindowsPolicy } from '@opencraw/core';
|
|
2
|
+
/**
|
|
3
|
+
* A job's windows, within the host's limit and never more than the job has
|
|
4
|
+
* items: twelve windows for three items would only sit idle.
|
|
5
|
+
*
|
|
6
|
+
* @param policy - What the job asked for.
|
|
7
|
+
* @param maxWindows - The host's limit.
|
|
8
|
+
* @param jobSize - About how many items the job has.
|
|
9
|
+
* @returns The policy, capped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function capWindows(policy: WindowsPolicy | number, maxWindows: number, jobSize?: number): WindowsPolicy;
|
|
12
|
+
//# sourceMappingURL=windows-cap.policy.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { HostSettings } from '../host-options/index.js';
|
|
2
|
+
import type { ItemJob, ItemOutcome, PoolState } from './pool-job.model.js';
|
|
3
|
+
/**
|
|
4
|
+
* The warm worker pools of this process, one per caller and crawl id. A pool
|
|
5
|
+
* is created by its first item, runs `crawler.work` on an inbox, and closes
|
|
6
|
+
* when its job size is reached, when it has had no item for `idleTtlMs`, or
|
|
7
|
+
* when asked. Pools live in this process's memory: a Function App scaled to
|
|
8
|
+
* several instances has one pool per instance for the same crawl id.
|
|
9
|
+
*/
|
|
10
|
+
export declare class WorkerPools {
|
|
11
|
+
private readonly settings;
|
|
12
|
+
private readonly now;
|
|
13
|
+
private readonly pools;
|
|
14
|
+
/** Each pool's recipe version, known from the moment it starts opening. */
|
|
15
|
+
private readonly versions;
|
|
16
|
+
private readonly live;
|
|
17
|
+
private sweeper;
|
|
18
|
+
constructor(settings: HostSettings, now?: () => number);
|
|
19
|
+
private open;
|
|
20
|
+
private create;
|
|
21
|
+
/** Waits for a pool to run dry, then closes its browser and forgets it; a pool that failed rejects what it still held. */
|
|
22
|
+
private work;
|
|
23
|
+
private forget;
|
|
24
|
+
/** Starts looking for pools that have had no item for `idleTtlMs`, while any is open. */
|
|
25
|
+
private sweep;
|
|
26
|
+
/**
|
|
27
|
+
* The recipe version a pool runs, if one is open under this key.
|
|
28
|
+
*
|
|
29
|
+
* @param key - The caller and crawl id.
|
|
30
|
+
* @returns The version.
|
|
31
|
+
*/
|
|
32
|
+
versionOf(key: string): {
|
|
33
|
+
name: string;
|
|
34
|
+
version: string;
|
|
35
|
+
} | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* @param key - The caller and crawl id.
|
|
38
|
+
* @returns The pool as a caller sees it, if one is open.
|
|
39
|
+
*/
|
|
40
|
+
state(key: string): PoolState | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Runs an item on its job's pool, opening the pool when it is the first.
|
|
43
|
+
*
|
|
44
|
+
* @param job - The item and its job.
|
|
45
|
+
* @returns How it ended, with the pool's state.
|
|
46
|
+
*/
|
|
47
|
+
run(job: ItemJob): Promise<ItemOutcome>;
|
|
48
|
+
/**
|
|
49
|
+
* Closes a pool: queued and running items finish, then its browser closes.
|
|
50
|
+
*
|
|
51
|
+
* @param key - The caller and crawl id.
|
|
52
|
+
* @returns Whether there was a pool to close.
|
|
53
|
+
*/
|
|
54
|
+
close(key: string): Promise<boolean>;
|
|
55
|
+
/** Closes every pool, as the host shuts down. */
|
|
56
|
+
closeAll(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=worker-pools.store.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opencraw/azure-durable",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.esm.js",
|
|
6
|
+
"module": "./dist/index.esm.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/src/index.d.ts",
|
|
12
|
+
"import": "./dist/index.esm.js",
|
|
13
|
+
"default": "./dist/index.esm.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"!**/*.tsbuildinfo",
|
|
19
|
+
"!**/*.d.ts.map",
|
|
20
|
+
"!**/*.js.map"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@azure/functions": ">=4.0.0 <5",
|
|
24
|
+
"@azure/storage-blob": "^12.34.0",
|
|
25
|
+
"@mnci/az-durable": "^0.1.12",
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
27
|
+
"@opencraw/cli": "^0.1.9",
|
|
28
|
+
"@opencraw/core": "^0.1.9",
|
|
29
|
+
"@opencraw/mcp": "^0.1.9",
|
|
30
|
+
"durable-functions": ">=3.0.0 <4",
|
|
31
|
+
"zod": "^4.6.5"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@azure/functions": ">=4.0.0 <5",
|
|
35
|
+
"durable-functions": ">=3.0.0 <4"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"description": "OpenCraw over HTTP on Azure Durable Functions: run recipes with one call, or feed warm worker pools one item at a time.",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/russoedu/open.craw.git",
|
|
45
|
+
"directory": "packages/azure-durable"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"crawler",
|
|
49
|
+
"scraper",
|
|
50
|
+
"azure-functions",
|
|
51
|
+
"durable-functions",
|
|
52
|
+
"recipe"
|
|
53
|
+
],
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22"
|
|
56
|
+
},
|
|
57
|
+
"sideEffects": false,
|
|
58
|
+
"nx": {
|
|
59
|
+
"targets": {
|
|
60
|
+
"typecheck": {
|
|
61
|
+
"dependsOn": [
|
|
62
|
+
"^build"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"lint": {
|
|
66
|
+
"dependsOn": [
|
|
67
|
+
"^build"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"e2e": {
|
|
71
|
+
"executor": "nx:run-commands",
|
|
72
|
+
"dependsOn": [
|
|
73
|
+
"build",
|
|
74
|
+
{
|
|
75
|
+
"projects": [
|
|
76
|
+
"@opencraw/core"
|
|
77
|
+
],
|
|
78
|
+
"target": "build"
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"inputs": [
|
|
82
|
+
"default",
|
|
83
|
+
"^production",
|
|
84
|
+
"{workspaceRoot}/packages/core/e2e/**"
|
|
85
|
+
],
|
|
86
|
+
"options": {
|
|
87
|
+
"command": "tsc -p tsconfig.e2e.json && jest --config jest.e2e.config.cts",
|
|
88
|
+
"cwd": "packages/azure-durable"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|