@flow-conductor/adapter-fetch 1.0.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 ADDED
@@ -0,0 +1,422 @@
1
+ # @flow-conductor/adapter-fetch
2
+
3
+ Fetch API adapter for flow-conductor. This adapter uses the native Fetch API available in Node.js 18+ and modern browsers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @flow-conductor/adapter-fetch @flow-conductor/core
9
+ ```
10
+
11
+ **Note**: `@flow-conductor/core` is a peer dependency and must be installed alongside this package.
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { begin } from "@flow-conductor/core";
17
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
18
+
19
+ const adapter = new FetchRequestAdapter();
20
+
21
+ const result = await begin(
22
+ {
23
+ config: {
24
+ url: "https://api.example.com/users",
25
+ method: "GET",
26
+ },
27
+ },
28
+ adapter
29
+ ).execute();
30
+
31
+ const data = await result.json();
32
+ console.log(data);
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Basic GET Request
38
+
39
+ ```typescript
40
+ import { begin } from "@flow-conductor/core";
41
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
42
+
43
+ const adapter = new FetchRequestAdapter();
44
+
45
+ const result = await begin(
46
+ {
47
+ config: {
48
+ url: "https://api.example.com/users/1",
49
+ method: "GET",
50
+ },
51
+ },
52
+ adapter
53
+ ).execute();
54
+
55
+ const user = await result.json();
56
+ console.log(user);
57
+ ```
58
+
59
+ ### POST Request with Data
60
+
61
+ ```typescript
62
+ import { begin } from "@flow-conductor/core";
63
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
64
+
65
+ const adapter = new FetchRequestAdapter();
66
+
67
+ const result = await begin(
68
+ {
69
+ config: {
70
+ url: "https://api.example.com/users",
71
+ method: "POST",
72
+ data: {
73
+ name: "John Doe",
74
+ email: "john@example.com",
75
+ },
76
+ },
77
+ },
78
+ adapter
79
+ ).execute();
80
+
81
+ const newUser = await result.json();
82
+ console.log(newUser);
83
+ ```
84
+
85
+ ### Request with Custom Headers
86
+
87
+ ```typescript
88
+ import { begin } from "@flow-conductor/core";
89
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
90
+
91
+ const adapter = new FetchRequestAdapter();
92
+
93
+ const result = await begin(
94
+ {
95
+ config: {
96
+ url: "https://api.example.com/users",
97
+ method: "GET",
98
+ headers: {
99
+ Authorization: "Bearer your-token-here",
100
+ "X-Custom-Header": "value",
101
+ },
102
+ },
103
+ },
104
+ adapter
105
+ ).execute();
106
+ ```
107
+
108
+ ### Chained Requests
109
+
110
+ ```typescript
111
+ import { begin } from "@flow-conductor/core";
112
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
113
+
114
+ const adapter = new FetchRequestAdapter();
115
+
116
+ const result = await begin(
117
+ {
118
+ config: {
119
+ url: "https://api.example.com/users/1",
120
+ method: "GET",
121
+ },
122
+ },
123
+ adapter
124
+ )
125
+ .next({
126
+ config: async (previousResult) => {
127
+ const user = await previousResult.json();
128
+ return {
129
+ url: `https://api.example.com/users/${user.id}/posts`,
130
+ method: "GET",
131
+ };
132
+ },
133
+ })
134
+ .execute();
135
+
136
+ const posts = await result.json();
137
+ console.log(posts);
138
+ ```
139
+
140
+ ## Configuration
141
+
142
+ The `FetchRequestAdapter` accepts standard `IRequestConfig` objects compatible with the Fetch API. The adapter automatically:
143
+
144
+ - JSON stringifies `data` for non-GET requests
145
+ - Sets `Content-Type: application/json` header when data is provided
146
+ - Passes through all other Fetch API options
147
+
148
+ ### Request Config Interface
149
+
150
+ ```typescript
151
+ interface FetchRequestConfig extends IRequestConfig {
152
+ url: string;
153
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
154
+ data?: any; // Will be JSON stringified for non-GET requests
155
+ headers?: Record<string, string>;
156
+ // ... other fetch options (credentials, cache, mode, etc.)
157
+ }
158
+ ```
159
+
160
+ ### Supported Fetch Options
161
+
162
+ All standard Fetch API options are supported:
163
+
164
+ ```typescript
165
+ const result = await begin(
166
+ {
167
+ config: {
168
+ url: "https://api.example.com/users",
169
+ method: "GET",
170
+ headers: {
171
+ "Authorization": "Bearer token",
172
+ },
173
+ credentials: "include", // Include cookies
174
+ cache: "no-cache", // Cache control
175
+ mode: "cors", // CORS mode
176
+ redirect: "follow", // Redirect handling
177
+ // ... any other RequestInit options
178
+ },
179
+ },
180
+ adapter
181
+ ).execute();
182
+ ```
183
+
184
+ ### Data Handling
185
+
186
+ The adapter automatically handles data serialization:
187
+
188
+ - **GET requests**: Data is ignored (use query parameters in URL instead)
189
+ - **Other methods**: Data is JSON stringified and sent as the request body
190
+ - **Content-Type**: Automatically set to `application/json` when data is provided
191
+
192
+ ```typescript
193
+ // POST with JSON data
194
+ const result = await begin(
195
+ {
196
+ config: {
197
+ url: "https://api.example.com/users",
198
+ method: "POST",
199
+ data: { name: "John", email: "john@example.com" },
200
+ // Content-Type: application/json is automatically added
201
+ },
202
+ },
203
+ adapter
204
+ ).execute();
205
+
206
+ // PUT with JSON data
207
+ const updateResult = await begin(
208
+ {
209
+ config: {
210
+ url: "https://api.example.com/users/1",
211
+ method: "PUT",
212
+ data: { name: "Jane" },
213
+ },
214
+ },
215
+ adapter
216
+ ).execute();
217
+ ```
218
+
219
+ ### Custom Headers
220
+
221
+ You can provide custom headers, which will be merged with default headers:
222
+
223
+ ```typescript
224
+ const result = await begin(
225
+ {
226
+ config: {
227
+ url: "https://api.example.com/users",
228
+ method: "POST",
229
+ data: { name: "John" },
230
+ headers: {
231
+ "Authorization": "Bearer token",
232
+ "X-Custom-Header": "value",
233
+ // Content-Type will be automatically added if not specified
234
+ },
235
+ },
236
+ },
237
+ adapter
238
+ ).execute();
239
+ ```
240
+
241
+ ## Response Handling
242
+
243
+ The adapter returns a standard `Response` object from the Fetch API. You can use all standard Response methods:
244
+
245
+ ```typescript
246
+ const result = await begin(
247
+ {
248
+ config: {
249
+ url: "https://api.example.com/users",
250
+ method: "GET",
251
+ },
252
+ },
253
+ adapter
254
+ ).execute();
255
+
256
+ // Standard Response methods
257
+ const json = await result.json();
258
+ const text = await result.text();
259
+ const blob = await result.blob();
260
+ const arrayBuffer = await result.arrayBuffer();
261
+
262
+ // Response properties
263
+ console.log(result.status); // HTTP status code
264
+ console.log(result.statusText); // Status text
265
+ console.log(result.ok); // true if status 200-299
266
+ console.log(result.headers); // Headers object
267
+ ```
268
+
269
+ ## Error Handling
270
+
271
+ The Fetch API only rejects on network errors, not HTTP error statuses. You may want to check the response status:
272
+
273
+ ```typescript
274
+ import { begin } from "@flow-conductor/core";
275
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
276
+
277
+ const adapter = new FetchRequestAdapter();
278
+
279
+ try {
280
+ const result = await begin(
281
+ {
282
+ config: {
283
+ url: "https://api.example.com/users",
284
+ method: "GET",
285
+ },
286
+ },
287
+ adapter
288
+ )
289
+ .withErrorHandler((error) => {
290
+ console.error("Request failed:", error);
291
+ })
292
+ .execute();
293
+
294
+ if (!result.ok) {
295
+ throw new Error(`HTTP error! status: ${result.status}`);
296
+ }
297
+
298
+ const data = await result.json();
299
+ console.log(data);
300
+ } catch (error) {
301
+ console.error("Error:", error);
302
+ }
303
+ ```
304
+
305
+ ## Browser vs Node.js
306
+
307
+ ### Browser
308
+
309
+ The Fetch API is natively available in modern browsers. No additional setup needed.
310
+
311
+ ```typescript
312
+ // Works in browsers with native fetch support
313
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
314
+ const adapter = new FetchRequestAdapter();
315
+ ```
316
+
317
+ ### Node.js
318
+
319
+ Node.js 18+ includes native Fetch API support. For older versions, you may need a polyfill:
320
+
321
+ ```typescript
322
+ // Node.js 18+
323
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
324
+ const adapter = new FetchRequestAdapter();
325
+
326
+ // Node.js < 18 (requires polyfill)
327
+ import fetch from "node-fetch";
328
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
329
+
330
+ // Note: You may need to configure the adapter to use the polyfill
331
+ // or use a different adapter implementation
332
+ ```
333
+
334
+ ## Examples
335
+
336
+ ### Authentication Flow
337
+
338
+ ```typescript
339
+ import { begin } from "@flow-conductor/core";
340
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
341
+
342
+ const adapter = new FetchRequestAdapter();
343
+
344
+ // Login and use token
345
+ const userData = await begin(
346
+ {
347
+ config: {
348
+ url: "https://api.example.com/auth/login",
349
+ method: "POST",
350
+ data: { username: "user", password: "pass" },
351
+ },
352
+ },
353
+ adapter
354
+ )
355
+ .next({
356
+ config: async (previousResult) => {
357
+ const auth = await previousResult.json();
358
+ return {
359
+ url: "https://api.example.com/user/profile",
360
+ method: "GET",
361
+ headers: { Authorization: `Bearer ${auth.token}` },
362
+ };
363
+ },
364
+ })
365
+ .execute();
366
+
367
+ const profile = await userData.json();
368
+ console.log(profile);
369
+ ```
370
+
371
+ ### File Upload
372
+
373
+ ```typescript
374
+ import { begin } from "@flow-conductor/core";
375
+ import { FetchRequestAdapter } from "@flow-conductor/adapter-fetch";
376
+
377
+ const adapter = new FetchRequestAdapter();
378
+
379
+ // Note: For file uploads, you may need to customize the adapter
380
+ // to handle FormData instead of JSON
381
+ const result = await begin(
382
+ {
383
+ config: {
384
+ url: "https://api.example.com/upload",
385
+ method: "POST",
386
+ data: formData, // FormData object
387
+ headers: {
388
+ // Don't set Content-Type, let browser set it with boundary
389
+ },
390
+ },
391
+ },
392
+ adapter
393
+ ).execute();
394
+ ```
395
+
396
+ ## API Reference
397
+
398
+ ### FetchRequestAdapter
399
+
400
+ ```typescript
401
+ class FetchRequestAdapter extends RequestAdapter<Response, FetchRequestConfig> {
402
+ createRequest(requestConfig: IRequestConfig): Promise<Response>;
403
+ }
404
+ ```
405
+
406
+ ### FetchRequestConfig
407
+
408
+ ```typescript
409
+ type FetchRequestConfig = IRequestConfig;
410
+ ```
411
+
412
+ Extends `IRequestConfig` with all standard Fetch API options.
413
+
414
+ ## Requirements
415
+
416
+ - `@flow-conductor/core` (peer dependency)
417
+ - Node.js 18+ (for native Fetch API) or a Fetch polyfill
418
+ - TypeScript 5.0+
419
+
420
+ ## License
421
+
422
+ MIT
@@ -0,0 +1,38 @@
1
+ import { RequestAdapter, IRequestConfig, UrlValidationOptions } from '@flow-conductor/core';
2
+
3
+ /**
4
+ * Request configuration type for Fetch adapter.
5
+ * Extends IRequestConfig with fetch-specific options via the index signature.
6
+ */
7
+ type FetchRequestConfig = IRequestConfig;
8
+ /**
9
+ * Request adapter implementation using the native Fetch API.
10
+ * Provides a lightweight, dependency-free HTTP client adapter.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const adapter = new FetchRequestAdapter();
15
+ * const chain = begin(
16
+ * { config: { url: 'https://api.example.com/users', method: 'GET' } },
17
+ * adapter
18
+ * );
19
+ * ```
20
+ */
21
+ declare class FetchRequestAdapter extends RequestAdapter<Response, FetchRequestConfig> {
22
+ /**
23
+ * Creates a new FetchRequestAdapter instance.
24
+ *
25
+ * @param urlValidationOptions - Optional URL validation options to prevent SSRF attacks
26
+ */
27
+ constructor(urlValidationOptions?: UrlValidationOptions);
28
+ /**
29
+ * Creates and executes an HTTP request using the Fetch API.
30
+ * Automatically handles JSON serialization for request bodies.
31
+ *
32
+ * @param requestConfig - The request configuration object
33
+ * @returns A promise that resolves to a Response object
34
+ */
35
+ createRequest(requestConfig: IRequestConfig): Promise<Response>;
36
+ }
37
+
38
+ export { type FetchRequestConfig, FetchRequestAdapter as default };
package/build/index.js ADDED
@@ -0,0 +1,36 @@
1
+ import { RequestAdapter } from '@flow-conductor/core';
2
+
3
+ // src/fetch-request-adapter.ts
4
+ var FetchRequestAdapter = class extends RequestAdapter {
5
+ /**
6
+ * Creates a new FetchRequestAdapter instance.
7
+ *
8
+ * @param urlValidationOptions - Optional URL validation options to prevent SSRF attacks
9
+ */
10
+ constructor(urlValidationOptions) {
11
+ super(urlValidationOptions);
12
+ }
13
+ /**
14
+ * Creates and executes an HTTP request using the Fetch API.
15
+ * Automatically handles JSON serialization for request bodies.
16
+ *
17
+ * @param requestConfig - The request configuration object
18
+ * @returns A promise that resolves to a Response object
19
+ */
20
+ createRequest(requestConfig) {
21
+ const { data, url, ...rest } = requestConfig;
22
+ const fetchConfig = { ...rest };
23
+ if (data) {
24
+ fetchConfig.body = typeof data === "string" ? data : JSON.stringify(data);
25
+ fetchConfig.headers = {
26
+ "Content-Type": "application/json",
27
+ ...fetchConfig.headers
28
+ };
29
+ }
30
+ return fetch(url, fetchConfig);
31
+ }
32
+ };
33
+
34
+ export { FetchRequestAdapter as default };
35
+ //# sourceMappingURL=index.js.map
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fetch-request-adapter.ts"],"names":[],"mappings":";;;AAyBA,IAAqB,mBAAA,GAArB,cAAiD,cAAA,CAG/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,oBAAA,EAA6C;AACvD,IAAA,KAAA,CAAM,oBAAoB,CAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASO,cAAc,aAAA,EAAkD;AACrE,IAAA,MAAM,EAAE,IAAA,EAAM,GAAA,EAAK,GAAG,MAAK,GAAI,aAAA;AAC/B,IAAA,MAAM,WAAA,GAA2B,EAAE,GAAG,IAAA,EAAK;AAC3C,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,WAAA,CAAY,OAAO,OAAO,IAAA,KAAS,WAAW,IAAA,GAAO,IAAA,CAAK,UAAU,IAAI,CAAA;AACxE,MAAA,WAAA,CAAY,OAAA,GAAU;AAAA,QACpB,cAAA,EAAgB,kBAAA;AAAA,QAChB,GAAI,WAAA,CAAY;AAAA,OAClB;AAAA,IACF;AACA,IAAA,OAAO,KAAA,CAAM,KAAK,WAAW,CAAA;AAAA,EAC/B;AACF","file":"index.js","sourcesContent":["import { RequestAdapter } from \"@flow-conductor/core\";\nimport type {\n IRequestConfig,\n UrlValidationOptions,\n} from \"@flow-conductor/core\";\n\n/**\n * Request configuration type for Fetch adapter.\n * Extends IRequestConfig with fetch-specific options via the index signature.\n */\nexport type FetchRequestConfig = IRequestConfig;\n\n/**\n * Request adapter implementation using the native Fetch API.\n * Provides a lightweight, dependency-free HTTP client adapter.\n *\n * @example\n * ```typescript\n * const adapter = new FetchRequestAdapter();\n * const chain = begin(\n * { config: { url: 'https://api.example.com/users', method: 'GET' } },\n * adapter\n * );\n * ```\n */\nexport default class FetchRequestAdapter extends RequestAdapter<\n Response,\n FetchRequestConfig\n> {\n /**\n * Creates a new FetchRequestAdapter instance.\n *\n * @param urlValidationOptions - Optional URL validation options to prevent SSRF attacks\n */\n constructor(urlValidationOptions?: UrlValidationOptions) {\n super(urlValidationOptions);\n }\n\n /**\n * Creates and executes an HTTP request using the Fetch API.\n * Automatically handles JSON serialization for request bodies.\n *\n * @param requestConfig - The request configuration object\n * @returns A promise that resolves to a Response object\n */\n public createRequest(requestConfig: IRequestConfig): Promise<Response> {\n const { data, url, ...rest } = requestConfig;\n const fetchConfig: RequestInit = { ...rest };\n if (data) {\n fetchConfig.body = typeof data === \"string\" ? data : JSON.stringify(data);\n fetchConfig.headers = {\n \"Content-Type\": \"application/json\",\n ...(fetchConfig.headers as Record<string, string>),\n };\n }\n return fetch(url, fetchConfig);\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@flow-conductor/adapter-fetch",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Fetch API adapter for flow-conductor",
6
+ "main": "./build/index.js",
7
+ "types": "./build/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./build/index.d.ts",
11
+ "import": "./build/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "build/**/*.js",
16
+ "build/**/*.d.ts",
17
+ "build/**/*.js.map",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "keywords": [
22
+ "flow-conductor",
23
+ "request",
24
+ "adapter",
25
+ "fetch",
26
+ "http",
27
+ "typescript"
28
+ ],
29
+ "scripts": {
30
+ "test": "echo \"No tests specified\" && exit 0",
31
+ "build": "tsup",
32
+ "clean": "rm -rf build",
33
+ "lint": "eslint src --ext .ts",
34
+ "lint:fix": "eslint src --ext .ts --fix",
35
+ "type-check": "tsc --noEmit",
36
+ "prepublishOnly": "npm run clean && npm run lint && npm run type-check && npm run build"
37
+ },
38
+ "author": "Dawid Hermann",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/dawidhermann/flow-conductor.git",
43
+ "directory": "packages/adapter-fetch"
44
+ },
45
+ "sideEffects": false,
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "peerDependencies": {
53
+ "@flow-conductor/core": "^1.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@eslint/js": "^9.39.2",
57
+ "@flow-conductor/core": "*",
58
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
59
+ "@typescript-eslint/parser": "^8.50.1",
60
+ "eslint": "^9.39.2",
61
+ "eslint-config-prettier": "^10.1.8",
62
+ "eslint-plugin-prettier": "^5.5.4",
63
+ "prettier": "^3.7.4",
64
+ "typescript": "^5.9.3",
65
+ "typescript-eslint": "^8.50.1"
66
+ }
67
+ }