litescrape-sdk 0.1.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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/cjs/batch.d.ts +12 -0
- package/dist/cjs/batch.d.ts.map +1 -0
- package/dist/cjs/batch.js +131 -0
- package/dist/cjs/batch.js.map +1 -0
- package/dist/cjs/cache.d.ts +21 -0
- package/dist/cjs/cache.d.ts.map +1 -0
- package/dist/cjs/cache.js +112 -0
- package/dist/cjs/cache.js.map +1 -0
- package/dist/cjs/client.d.ts +38 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/client.js +143 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/errors.d.ts +53 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +121 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +40 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/models.d.ts +1735 -0
- package/dist/cjs/models.d.ts.map +1 -0
- package/dist/cjs/models.js +389 -0
- package/dist/cjs/models.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/result.d.ts +23 -0
- package/dist/cjs/result.d.ts.map +1 -0
- package/dist/cjs/result.js +49 -0
- package/dist/cjs/result.js.map +1 -0
- package/dist/cjs/runtime.d.ts +53 -0
- package/dist/cjs/runtime.d.ts.map +1 -0
- package/dist/cjs/runtime.js +237 -0
- package/dist/cjs/runtime.js.map +1 -0
- package/dist/cjs/stores.d.ts +143 -0
- package/dist/cjs/stores.d.ts.map +1 -0
- package/dist/cjs/stores.js +194 -0
- package/dist/cjs/stores.js.map +1 -0
- package/dist/cjs/validation.d.ts +65 -0
- package/dist/cjs/validation.d.ts.map +1 -0
- package/dist/cjs/validation.js +122 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/cjs/version.d.ts +2 -0
- package/dist/cjs/version.d.ts.map +1 -0
- package/dist/cjs/version.js +5 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/esm/batch.d.ts +12 -0
- package/dist/esm/batch.d.ts.map +1 -0
- package/dist/esm/batch.js +128 -0
- package/dist/esm/batch.js.map +1 -0
- package/dist/esm/cache.d.ts +21 -0
- package/dist/esm/cache.d.ts.map +1 -0
- package/dist/esm/cache.js +106 -0
- package/dist/esm/cache.js.map +1 -0
- package/dist/esm/client.d.ts +38 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +138 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/errors.d.ts +53 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +106 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/models.d.ts +1735 -0
- package/dist/esm/models.d.ts.map +1 -0
- package/dist/esm/models.js +384 -0
- package/dist/esm/models.js.map +1 -0
- package/dist/esm/result.d.ts +23 -0
- package/dist/esm/result.d.ts.map +1 -0
- package/dist/esm/result.js +45 -0
- package/dist/esm/result.js.map +1 -0
- package/dist/esm/runtime.d.ts +53 -0
- package/dist/esm/runtime.d.ts.map +1 -0
- package/dist/esm/runtime.js +226 -0
- package/dist/esm/runtime.js.map +1 -0
- package/dist/esm/stores.d.ts +143 -0
- package/dist/esm/stores.d.ts.map +1 -0
- package/dist/esm/stores.js +190 -0
- package/dist/esm/stores.js.map +1 -0
- package/dist/esm/validation.d.ts +65 -0
- package/dist/esm/validation.d.ts.map +1 -0
- package/dist/esm/validation.js +109 -0
- package/dist/esm/validation.js.map +1 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/esm/version.d.ts.map +1 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/version.js.map +1 -0
- package/package.json +74 -0
- package/src/batch.ts +158 -0
- package/src/cache.ts +123 -0
- package/src/client.ts +199 -0
- package/src/errors.ts +120 -0
- package/src/index.ts +17 -0
- package/src/models.ts +570 -0
- package/src/package.json +3 -0
- package/src/result.ts +49 -0
- package/src/runtime.ts +255 -0
- package/src/stores.ts +206 -0
- package/src/validation.ts +140 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,UAAU,CAAC,oCAAqB,CAAC;AAC7E,eAAO,MAAM,IAAI,2HAA2E,CAAC;AAC7F,eAAO,MAAM,MAAM,8KAUS,CAAC;AAC7B,eAAO,MAAM,OAAO,sMAAgC,CAAC;AACrD,eAAO,MAAM,OAAO;;;;;;;;;;;;;qIAUkE,CAAC;AACvF,eAAO,MAAM,IAAI;;;6GAE0F,CAAC;AAC5G,eAAO,MAAM,aAAa;;;IAAoD,CAAC;AAC/E,eAAO,MAAM,cAAc,aAAoC,CAAC;AAChE,eAAO,MAAM,MAAM;;;;EAA0C,CAAC;AAE9D,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACtC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,aAAa,KAAK,IAAI,CAAC;AACtE,eAAO,MAAM,KAAK,YAAa,CAAC,CAAC,aAAa,WAAW,MAAM,SAAQ,MAAM,EAAE,SAC1B,CAAC;AACtD,eAAO,MAAM,GAAG,WAAY,MAAM,SAAS,MAAM,YAA0D,CAAC;AAC5G,eAAO,MAAM,UAAU,cACT,MAAM,EAAE,KAAG,IAGtB,CAAC;AACJ,eAAO,MAAM,UAAU,cACT,MAAM,EAAE,KAAG,IAItB,CAAC;AACJ,eAAO,MAAM,SAAS,cACR,MAAM,EAAE,KAAG,IAItB,CAAC;AACJ,eAAO,MAAM,MAAM,MACb,MAAM,KAAK,MAAM,KAAG,IAGvB,CAAC;AACJ,eAAO,MAAM,QAAQ,UACX,MAAM,cAAc,MAAM,KAAG,IAGpC,CAAC;AAEJ,kGAAkG;AAClG,qBAAa,aAAa,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;;IAClD,SAAiB,QAAQ,EAAE,CAAC,CAAC;IAC7B,SAAiB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGzC,YAAY,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,CAAC,CAAA;KAAE,EAAE,SAAS,CAAC,EAAE,MAAM,EAK7E;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMpC;IAED,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAErC;IAED,gGAAgG;IAChG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGjC;CACF;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM;IAC3D,KAAK,MAAM,EAAE,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzD,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,SAAS,CAAC,CAAC,WAAW,EACzE,QAAQ,EAAE,CAAC,EACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,KAAK,EACZ,KAAK,GAAE,IAAI,EAAO,GACjB,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAuB5E"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ValidationError } from "./errors.js";
|
|
3
|
+
export const optional = (schema) => schema.nullish();
|
|
4
|
+
export const text = z.union([z.string(), z.number().finite(), z.bigint()]).transform(String);
|
|
5
|
+
export const number = z
|
|
6
|
+
.union([
|
|
7
|
+
z.number(),
|
|
8
|
+
z
|
|
9
|
+
.string()
|
|
10
|
+
.trim()
|
|
11
|
+
.regex(/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/)
|
|
12
|
+
.transform(Number),
|
|
13
|
+
z.boolean().transform(Number),
|
|
14
|
+
])
|
|
15
|
+
.pipe(z.number().finite());
|
|
16
|
+
export const integer = number.pipe(z.number().int());
|
|
17
|
+
export const boolean = z
|
|
18
|
+
.union([
|
|
19
|
+
z.boolean(),
|
|
20
|
+
z.literal(0),
|
|
21
|
+
z.literal(1),
|
|
22
|
+
z
|
|
23
|
+
.string()
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.pipe(z.enum(["0", "1", "true", "false", "yes", "no", "on", "off", "t", "f", "y", "n"])),
|
|
26
|
+
])
|
|
27
|
+
.transform((value) => [true, 1, "1", "true", "yes", "on", "t", "y"].includes(value));
|
|
28
|
+
export const flag = z
|
|
29
|
+
.union([z.enum(["0", "1"]), z.literal(0), z.literal(1), z.boolean()])
|
|
30
|
+
.transform((value) => (value === true || value === 1 || value === "1" ? "1" : "0"));
|
|
31
|
+
export const strictBoolean = z.union([z.boolean(), z.enum(["true", "false"])]);
|
|
32
|
+
export const timeoutSeconds = z.number().finite().gt(0).max(90);
|
|
33
|
+
export const device = z.enum(["desktop", "tablet", "mobile"]);
|
|
34
|
+
export const issue = (context, message, path = []) => context.addIssue({ code: "custom", message, path });
|
|
35
|
+
export const has = (params, field) => params[field] !== null && params[field] !== undefined;
|
|
36
|
+
export const requireAny = (...fields) => (p, c) => {
|
|
37
|
+
if (!fields.some((f) => has(p, f)))
|
|
38
|
+
issue(c, `one of ${fields.join(", ")} is required`);
|
|
39
|
+
};
|
|
40
|
+
export const exactlyOne = (...fields) => (p, c) => {
|
|
41
|
+
if (fields.filter((f) => has(p, f)).length !== 1)
|
|
42
|
+
issue(c, `exactly one of ${fields.join(", ")} is required`);
|
|
43
|
+
};
|
|
44
|
+
export const atMostOne = (...fields) => (p, c) => {
|
|
45
|
+
const found = fields.filter((f) => has(p, f));
|
|
46
|
+
if (found.length > 1)
|
|
47
|
+
issue(c, `${found.join(" and ")} cannot be combined`);
|
|
48
|
+
};
|
|
49
|
+
export const paired = (a, b) => (p, c) => {
|
|
50
|
+
if (has(p, a) !== has(p, b))
|
|
51
|
+
issue(c, `${a} and ${b} must be given together`);
|
|
52
|
+
};
|
|
53
|
+
export const requires = (field, dependency) => (p, c) => {
|
|
54
|
+
if (has(p, field) && !has(p, dependency))
|
|
55
|
+
issue(c, `${field} requires ${dependency}`);
|
|
56
|
+
};
|
|
57
|
+
/** Validated, immutable request. Construct an endpoint class or pass a plain object to scrape. */
|
|
58
|
+
export class ScrapeRequest {
|
|
59
|
+
#path;
|
|
60
|
+
constructor(path, params, prototype) {
|
|
61
|
+
this.#path = path;
|
|
62
|
+
Object.assign(this, params);
|
|
63
|
+
if (prototype)
|
|
64
|
+
Object.setPrototypeOf(this, prototype);
|
|
65
|
+
Object.freeze(this);
|
|
66
|
+
}
|
|
67
|
+
get path() {
|
|
68
|
+
return this.#path;
|
|
69
|
+
}
|
|
70
|
+
queryParams() {
|
|
71
|
+
return Object.fromEntries(Object.entries(this)
|
|
72
|
+
.filter(([key, value]) => key !== "endpoint" && value != null)
|
|
73
|
+
.map(([key, value]) => [key, String(value)]));
|
|
74
|
+
}
|
|
75
|
+
query_params() {
|
|
76
|
+
return this.queryParams();
|
|
77
|
+
}
|
|
78
|
+
/** Copy a validated request with a default deadline without revalidating its cursor context. */
|
|
79
|
+
withTimeout(timeout) {
|
|
80
|
+
const checked = timeoutSeconds.parse(timeout);
|
|
81
|
+
return new ScrapeRequest(this.path, { ...this, timeout: checked }, Object.getPrototypeOf(this));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function defineRequest(endpoint, path, shape, rules = []) {
|
|
85
|
+
const schema = z.strictObject(shape).superRefine((params, ctx) => {
|
|
86
|
+
for (const rule of rules)
|
|
87
|
+
rule(params, ctx);
|
|
88
|
+
});
|
|
89
|
+
class Request extends ScrapeRequest {
|
|
90
|
+
static endpoint = endpoint;
|
|
91
|
+
static path = path;
|
|
92
|
+
static parameters = Object.freeze(Object.keys(shape));
|
|
93
|
+
constructor(params) {
|
|
94
|
+
const result = schema.safeParse(params);
|
|
95
|
+
if (!result.success)
|
|
96
|
+
throw new ValidationError([
|
|
97
|
+
[
|
|
98
|
+
0,
|
|
99
|
+
result.error.issues
|
|
100
|
+
.map((i) => `${i.path.join(".")}${i.path.length ? ": " : ""}${i.message}`)
|
|
101
|
+
.join("; "),
|
|
102
|
+
],
|
|
103
|
+
]);
|
|
104
|
+
super(path, { ...result.data, endpoint });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Request;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "litescrape-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for the Litescrape API: validated, concurrent, retrying calls and durable batches.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/esm/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/esm/index.d.ts",
|
|
13
|
+
"default": "./dist/esm/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/cjs/index.d.ts",
|
|
17
|
+
"default": "./dist/cjs/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22.13.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/build-cjs.mjs",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tests/tsconfig.json",
|
|
35
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
36
|
+
"test:package": "node scripts/smoke-package.mjs",
|
|
37
|
+
"lint": "prettier --check . && npm run typecheck",
|
|
38
|
+
"format": "prettier --write .",
|
|
39
|
+
"check": "npm run lint && npm test",
|
|
40
|
+
"prepare": "npm run build",
|
|
41
|
+
"prepack": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"author": "Litescraper <support@litescrape.com>",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"homepage": "https://litescrape.com",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/litescrape/litescrape-sdk-ts.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/litescrape/litescrape-sdk-ts/issues"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"litescrape",
|
|
55
|
+
"serp",
|
|
56
|
+
"google",
|
|
57
|
+
"maps",
|
|
58
|
+
"reviews",
|
|
59
|
+
"scraping",
|
|
60
|
+
"api",
|
|
61
|
+
"typescript"
|
|
62
|
+
],
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"zod": "^4.6.5"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/node": "^22.20.4",
|
|
71
|
+
"prettier": "^3.9.8",
|
|
72
|
+
"typescript": "^7.0.2"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/batch.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { APIError, TransportError, apiError, isObject } from "./errors.js";
|
|
2
|
+
import { JobCache, cacheKeys, newId, type CacheEntry } from "./cache.js";
|
|
3
|
+
import type { AnyRequest } from "./models.js";
|
|
4
|
+
import { Result } from "./result.js";
|
|
5
|
+
import {
|
|
6
|
+
Semaphore,
|
|
7
|
+
backoff,
|
|
8
|
+
each,
|
|
9
|
+
requestWithRetries,
|
|
10
|
+
sleep,
|
|
11
|
+
type Outcome,
|
|
12
|
+
type RuntimeOptions,
|
|
13
|
+
} from "./runtime.js";
|
|
14
|
+
|
|
15
|
+
interface BatchOptions extends RuntimeOptions {
|
|
16
|
+
concurrency?: number;
|
|
17
|
+
useCache: boolean;
|
|
18
|
+
cachePath?: string;
|
|
19
|
+
onResult: (result: Result) => void;
|
|
20
|
+
}
|
|
21
|
+
type Submission = { entry: CacheEntry; outcome: Outcome };
|
|
22
|
+
|
|
23
|
+
export async function runBatch(items: readonly AnyRequest[], options: BatchOptions): Promise<Result[]> {
|
|
24
|
+
options.signal?.throwIfAborted();
|
|
25
|
+
const cache = new JobCache(options.cachePath);
|
|
26
|
+
const keys = cacheKeys(items, options.apiKey, options.baseUrl);
|
|
27
|
+
const limit = options.concurrency ?? 32;
|
|
28
|
+
const semaphores = [new Semaphore(limit)];
|
|
29
|
+
const started = performance.now();
|
|
30
|
+
const batchKey = newId();
|
|
31
|
+
const indices = items.map((_, i) => i);
|
|
32
|
+
const results: Result[] = new Array(items.length);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const entries = cache.prepareMany(keys, options.useCache, batchKey, items.length);
|
|
36
|
+
const pollOnce = (jobId: string) =>
|
|
37
|
+
requestWithRetries(`/api/batch/jobs/${encodeURIComponent(jobId)}`, { ...options, semaphores });
|
|
38
|
+
const replace = (index: number) => cache.prepareMany([keys[index]!], false, batchKey, items.length)[0]!;
|
|
39
|
+
|
|
40
|
+
const submit = async (index: number, replaceExpired = false): Promise<Submission> => {
|
|
41
|
+
let entry = replaceExpired ? replace(index) : entries[index]!;
|
|
42
|
+
if (entry.job_id) {
|
|
43
|
+
const outcome = await pollOnce(entry.job_id);
|
|
44
|
+
// Only positive evidence of expiry/missing jobs permits a fresh reservation.
|
|
45
|
+
if (
|
|
46
|
+
(!outcome.error && outcome.data?.state !== "expired") ||
|
|
47
|
+
(outcome.error && ![404, 410].includes(outcome.statusCode ?? 0))
|
|
48
|
+
)
|
|
49
|
+
return { entry, outcome };
|
|
50
|
+
entry = replace(index);
|
|
51
|
+
}
|
|
52
|
+
const outcome = await requestWithRetries(items[index]!.path + "/async", {
|
|
53
|
+
...options,
|
|
54
|
+
semaphores,
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: items[index]!.queryParams(),
|
|
57
|
+
acceptedStatuses: [200, 202],
|
|
58
|
+
headers: {
|
|
59
|
+
"Idempotency-Key": entry.submission,
|
|
60
|
+
"X-Litescrape-Batch-ID": entry.batch_key,
|
|
61
|
+
"X-Litescrape-Batch-Size": String(entry.batch_size),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
if (!outcome.error) {
|
|
65
|
+
const job = outcome.data!;
|
|
66
|
+
if (typeof job.job_id !== "string" || !job.job_id)
|
|
67
|
+
outcome.error = new TransportError("Batch submission returned no job ID.", { retryable: true });
|
|
68
|
+
else {
|
|
69
|
+
entry.job_id = job.job_id;
|
|
70
|
+
cache.save(keys[index]!, entry.submission, job.job_id, job.expires_at);
|
|
71
|
+
if (job.state === "expired" && options.useCache && !replaceExpired) return submit(index, true);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { entry, outcome };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const submissions: Submission[] = new Array(items.length);
|
|
78
|
+
await each(indices, limit, async (index) => {
|
|
79
|
+
submissions[index] = await submit(index);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const resolve = (index: number, { entry, outcome }: Submission): Result | null => {
|
|
83
|
+
if (!outcome.error) {
|
|
84
|
+
const job = outcome.data!;
|
|
85
|
+
if (typeof job.job_id !== "string" || !job.job_id || job.job_id !== entry.job_id) {
|
|
86
|
+
outcome.error = new TransportError("Batch job returned an invalid job ID.", { retryable: false });
|
|
87
|
+
} else if (job.state === "succeeded" || job.state === "failed") {
|
|
88
|
+
const status = job.status_code;
|
|
89
|
+
const payload = job.response;
|
|
90
|
+
if (
|
|
91
|
+
typeof status !== "number" ||
|
|
92
|
+
!Number.isInteger(status) ||
|
|
93
|
+
status < 100 ||
|
|
94
|
+
status > 599 ||
|
|
95
|
+
!isObject(payload) ||
|
|
96
|
+
(job.state === "succeeded") !== (status === 200)
|
|
97
|
+
) {
|
|
98
|
+
outcome.error = new TransportError("Batch job returned an invalid response.", {
|
|
99
|
+
retryable: false,
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
cache.save(keys[index]!, entry.submission, job.job_id, job.expires_at);
|
|
103
|
+
return new Result(
|
|
104
|
+
index,
|
|
105
|
+
items[index]!,
|
|
106
|
+
{
|
|
107
|
+
data: status === 200 ? payload : null,
|
|
108
|
+
error: status === 200 ? null : apiError(status, payload),
|
|
109
|
+
statusCode: status,
|
|
110
|
+
requestId: job.job_id,
|
|
111
|
+
attempts: outcome.attempts,
|
|
112
|
+
},
|
|
113
|
+
(performance.now() - started) / 1000,
|
|
114
|
+
job.job_id,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
} else if (job.state === "expired") {
|
|
118
|
+
outcome.error = new APIError("The retained batch response expired.", {
|
|
119
|
+
statusCode: 410,
|
|
120
|
+
errorCode: "job_expired",
|
|
121
|
+
requestId: entry.job_id ?? "",
|
|
122
|
+
});
|
|
123
|
+
outcome.statusCode = 410;
|
|
124
|
+
} else if (job.state === "queued" || job.state === "running") return null;
|
|
125
|
+
else outcome.error = new TransportError("Batch job returned an unknown state.", { retryable: false });
|
|
126
|
+
}
|
|
127
|
+
return new Result(
|
|
128
|
+
index,
|
|
129
|
+
items[index]!,
|
|
130
|
+
{ ...outcome, data: null, requestId: entry.job_id ?? outcome.requestId },
|
|
131
|
+
(performance.now() - started) / 1000,
|
|
132
|
+
entry.job_id ?? "",
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
let pending = indices;
|
|
137
|
+
let polls = 0;
|
|
138
|
+
while (pending.length) {
|
|
139
|
+
await each(pending, limit, async (index) => {
|
|
140
|
+
let result = resolve(index, submissions[index]!);
|
|
141
|
+
if (!result) {
|
|
142
|
+
const entry = submissions[index]!.entry;
|
|
143
|
+
submissions[index] = { entry, outcome: await pollOnce(entry.job_id!) };
|
|
144
|
+
result = resolve(index, submissions[index]!);
|
|
145
|
+
}
|
|
146
|
+
if (result) {
|
|
147
|
+
results[index] = result;
|
|
148
|
+
options.onResult(result);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
pending = pending.filter((index) => !results[index]);
|
|
152
|
+
if (pending.length) await sleep(backoff(++polls), options.signal);
|
|
153
|
+
}
|
|
154
|
+
return results;
|
|
155
|
+
} finally {
|
|
156
|
+
cache.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import type { AnyRequest } from "./models.js";
|
|
7
|
+
|
|
8
|
+
export interface CacheEntry {
|
|
9
|
+
submission: string;
|
|
10
|
+
job_id: string | null;
|
|
11
|
+
batch_key: string;
|
|
12
|
+
batch_size: number;
|
|
13
|
+
}
|
|
14
|
+
export const newId = (): string => randomUUID().replaceAll("-", "");
|
|
15
|
+
const hash = (value: string) => createHash("sha256").update(value).digest("hex");
|
|
16
|
+
|
|
17
|
+
/** Fingerprints include the key, origin, all parameters, and duplicate occurrence. */
|
|
18
|
+
export function cacheKeys(items: readonly AnyRequest[], apiKey: string, baseUrl: string): string[] {
|
|
19
|
+
const identity = hash(apiKey);
|
|
20
|
+
const occurrences = new Map<string, number>();
|
|
21
|
+
return items.map((item) => {
|
|
22
|
+
const params = item.queryParams();
|
|
23
|
+
const sorted = Object.fromEntries(
|
|
24
|
+
Object.keys(params)
|
|
25
|
+
.sort()
|
|
26
|
+
.map((key) => [key, params[key]]),
|
|
27
|
+
);
|
|
28
|
+
const canonical = JSON.stringify([identity, baseUrl, item.path, sorted]);
|
|
29
|
+
const occurrence = occurrences.get(canonical) ?? 0;
|
|
30
|
+
occurrences.set(canonical, occurrence + 1);
|
|
31
|
+
return hash(`${canonical}:${occurrence}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** SQLite FULL commits persist the entire workload before its first POST. */
|
|
36
|
+
export class JobCache {
|
|
37
|
+
readonly path: string;
|
|
38
|
+
private readonly db: DatabaseSync;
|
|
39
|
+
|
|
40
|
+
constructor(path?: string) {
|
|
41
|
+
this.path = resolve(
|
|
42
|
+
path || process.env.LITESCRAPE_JOB_CACHE || join(homedir(), ".cache", "litescrape", "jobs-ts.sqlite3"),
|
|
43
|
+
);
|
|
44
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
45
|
+
this.db = new DatabaseSync(this.path);
|
|
46
|
+
try {
|
|
47
|
+
if (process.platform !== "win32") chmodSync(this.path, 0o600);
|
|
48
|
+
this.db.exec(`PRAGMA busy_timeout=30000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;
|
|
49
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
50
|
+
cache_key TEXT PRIMARY KEY, submission TEXT NOT NULL, job_id TEXT,
|
|
51
|
+
expires_at TEXT, updated_at REAL NOT NULL
|
|
52
|
+
);`);
|
|
53
|
+
this.transaction(() => {
|
|
54
|
+
const columns = this.db
|
|
55
|
+
.prepare("PRAGMA table_info(jobs)")
|
|
56
|
+
.all()
|
|
57
|
+
.map((row) => row.name);
|
|
58
|
+
if (!columns.includes("batch_key")) this.db.exec("ALTER TABLE jobs ADD COLUMN batch_key TEXT");
|
|
59
|
+
if (!columns.includes("batch_size")) this.db.exec("ALTER TABLE jobs ADD COLUMN batch_size INTEGER");
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.db.close();
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private transaction<T>(operation: () => T): T {
|
|
68
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
69
|
+
try {
|
|
70
|
+
const result = operation();
|
|
71
|
+
this.db.exec("COMMIT");
|
|
72
|
+
return result;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.db.exec("ROLLBACK");
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
prepareMany(keys: readonly string[], useCache: boolean, batchKey: string, batchSize: number): CacheEntry[] {
|
|
80
|
+
return this.transaction(() =>
|
|
81
|
+
keys.map((key) => {
|
|
82
|
+
const row = this.db
|
|
83
|
+
.prepare("SELECT submission,job_id,batch_key,batch_size FROM jobs WHERE cache_key=?")
|
|
84
|
+
.get(key);
|
|
85
|
+
if (useCache && row) {
|
|
86
|
+
if (!row.batch_key) {
|
|
87
|
+
row.batch_key = batchKey;
|
|
88
|
+
row.batch_size = batchSize;
|
|
89
|
+
this.db
|
|
90
|
+
.prepare("UPDATE jobs SET batch_key=?,batch_size=? WHERE cache_key=?")
|
|
91
|
+
.run(batchKey, batchSize, key);
|
|
92
|
+
}
|
|
93
|
+
return row as unknown as CacheEntry;
|
|
94
|
+
}
|
|
95
|
+
const entry: CacheEntry = {
|
|
96
|
+
submission: newId(),
|
|
97
|
+
job_id: null,
|
|
98
|
+
batch_key: batchKey,
|
|
99
|
+
batch_size: batchSize,
|
|
100
|
+
};
|
|
101
|
+
this.db
|
|
102
|
+
.prepare(
|
|
103
|
+
`INSERT INTO jobs(cache_key,submission,updated_at,batch_key,batch_size) VALUES (?,?,?,?,?)
|
|
104
|
+
ON CONFLICT(cache_key) DO UPDATE SET submission=excluded.submission,job_id=NULL,expires_at=NULL,
|
|
105
|
+
updated_at=excluded.updated_at,batch_key=excluded.batch_key,batch_size=excluded.batch_size`,
|
|
106
|
+
)
|
|
107
|
+
.run(key, entry.submission, Date.now() / 1000, batchKey, batchSize);
|
|
108
|
+
return entry;
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
save(key: string, submission: string, jobId: string, expiresAt?: unknown): void {
|
|
114
|
+
// A concurrent fresh run can replace a slot; its submission must not be overwritten.
|
|
115
|
+
this.db
|
|
116
|
+
.prepare("UPDATE jobs SET job_id=?,expires_at=?,updated_at=? WHERE cache_key=? AND submission=?")
|
|
117
|
+
.run(jobId, typeof expiresAt === "string" ? expiresAt : null, Date.now() / 1000, key, submission);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
close(): void {
|
|
121
|
+
this.db.close();
|
|
122
|
+
}
|
|
123
|
+
}
|