@scrapyio/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.
Files changed (62) hide show
  1. package/README.md +140 -0
  2. package/dist/client.d.ts +25 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +65 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/errors.d.ts +21 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +93 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/execution.d.ts +15 -0
  11. package/dist/execution.d.ts.map +1 -0
  12. package/dist/execution.js +117 -0
  13. package/dist/execution.js.map +1 -0
  14. package/dist/generated/client.d.ts +15 -0
  15. package/dist/generated/client.d.ts.map +1 -0
  16. package/dist/generated/client.js +15 -0
  17. package/dist/generated/client.js.map +1 -0
  18. package/dist/generated/schema.d.ts +762 -0
  19. package/dist/generated/schema.d.ts.map +1 -0
  20. package/dist/generated/schema.js +6 -0
  21. package/dist/generated/schema.js.map +1 -0
  22. package/dist/http.d.ts +15 -0
  23. package/dist/http.d.ts.map +1 -0
  24. package/dist/http.js +69 -0
  25. package/dist/http.js.map +1 -0
  26. package/dist/index.d.ts +13 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +6 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/pagination.d.ts +20 -0
  31. package/dist/pagination.d.ts.map +1 -0
  32. package/dist/pagination.js +13 -0
  33. package/dist/pagination.js.map +1 -0
  34. package/dist/platform.d.ts +9 -0
  35. package/dist/platform.d.ts.map +1 -0
  36. package/dist/platform.js +28 -0
  37. package/dist/platform.js.map +1 -0
  38. package/dist/run.d.ts +32 -0
  39. package/dist/run.d.ts.map +1 -0
  40. package/dist/run.js +73 -0
  41. package/dist/run.js.map +1 -0
  42. package/dist/runs.d.ts +19 -0
  43. package/dist/runs.d.ts.map +1 -0
  44. package/dist/runs.js +33 -0
  45. package/dist/runs.js.map +1 -0
  46. package/dist/schedules.d.ts +22 -0
  47. package/dist/schedules.d.ts.map +1 -0
  48. package/dist/schedules.js +62 -0
  49. package/dist/schedules.js.map +1 -0
  50. package/dist/tool.d.ts +28 -0
  51. package/dist/tool.d.ts.map +1 -0
  52. package/dist/tool.js +49 -0
  53. package/dist/tool.js.map +1 -0
  54. package/dist/tools.d.ts +21 -0
  55. package/dist/tools.d.ts.map +1 -0
  56. package/dist/tools.js +35 -0
  57. package/dist/tools.js.map +1 -0
  58. package/dist/types.d.ts +27 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +11 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # @scrapyio/sdk
2
+
3
+ Official TypeScript / JavaScript SDK for [Scrapy.io](https://scrapy.io).
4
+
5
+ **Node / server-first.** Keep API keys on the server — never ship them in browser apps.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @scrapyio/sdk
11
+ ```
12
+
13
+ (Until publish: `npm install ./scrapyio-sdk-0.1.0.tgz` from a local pack.)
14
+
15
+ ## Authentication
16
+
17
+ ```ts
18
+ import { ScrapyIO } from '@scrapyio/sdk';
19
+
20
+ const client = new ScrapyIO({
21
+ apiKey: process.env.SCRAPYIO_API_KEY!, // or SCRAPY_API_KEY
22
+ });
23
+ ```
24
+
25
+ The SDK always sends:
26
+
27
+ ```http
28
+ Authorization: Bearer <apiKey>
29
+ ```
30
+
31
+ Missing/blank keys throw `ScrapyIOError` immediately. Invalid keys produce `type: "unauthorized"` (HTTP 401).
32
+
33
+ ## First request
34
+
35
+ ```ts
36
+ const tools = await client.tools.list({ q: 'instagram', limit: 20 });
37
+ // { items, total, offset, limit }
38
+
39
+ const tool = await client.tools.get('datadoping', 'instagram-profile-scraper');
40
+ // or:
41
+ const same = await client.tool('datadoping/instagram-profile-scraper').get();
42
+
43
+ console.log(tool.inputSchema, tool.execution.apiUrl);
44
+ ```
45
+
46
+ ## Run a scraper
47
+
48
+ ### Sync (`/v1/api`) — one primary input
49
+
50
+ ```ts
51
+ const result = await client.tool('datadoping/instagram-profile-scraper').call({
52
+ username: 'nasa',
53
+ });
54
+ ```
55
+
56
+ ### Async (`/v1/scraper`) — batch + poll
57
+
58
+ ```ts
59
+ const run = await client.tool('datadoping/instagram-profile-scraper').start({
60
+ usernames: ['nasa'],
61
+ });
62
+
63
+ const finished = await client.run(run.id).wait({
64
+ pollIntervalMs: 3000,
65
+ timeoutMs: 15 * 60_000,
66
+ });
67
+
68
+ const page = await client.run(run.id).listItems({ offset: 0, limit: 100 });
69
+ // { items, total, offset, limit } — rows are Record<string, unknown>
70
+ ```
71
+
72
+ Public run statuses: `queued` | `running` | `succeeded` | `partial` | `failed` | `cancelled`.
73
+
74
+ ## Schedules
75
+
76
+ ```ts
77
+ const schedule = await client.schedules.create(
78
+ {
79
+ publisher: 'datadoping',
80
+ slug: 'instagram-profile-scraper',
81
+ runName: 'nightly-ig',
82
+ timezone: 'UTC',
83
+ frequency: 'one-time',
84
+ date: '2026-09-20',
85
+ time: '23:50',
86
+ inputs: ['nasa'],
87
+ },
88
+ { idempotencyKey: 'nightly-ig-v1' }
89
+ );
90
+
91
+ // PATCH: exactly one of isActive | notificationsEnabled
92
+ await client.schedules.update(schedule.id, { isActive: false });
93
+ await client.schedules.delete(schedule.id);
94
+ ```
95
+
96
+ ## Errors
97
+
98
+ ```ts
99
+ import { ScrapyIO, ScrapyIOError } from '@scrapyio/sdk';
100
+
101
+ try {
102
+ await client.tools.get('nope', 'missing');
103
+ } catch (err) {
104
+ if (err instanceof ScrapyIOError) {
105
+ // type, status, message, docUrl
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Mental model
111
+
112
+ ```text
113
+ client
114
+ ├── tools.list / tools.get
115
+ ├── tool("publisher/slug").get / .call / .start
116
+ ├── runs.list / runs.get
117
+ ├── run(id).get / .wait / .listItems
118
+ └── schedules.list / get / create / update / delete
119
+ ```
120
+
121
+ ## Development / verification
122
+
123
+ ```bash
124
+ cd sdk/js
125
+ npm install
126
+ npm run generate
127
+ npm run check # openapi lint + generate + typecheck + unit tests
128
+ npm run build
129
+ npm pack --dry-run
130
+
131
+ # Real API (requires sdk/js/.env — never commit)
132
+ cd ../backend && npx tsx ../sdk/js/scripts/bootstrap-integration-env.ts
133
+ cd ../sdk/js && npm run test:integration
134
+ ```
135
+
136
+ ## Notes
137
+
138
+ - GET requests may retry on transient 5xx/429; execution `POST`s and schedule creates are **not** auto-retried (use `idempotencyKey` for schedule creates).
139
+ - `listItems()` is JSON-only in v0.1.
140
+ - Async run `failed` can still return dataset rows (e.g. per-item errors); that is Platform behavior, not an SDK envelope bug.
@@ -0,0 +1,25 @@
1
+ import { ToolHandle } from './tool.js';
2
+ import { ToolsResource } from './tools.js';
3
+ import { RunsResource } from './runs.js';
4
+ import { RunHandle } from './run.js';
5
+ import { SchedulesResource } from './schedules.js';
6
+ import type { ScrapyIOOptions } from './types.js';
7
+ /**
8
+ * Official Scrapy.io TypeScript SDK.
9
+ *
10
+ * API keys must stay server-side. Do not embed keys in browser apps.
11
+ */
12
+ export declare class ScrapyIO {
13
+ readonly tools: ToolsResource;
14
+ readonly runs: RunsResource;
15
+ readonly schedules: SchedulesResource;
16
+ private readonly apiKey;
17
+ private readonly platform;
18
+ private readonly fetchImpl;
19
+ constructor(options: ScrapyIOOptions);
20
+ /** Lightweight tool handle (`publisher/slug`). Does not hit the network. */
21
+ tool(ref: string): ToolHandle;
22
+ /** Lightweight run handle. Does not hit the network. */
23
+ run(runId: string): RunHandle;
24
+ }
25
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD;;;;GAIG;AACH,qBAAa,QAAQ;IACnB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IAEtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;gBAExC,OAAO,EAAE,eAAe;IAuCpC,4EAA4E;IAC5E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU;IAI7B,wDAAwD;IACxD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;CAG9B"}
package/dist/client.js ADDED
@@ -0,0 +1,65 @@
1
+ import { ScrapyIOError } from './errors.js';
2
+ import { createPlatformClient } from './generated/client.js';
3
+ import { createRetryFetch } from './http.js';
4
+ import { ToolHandle } from './tool.js';
5
+ import { ToolsResource } from './tools.js';
6
+ import { RunsResource } from './runs.js';
7
+ import { RunHandle } from './run.js';
8
+ import { SchedulesResource } from './schedules.js';
9
+ const DEFAULT_BASE_URL = 'https://api.scrapy.io/v1';
10
+ /**
11
+ * Official Scrapy.io TypeScript SDK.
12
+ *
13
+ * API keys must stay server-side. Do not embed keys in browser apps.
14
+ */
15
+ export class ScrapyIO {
16
+ tools;
17
+ runs;
18
+ schedules;
19
+ apiKey;
20
+ platform;
21
+ fetchImpl;
22
+ constructor(options) {
23
+ const apiKey = options.apiKey?.trim();
24
+ if (!apiKey) {
25
+ throw new ScrapyIOError({
26
+ message: 'ScrapyIO requires an apiKey (e.g. process.env.SCRAPYIO_API_KEY)',
27
+ type: 'sdk_error',
28
+ });
29
+ }
30
+ this.apiKey = apiKey;
31
+ const baseFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
32
+ if (typeof baseFetch !== 'function') {
33
+ throw new ScrapyIOError({
34
+ message: 'ScrapyIO requires a fetch implementation (Node 18+ recommended)',
35
+ type: 'sdk_error',
36
+ });
37
+ }
38
+ this.fetchImpl = createRetryFetch({
39
+ fetchImpl: baseFetch,
40
+ maxRetries: options.maxRetries ?? 2,
41
+ retryBackoffMs: options.retryBackoffMs ?? 250,
42
+ });
43
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
44
+ this.platform = createPlatformClient({
45
+ baseUrl,
46
+ headers: {
47
+ Authorization: `Bearer ${this.apiKey}`,
48
+ Accept: 'application/json',
49
+ },
50
+ fetch: this.fetchImpl,
51
+ });
52
+ this.tools = new ToolsResource(this.platform);
53
+ this.runs = new RunsResource(this.platform);
54
+ this.schedules = new SchedulesResource(this.platform);
55
+ }
56
+ /** Lightweight tool handle (`publisher/slug`). Does not hit the network. */
57
+ tool(ref) {
58
+ return new ToolHandle(this.platform, { apiKey: this.apiKey, fetchImpl: this.fetchImpl }, ref);
59
+ }
60
+ /** Lightweight run handle. Does not hit the network. */
61
+ run(runId) {
62
+ return new RunHandle(this.platform, runId);
63
+ }
64
+ }
65
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAuB,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGnD,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAEpD;;;;GAIG;AACH,MAAM,OAAO,QAAQ;IACV,KAAK,CAAgB;IACrB,IAAI,CAAe;IACnB,SAAS,CAAoB;IAErB,MAAM,CAAS;IACf,QAAQ,CAAiB;IACzB,SAAS,CAA0B;IAEpD,YAAY,OAAwB;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,aAAa,CAAC;gBACtB,OAAO,EAAE,iEAAiE;gBAC1E,IAAI,EAAE,WAAW;aAClB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACrE,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,IAAI,aAAa,CAAC;gBACtB,OAAO,EAAE,iEAAiE;gBAC1E,IAAI,EAAE,WAAW;aAClB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,gBAAgB,CAAC;YAChC,SAAS,EAAE,SAAS;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC;YACnC,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,GAAG;SAC9C,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,QAAQ,GAAG,oBAAoB,CAAC;YACnC,OAAO;YACP,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;gBACtC,MAAM,EAAE,kBAAkB;aAC3B;YACD,KAAK,EAAE,IAAI,CAAC,SAAS;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,SAAS,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,4EAA4E;IAC5E,IAAI,CAAC,GAAW;QACd,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,CAAC;IAChG,CAAC;IAED,wDAAwD;IACxD,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ export type JsonObject = Record<string, unknown>;
2
+ export type PlatformErrorType = 'unauthorized' | 'forbidden' | 'not_found' | 'validation_error' | 'insufficient_credits' | 'conflict' | 'rate_limit_exceeded' | 'internal_error' | 'timeout' | 'publisher_error' | 'sdk_error';
3
+ export declare class ScrapyIOError extends Error {
4
+ readonly name = "ScrapyIOError";
5
+ readonly status: number | undefined;
6
+ readonly type: PlatformErrorType;
7
+ readonly docUrl: string | undefined;
8
+ readonly body: unknown;
9
+ constructor(options: {
10
+ message: string;
11
+ type?: PlatformErrorType;
12
+ status?: number;
13
+ docUrl?: string;
14
+ body?: unknown;
15
+ cause?: unknown;
16
+ });
17
+ }
18
+ export declare function isPlatformErrorType(value: string): value is PlatformErrorType;
19
+ export declare function errorFromPlatformBody(status: number, body: unknown, fallbackMessage?: string): ScrapyIOError;
20
+ export declare function errorFromPublisherBody(status: number, body: unknown, fallbackMessage?: string): ScrapyIOError;
21
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,WAAW,GACX,WAAW,GACX,kBAAkB,GAClB,sBAAsB,GACtB,UAAU,GACV,qBAAqB,GACrB,gBAAgB,GAChB,SAAS,GACT,iBAAiB,GACjB,WAAW,CAAC;AAEhB,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,OAAO,EAAE;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,iBAAiB,CAAC;QACzB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CAOF;AAqBD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,eAAe,SAAgC,GAC9C,aAAa,CAoBf;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,eAAe,SAAuC,GACrD,aAAa,CA8Cf"}
package/dist/errors.js ADDED
@@ -0,0 +1,93 @@
1
+ export class ScrapyIOError extends Error {
2
+ name = 'ScrapyIOError';
3
+ status;
4
+ type;
5
+ docUrl;
6
+ body;
7
+ constructor(options) {
8
+ super(options.message, options.cause !== undefined ? { cause: options.cause } : undefined);
9
+ this.type = options.type ?? 'sdk_error';
10
+ this.status = options.status;
11
+ this.docUrl = options.docUrl;
12
+ this.body = options.body;
13
+ }
14
+ }
15
+ const PLATFORM_ERROR_TYPES = new Set([
16
+ 'unauthorized',
17
+ 'forbidden',
18
+ 'not_found',
19
+ 'validation_error',
20
+ 'insufficient_credits',
21
+ 'conflict',
22
+ 'rate_limit_exceeded',
23
+ 'internal_error',
24
+ ]);
25
+ export function isPlatformErrorType(value) {
26
+ return PLATFORM_ERROR_TYPES.has(value);
27
+ }
28
+ export function errorFromPlatformBody(status, body, fallbackMessage = 'Platform API request failed') {
29
+ const parsed = body;
30
+ const err = parsed?.error;
31
+ if (err && typeof err === 'object') {
32
+ const typeRaw = typeof err.type === 'string' ? err.type : 'internal_error';
33
+ const type = isPlatformErrorType(typeRaw) ? typeRaw : 'internal_error';
34
+ return new ScrapyIOError({
35
+ message: typeof err.message === 'string' && err.message ? err.message : fallbackMessage,
36
+ type,
37
+ status,
38
+ docUrl: typeof err.doc_url === 'string' ? err.doc_url : undefined,
39
+ body,
40
+ });
41
+ }
42
+ return new ScrapyIOError({
43
+ message: fallbackMessage,
44
+ type: status === 401 ? 'unauthorized' : 'internal_error',
45
+ status,
46
+ body,
47
+ });
48
+ }
49
+ export function errorFromPublisherBody(status, body, fallbackMessage = 'Publisher execution request failed') {
50
+ if (body && typeof body === 'object') {
51
+ const record = body;
52
+ if (record.error === 'INSUFFICIENT_CREDITS') {
53
+ return new ScrapyIOError({
54
+ message: typeof record.message === 'string' && record.message
55
+ ? record.message
56
+ : 'Insufficient credits',
57
+ type: 'insufficient_credits',
58
+ status: status || 402,
59
+ body,
60
+ });
61
+ }
62
+ if (typeof record.error === 'string' && record.error) {
63
+ const message = typeof record.message === 'string' && record.message
64
+ ? `${record.error}: ${record.message}`
65
+ : record.error;
66
+ const type = status === 401
67
+ ? 'unauthorized'
68
+ : status === 404
69
+ ? 'not_found'
70
+ : status === 400
71
+ ? 'validation_error'
72
+ : status === 409
73
+ ? 'conflict'
74
+ : 'publisher_error';
75
+ return new ScrapyIOError({ message, type, status, body });
76
+ }
77
+ if (record.success === false && typeof record.error === 'string') {
78
+ return new ScrapyIOError({
79
+ message: record.error,
80
+ type: status === 401 ? 'unauthorized' : 'publisher_error',
81
+ status,
82
+ body,
83
+ });
84
+ }
85
+ }
86
+ return new ScrapyIOError({
87
+ message: fallbackMessage,
88
+ type: 'publisher_error',
89
+ status,
90
+ body,
91
+ });
92
+ }
93
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { JsonObject, Run, ToolExecution } from './types.js';
2
+ export type ExecutionContext = {
3
+ apiKey: string;
4
+ fetchImpl: typeof globalThis.fetch;
5
+ };
6
+ /**
7
+ * POST tool-native JSON to publisher `/v1/api` (sync).
8
+ * Returns the publisher `data` payload on success.
9
+ */
10
+ export declare function executeCall(ctx: ExecutionContext, execution: ToolExecution, input: JsonObject): Promise<unknown>;
11
+ /**
12
+ * POST to publisher `/v1/scraper` (async). Maps `taskId` → public `Run.id`.
13
+ */
14
+ export declare function executeStart(ctx: ExecutionContext, execution: ToolExecution, input: JsonObject): Promise<Run>;
15
+ //# sourceMappingURL=execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACpC,CAAC;AAaF;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,OAAO,CAAC,CAiBlB;AA0BD;;GAEG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,GAAG,CAAC,CA4Cd"}
@@ -0,0 +1,117 @@
1
+ import { errorFromPublisherBody, ScrapyIOError } from './errors.js';
2
+ /**
3
+ * POST tool-native JSON to publisher `/v1/api` (sync).
4
+ * Returns the publisher `data` payload on success.
5
+ */
6
+ export async function executeCall(ctx, execution, input) {
7
+ const response = await ctx.fetchImpl(execution.apiUrl, {
8
+ method: 'POST',
9
+ headers: {
10
+ Authorization: `Bearer ${ctx.apiKey}`,
11
+ 'Content-Type': 'application/json',
12
+ Accept: 'application/json',
13
+ },
14
+ body: JSON.stringify(input),
15
+ });
16
+ const body = await readJsonSafe(response);
17
+ if (!response.ok) {
18
+ throw errorFromPublisherBody(response.status, body);
19
+ }
20
+ return unwrapPublisherSuccess(body);
21
+ }
22
+ /**
23
+ * Publisher sync responses use `{ success, data }`. Some tools nest this twice.
24
+ * Unwrap while the value is a successful envelope (bounded depth).
25
+ */
26
+ function unwrapPublisherSuccess(body) {
27
+ let current = body;
28
+ for (let depth = 0; depth < 4; depth++) {
29
+ if (!current || typeof current !== 'object' || Array.isArray(current)) {
30
+ return current;
31
+ }
32
+ if (!('success' in current)) {
33
+ return current;
34
+ }
35
+ const record = current;
36
+ if (record.success === false) {
37
+ throw errorFromPublisherBody(400, current);
38
+ }
39
+ if (record.success !== true) {
40
+ return current;
41
+ }
42
+ current = record.data;
43
+ }
44
+ return current;
45
+ }
46
+ /**
47
+ * POST to publisher `/v1/scraper` (async). Maps `taskId` → public `Run.id`.
48
+ */
49
+ export async function executeStart(ctx, execution, input) {
50
+ const response = await ctx.fetchImpl(execution.scraperUrl, {
51
+ method: 'POST',
52
+ headers: {
53
+ Authorization: `Bearer ${ctx.apiKey}`,
54
+ 'Content-Type': 'application/json',
55
+ Accept: 'application/json',
56
+ },
57
+ body: JSON.stringify(input),
58
+ });
59
+ const body = await readJsonSafe(response);
60
+ if (!response.ok) {
61
+ throw errorFromPublisherBody(response.status, body);
62
+ }
63
+ const start = body;
64
+ if (!start || typeof start.taskId !== 'string' || !start.taskId) {
65
+ throw new ScrapyIOError({
66
+ message: 'Publisher did not return a taskId',
67
+ type: 'publisher_error',
68
+ status: response.status,
69
+ body,
70
+ });
71
+ }
72
+ const status = normalizePublisherStatus(start.status);
73
+ return {
74
+ id: start.taskId,
75
+ kind: 'async_batch',
76
+ status,
77
+ toolId: '',
78
+ runName: start.runName ?? null,
79
+ totalItems: start.totalItems ?? null,
80
+ processedItems: start.processedItems ?? null,
81
+ completedItems: start.completedItems ?? null,
82
+ failedItems: start.failedItems ?? null,
83
+ estimatedCost: start.estimatedCost ?? null,
84
+ billedAmount: null,
85
+ publisher: null,
86
+ toolSlug: null,
87
+ createdAt: new Date().toISOString(),
88
+ completedAt: null,
89
+ };
90
+ }
91
+ function normalizePublisherStatus(raw) {
92
+ if (!raw)
93
+ return 'queued';
94
+ if (raw === 'completed')
95
+ return 'succeeded';
96
+ if (raw === 'queued' ||
97
+ raw === 'running' ||
98
+ raw === 'succeeded' ||
99
+ raw === 'failed' ||
100
+ raw === 'cancelled' ||
101
+ raw === 'partial') {
102
+ return raw;
103
+ }
104
+ return 'queued';
105
+ }
106
+ async function readJsonSafe(response) {
107
+ const text = await response.text();
108
+ if (!text)
109
+ return null;
110
+ try {
111
+ return JSON.parse(text);
112
+ }
113
+ catch {
114
+ return text;
115
+ }
116
+ }
117
+ //# sourceMappingURL=execution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution.js","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * AUTO-GENERATED — do not edit by hand.
3
+ * Re-run: npm run generate
4
+ */
5
+ import createClient from "openapi-fetch";
6
+ import type { paths } from "./schema.js";
7
+ export type { paths };
8
+ export { createClient };
9
+ export type PlatformClient = ReturnType<typeof createClient<paths>>;
10
+ export declare function createPlatformClient(options: {
11
+ baseUrl: string;
12
+ headers?: Record<string, string>;
13
+ fetch?: typeof globalThis.fetch;
14
+ }): PlatformClient;
15
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/generated/client.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,YAAY,EAAE,KAAK,EAAE,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AAEpE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,GAAG,cAAc,CAMjB"}
@@ -0,0 +1,15 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * AUTO-GENERATED — do not edit by hand.
4
+ * Re-run: npm run generate
5
+ */
6
+ import createClient from "openapi-fetch";
7
+ export { createClient };
8
+ export function createPlatformClient(options) {
9
+ return createClient({
10
+ baseUrl: options.baseUrl,
11
+ headers: options.headers,
12
+ fetch: options.fetch,
13
+ });
14
+ }
15
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/generated/client.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG;AACH,OAAO,YAAY,MAAM,eAAe,CAAC;AAIzC,OAAO,EAAE,YAAY,EAAE,CAAC;AAIxB,MAAM,UAAU,oBAAoB,CAAC,OAIpC;IACC,OAAO,YAAY,CAAQ;QACzB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;AACL,CAAC"}