@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
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @opencraw/azure-durable
|
|
2
|
+
|
|
3
|
+
[OpenCraw](../../README.md) over HTTP, on Azure Durable Functions. One deployed Function App holds the browser,
|
|
4
|
+
the captcha readers and the proxies; every crawler calls it with recipes instead of bundling Playwright itself.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install @opencraw/azure-durable @azure/functions durable-functions
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Two ways in:
|
|
11
|
+
|
|
12
|
+
- **`POST /crawl`**: run recipes once. Each input recipe runs in its own activity; the result comes back in the
|
|
13
|
+
Durable status response, with a link to Blob storage for the large ones.
|
|
14
|
+
- **`POST /jobs/{crawlId}/items`**: send one item at a time to a warm worker pool. The pool keeps its windows
|
|
15
|
+
between items (steps marked `keep` are skipped on a window that already did them), grows while the caller keeps
|
|
16
|
+
enough items in flight, and lets idle windows go. Thousands of per-item calls cost one page load per window,
|
|
17
|
+
not one per item.
|
|
18
|
+
|
|
19
|
+
## Register it
|
|
20
|
+
|
|
21
|
+
In the Function App's entry module (Functions v4 programming model), call `registerOpenCraw` once:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// src/index.ts
|
|
25
|
+
import { blobResults, memoryRecipes, registerOpenCraw } from '@opencraw/azure-durable'
|
|
26
|
+
import { tesseractReader } from '@opencraw/captcha-tesseract'
|
|
27
|
+
import report from '../recipes/report.json'
|
|
28
|
+
import row from '../recipes/row.output.json'
|
|
29
|
+
|
|
30
|
+
registerOpenCraw({
|
|
31
|
+
allowedHosts: ['*.example.gov'], // the only hosts recipes may reach
|
|
32
|
+
recipes: memoryRecipes([{ name: 'report', version: '3', recipes: [row, report] }]),
|
|
33
|
+
results: blobResults({ connectionString: process.env.AzureWebJobsStorage ?? '' }),
|
|
34
|
+
captchaSolvers: () => [tesseractReader({ charset: '1-9A-HJ-NP-Zabdefghnqrtu' })],
|
|
35
|
+
pools: { maxWindows: 8, idleTtlMs: 600_000 },
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Everything that is code or a secret stays in the host: hooks, captcha solvers, access profiles (credentials as
|
|
40
|
+
`{{env.NAME}}`), named recipes. Callers send data and names, never code or credentials.
|
|
41
|
+
|
|
42
|
+
| Option | What it does |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `allowedHosts` | The hosts recipes may reach (`example.com`, `*.example.com`, `host:port`, `*`), enforced on every request, redirect and web socket. A function `(caller) => hosts` decides per caller; a caller it returns nothing for gets 403. |
|
|
45
|
+
| `identify` | Who is calling, from the request: an Entra ID principal (`x-ms-client-principal-name`, set by App Service authentication), or a header your gateway sets. Trust only headers callers cannot set themselves. |
|
|
46
|
+
| `recipes` | Named, versioned recipe sets: `memoryRecipes([...])` (what the deployment ships), `blobRecipes({ connectionString, shipped })` (those plus versions published over MCP, kept in Blob storage), or your own `RecipeStore`. A draft is refused by production routes. |
|
|
47
|
+
| `mcp`, `canPromote` | The recipe-authoring MCP endpoint, `/mcp` (off by default; `true` or `{ sampleRecords, sampleMs }`), and who may promote a draft (default: nobody). |
|
|
48
|
+
| `results`, `inlineLimitBytes` | Where results larger than the limit (default 256 KiB) go: `blobResults({ connectionString })` returns read-only links that expire. Without a store, everything is inline. |
|
|
49
|
+
| `hooks`, `captchaSolvers`, `access`, `accessPlugins`, `browser` | As in `createCrawler`. `captchaSolvers` is a factory: each crawler gets its own set. |
|
|
50
|
+
| `pools` | `windows` (the default policy), `maxWindows` (the cap per pool, default 8), `idleTtlMs` (a pool with no item this long closes, default 10 minutes). |
|
|
51
|
+
| `authLevel`, `routePrefix` | Default `function` (a function key) and no prefix. |
|
|
52
|
+
|
|
53
|
+
## Call it
|
|
54
|
+
|
|
55
|
+
### Once
|
|
56
|
+
|
|
57
|
+
```http
|
|
58
|
+
POST /api/crawl
|
|
59
|
+
{ "output": { ...output recipe... }, "inputs": [ { ...input recipe... } ], "options": { "dedupe": "recipe" } }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
or `{ "recipe": { "name": "report", "version": "3" } }` for a stored set. Recipes that don't load return 400 with
|
|
63
|
+
every issue and its JSON path, before anything is queued. Otherwise the answer is Durable's 202 with its status
|
|
64
|
+
URLs. While it runs, `customStatus` is `{ recipes, recipesDone, records }`; when it is done, `output` is:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{ "recipes": [ { "recipeId": "report", "emitted": 120, "pages": 3, "durationMs": 5400 } ],
|
|
68
|
+
"records": [ { "key": "...", "data": { ... } } ],
|
|
69
|
+
"results": [ "https://<account>.blob.core.windows.net/opencraw-results/<instance>/0.jsonl?<sas>" ] }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### One item at a time
|
|
73
|
+
|
|
74
|
+
```http
|
|
75
|
+
POST /api/jobs/delhi-2026-01/items
|
|
76
|
+
{ "recipe": { "name": "report", "version": "3" },
|
|
77
|
+
"item": { "id": "DL-53-Bus", "vars": { "state": "DL", "rto": "53", "group": "Bus" } },
|
|
78
|
+
"jobSize": 1200,
|
|
79
|
+
"windows": { "min": 1, "max": 6, "grow": { "after": 5 }, "idle": { "afterMs": 60000 } } }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The first item for a crawl id opens its pool; `jobSize` and `windows` count only then. The Durable instance id is
|
|
83
|
+
the crawl id and item id, so the same item sent again while it runs returns the running instance's status: it
|
|
84
|
+
runs once. When done, `output` is:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{ "outcome": "success", "report": { ... }, "records": [ ... ],
|
|
88
|
+
"pool": { "windows": 4, "idle": 1, "queued": 0, "running": 3, "ended": { "success": 311, "failure": 2, "neutral": 5 } } }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- **Keep enough items in flight.** A pool can only keep as many windows busy as the caller keeps items in
|
|
92
|
+
flight. Send more while `pool.idle` is 0 and the pool is below its maximum.
|
|
93
|
+
- **`neutral` means send it again.** A captcha that beat the reader, or a browser that died, says nothing about
|
|
94
|
+
the item, which is not retried by the host: resubmitting is the caller's decision.
|
|
95
|
+
- **`refused`** means the pool would not take the item: the crawl id runs another recipe version, or the version
|
|
96
|
+
is a draft or missing. The starter already answers 409 or 404 in those cases when it can tell.
|
|
97
|
+
- `GET /api/jobs/{crawlId}` returns the pool's state; `DELETE` closes it once its queued items finish.
|
|
98
|
+
|
|
99
|
+
## Write recipes over MCP
|
|
100
|
+
|
|
101
|
+
With `mcp: true`, `/api/mcp` serves an MCP server (Streamable HTTP, stateless) for an agent writing recipes. It
|
|
102
|
+
runs where the recipes will run: same browser, same outbound IP, same proxies, hooks and captcha readers. So a
|
|
103
|
+
recipe that works while you write it works in production too. Add the URL as a connector in your MCP client; there
|
|
104
|
+
is nothing to install.
|
|
105
|
+
|
|
106
|
+
| Tool | What it does |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `probe` | Fetches a page from the host and reports where its data lives. Only the caller's allowed hosts. |
|
|
109
|
+
| `validate` | Loads and binds inline recipes: every issue with its JSON path. |
|
|
110
|
+
| `run` | A sample by default (`sampleRecords` per input recipe, within `sampleMs`), returned inline. With `full: true`, a complete crawl in the background. |
|
|
111
|
+
| `status` | A full run's state and result, for the caller who started it only. |
|
|
112
|
+
| `list_recipes`, `get_recipes` | The stored sets, drafts included: where to start a new version from. |
|
|
113
|
+
| `publish` | Saves recipes that load as a new version, as a **draft**. A version, once published, never changes. |
|
|
114
|
+
|
|
115
|
+
No tool takes a file path: recipes travel inline, and nothing on the host's disk can be read. Production (`/crawl`
|
|
116
|
+
by name, `/jobs`) refuses a draft until someone `canPromote` allows calls
|
|
117
|
+
`POST /api/recipes/{name}/{version}/promote`. So an agent publishes, and a person decides what runs.
|
|
118
|
+
|
|
119
|
+
The endpoint refuses to start without authentication: keep `authLevel: 'function'`, or put the app behind App
|
|
120
|
+
Service authentication and set `identify`.
|
|
121
|
+
|
|
122
|
+
## Deploy it
|
|
123
|
+
|
|
124
|
+
See [docs/recipes/azure-durable.md](../../docs/recipes/azure-durable.md): the Dockerfile, `host.json`, the plan,
|
|
125
|
+
and the rules a warm pool needs (one instance per singleton job, enough activity slots).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index.js";
|