chainflow 0.1.7 → 0.1.9
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 +113 -56
- package/dist/core/chainflow.d.ts +13 -12
- package/dist/core/chainflow.js +15 -15
- package/dist/core/chainflow.js.map +1 -1
- package/dist/core/store.d.ts +1 -2
- package/dist/core/store.js +1 -1
- package/dist/core/store.js.map +1 -1
- package/dist/core/utils/link.d.ts +1 -1
- package/dist/http/endpoint.d.ts +9 -20
- package/dist/http/endpoint.js +31 -56
- package/dist/http/endpoint.js.map +1 -1
- package/dist/http/errors.d.ts +9 -4
- package/dist/http/errors.js +18 -9
- package/dist/http/errors.js.map +1 -1
- package/dist/http/logger.js.map +1 -1
- package/dist/http/{originServer.d.ts → origin.d.ts} +7 -6
- package/dist/http/{originServer.js → origin.js} +14 -12
- package/dist/http/origin.js.map +1 -0
- package/dist/http/utils/client.d.ts +26 -16
- package/dist/http/utils/client.js +68 -20
- package/dist/http/utils/client.js.map +1 -1
- package/dist/http/utils/id.d.ts +2 -2
- package/dist/http/utils/id.js +2 -2
- package/dist/http/utils/id.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/http/originServer.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<h1 align="center" style="border-bottom: none;">🌊hainflow</h1>
|
|
2
|
-
<h3 align="center">
|
|
2
|
+
<h3 align="center">An Open Source library to create dynamic and composable API call workflows in TypeScript.</h3>
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
5
|
[](https://github.com/edwinlzs/chainflow/blob/main/LICENSE)
|
|
@@ -11,49 +11,62 @@
|
|
|
11
11
|
[](https://codecov.io/gh/edwinlzs/chainflow)
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 📖 Documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
### Read the guides over at [Chainflow Docs](https://edwinlzs.github.io/chainflow-docs/) to get started!
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## ⚠️ (Important!) Note
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Chainflow is my first and somewhat experimental open-source project which I started to assist my own dev work. It's similar to [Postman Flows](https://learning.postman.com/docs/postman-flows/gs/flows-overview/), but done in code so us engineers have more control to weave logic in between API endpoint calls (+ the ease of writing simple scripts with Chainflow to be run in your CLI or as pipeline jobs etc.).
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
I am still finding and ironing out pain points in using the library and releases should be considered unstable while I improve the library's usability. It is far from perfect in its current state, but I will continue to improve upon it if I or others find potential in its use. Therefore, your feedback and ideas are very important to me. I welcome thoughts on just about anything - useful dev tools to manage this project, QoL features to support easier coding patterns when using this library or even thoughts on the future direction of this project.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Drop them as a [Github issue](https://github.com/edwinlzs/chainflow/issues) or email me at <edwinlzscode@gmail.com>!
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
## 💭 When might Chainflow be useful?
|
|
27
|
+
|
|
28
|
+
1. **_Setting up demo data_**
|
|
29
|
+
|
|
30
|
+
Say you have an app that you're developing new features for and you'd like to demo those features. You need your app to be in a certain context and your database in a specific state - perhaps that context requires that a user has logged in with certain permissions, has created a "group" in the app and has added other users to that group. To create that context, you could manually click through your app to log in as a user and set everything up, but that could be tedious. Alternatively, you may use raw SQL or other DB scripts to directly manipulate the DB and insert users, roles, etc. However, those scripts could miss out on important side effects relevant to the business context of your app - side effects that tend to be triggered by services exposed by your backend server.
|
|
31
|
+
|
|
32
|
+
Instead, you can setup the context in your app by calling the relevant service endpoints you have built (`POST /user`, `POST /role`, etc.) and triggering their business logic to make your data setup adhere closely to the way your app truly behaves - as if a user was _really_ logging in and doing everything. Chainflow helps you do this by providing tools to compose API call workflows. You can then avoid manual setups and minimize your use of database scripts to just data that is not configurable with existing endpoints!
|
|
33
|
+
|
|
34
|
+
2. **_Speeding up development_**
|
|
35
|
+
|
|
36
|
+
Similar to setting up demo data, while coding new features you may often want to test out how they behave in your app, and again you may want your app to be in a specific state locally for that. You can write API call workflow scripts built with Chainflow to help move your app into those states quickly.
|
|
37
|
+
|
|
38
|
+
3. **_Testing your endpoints_**
|
|
39
|
+
|
|
40
|
+
An API call workflow could behave as if it were a frontend client calling the backend. In that way, you can run UI-agnostic end-to-end testing of backend endpoints by using API call workflows to simulate how a frontend would interact with the backend.
|
|
30
41
|
|
|
31
42
|
## Basic Usage
|
|
32
43
|
|
|
44
|
+
_Note: Chainflow may not be compatible with Node versions past end-of-life (v16.x and below)._
|
|
45
|
+
|
|
33
46
|
```console
|
|
34
47
|
npm install --save-dev chainflow
|
|
35
48
|
```
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
`origin` creates a template on which you define your API endpoints by calling the endpoint's HTTP method verb (e.g. `post`, `patch`) as methods.
|
|
38
51
|
|
|
39
52
|
```typescript
|
|
40
|
-
import {
|
|
53
|
+
import { origin } from chainflow;
|
|
41
54
|
|
|
42
|
-
const
|
|
55
|
+
const backend = origin('127.0.0.1:5000');
|
|
43
56
|
|
|
44
|
-
const createUser =
|
|
57
|
+
const createUser = backend.post('/user').body({
|
|
45
58
|
name: 'Tom',
|
|
46
59
|
details: {
|
|
47
60
|
age: 40,
|
|
48
61
|
},
|
|
49
62
|
});
|
|
50
63
|
|
|
51
|
-
const createRole =
|
|
64
|
+
const createRole = backend.post('/role').body({
|
|
52
65
|
type: 'Engineer',
|
|
53
66
|
userId: createUser.resp.body.id,
|
|
54
67
|
});
|
|
55
68
|
|
|
56
|
-
const getUser =
|
|
69
|
+
const getUser = backend.get('/user').query({
|
|
57
70
|
roleType: createRole.resp.body.type,
|
|
58
71
|
});
|
|
59
72
|
```
|
|
@@ -74,7 +87,7 @@ flow.run();
|
|
|
74
87
|
---
|
|
75
88
|
|
|
76
89
|
\
|
|
77
|
-
The above setup will result in the following
|
|
90
|
+
The above setup will result in the following endpoint calls:
|
|
78
91
|
|
|
79
92
|
1. `POST` Request to `/user` with body:
|
|
80
93
|
|
|
@@ -107,7 +120,7 @@ The above setup will result in the following API calls:
|
|
|
107
120
|
Define query params with the `query` method on an endpoint.
|
|
108
121
|
|
|
109
122
|
```typescript
|
|
110
|
-
const getUsersInGroup =
|
|
123
|
+
const getUsersInGroup = backend.get('/user').query({ groupId: 'some-id' });
|
|
111
124
|
```
|
|
112
125
|
|
|
113
126
|
### Path params
|
|
@@ -115,13 +128,13 @@ const getUsersInGroup = origin.get('/user').query({ groupId: 'some-id' });
|
|
|
115
128
|
Define path params by wrapping a param name with `{}` in the endpoint path.
|
|
116
129
|
|
|
117
130
|
```typescript
|
|
118
|
-
const getGroupsWithUser =
|
|
131
|
+
const getGroupsWithUser = backend.get('/groups/{userId}');
|
|
119
132
|
```
|
|
120
133
|
|
|
121
134
|
You can specify values for your path params by calling `pathParams`. Note that path params which do not actually exist in the path will be discarded.
|
|
122
135
|
|
|
123
136
|
```typescript
|
|
124
|
-
const getGroupsWithUser =
|
|
137
|
+
const getGroupsWithUser = backend.get('/groups/{userId}').pathParams({
|
|
125
138
|
userId: 'user123',
|
|
126
139
|
});
|
|
127
140
|
```
|
|
@@ -131,17 +144,38 @@ const getGroupsWithUser = origin.get('/groups/{userId}').pathParams({
|
|
|
131
144
|
Specify headers with `headers` method on endpoints.
|
|
132
145
|
|
|
133
146
|
```typescript
|
|
134
|
-
const getInfo =
|
|
147
|
+
const getInfo = backend.get('/info').headers({ token: 'some-token' });
|
|
135
148
|
```
|
|
136
149
|
|
|
137
|
-
You can also use `headers` on an `
|
|
150
|
+
You can also use `headers` on an `Origin` to have all endpoints made for that origin bear those headers.
|
|
138
151
|
|
|
139
152
|
```typescript
|
|
140
|
-
const
|
|
153
|
+
const backend = origin('127.0.0.1:3001').headers({ token: 'some-token' });
|
|
141
154
|
|
|
142
|
-
const getInfo =
|
|
155
|
+
const getInfo = backend.get('/info'); // getInfo endpoint will have the headers defined above
|
|
143
156
|
```
|
|
144
157
|
|
|
158
|
+
### Default headers
|
|
159
|
+
|
|
160
|
+
Chainflow attaches default headers to all requests made by any endpoint with the value:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
'content-type': 'application/json',
|
|
164
|
+
'User-Agent': 'Chainflow/[major.minor version number]',
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
If you'd like to change this, pass your default headers to the `defaultHeaders` util.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { defaultHeaders } from 'chainflow';
|
|
171
|
+
|
|
172
|
+
defaultHeaders({ 'content-type': 'application/xml' });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Pass in `true` as the second argument if you want to replace the entire set of default headers. Otherwise, the example above only overwrites the `content-type` default header and keeps `User-Agent`.
|
|
176
|
+
|
|
177
|
+
### Initializing Values
|
|
178
|
+
|
|
145
179
|
The request payloads under `Basic Usage` are defined with only _default_ values - i.e. the values which a Chainflow use if there are no response values from other endpoint calls linked to it.
|
|
146
180
|
|
|
147
181
|
However, you can also use the following features to more flexibly define the values used in a request.
|
|
@@ -151,7 +185,7 @@ However, you can also use the following features to more flexibly define the val
|
|
|
151
185
|
Marks a value as required but without a default. The chainflow will expect this value to be sourced from another node. If no such source is available, the endpoint call will throw an error.
|
|
152
186
|
|
|
153
187
|
```typescript
|
|
154
|
-
const createUser =
|
|
188
|
+
const createUser = backend.post('/user').body({
|
|
155
189
|
name: required(),
|
|
156
190
|
});
|
|
157
191
|
```
|
|
@@ -163,7 +197,7 @@ Provide a callback that generates values for building requests.
|
|
|
163
197
|
```typescript
|
|
164
198
|
const randAge = () => Math.floor(Math.random() * 100);
|
|
165
199
|
|
|
166
|
-
const createUser =
|
|
200
|
+
const createUser = backend.post('/user').body({
|
|
167
201
|
name: 'Tom',
|
|
168
202
|
details: {
|
|
169
203
|
age: gen(randAge),
|
|
@@ -178,7 +212,7 @@ You can use the `link` function to specify a callback to transform the response
|
|
|
178
212
|
```typescript
|
|
179
213
|
const addGreeting = (name: string) => `Hello ${name}`;
|
|
180
214
|
|
|
181
|
-
const createMessage =
|
|
215
|
+
const createMessage = backend.post('message').body({
|
|
182
216
|
msg: link(getUser.resp.body.name, addGreeting);
|
|
183
217
|
});
|
|
184
218
|
```
|
|
@@ -216,7 +250,7 @@ For the argument containing the source nodes, you can either pass an _array_ of
|
|
|
216
250
|
const mergeValues = ([name, favAnimal]: [string, string]) =>
|
|
217
251
|
`${name} likes ${favAnimal}.`;
|
|
218
252
|
|
|
219
|
-
const createMessage =
|
|
253
|
+
const createMessage = backend.post('message').body({
|
|
220
254
|
msg: linkMerge(
|
|
221
255
|
// array of source nodes
|
|
222
256
|
[getUser.resp.body.name, getFavAnimal.resp.body.favAnimal],
|
|
@@ -238,7 +272,7 @@ const mergeValues = ({
|
|
|
238
272
|
}) => `${userName} likes ${favAnimal}.`;
|
|
239
273
|
|
|
240
274
|
|
|
241
|
-
const createMessage =
|
|
275
|
+
const createMessage = backend.post('message').body({
|
|
242
276
|
msg: linkMerge(
|
|
243
277
|
// object of source nodes
|
|
244
278
|
{
|
|
@@ -288,7 +322,7 @@ You can declare manual values for an endpoint call in the chainflow itself, shou
|
|
|
288
322
|
`body`, `pathParams`, `query` and `headers` can be set this way.
|
|
289
323
|
|
|
290
324
|
```typescript
|
|
291
|
-
const createUser =
|
|
325
|
+
const createUser = backend.post('/user').body({
|
|
292
326
|
name: 'Tom',
|
|
293
327
|
});
|
|
294
328
|
|
|
@@ -304,7 +338,7 @@ You can specify request nodes to take values from the chainflow 'seed' by import
|
|
|
304
338
|
```typescript
|
|
305
339
|
import { chainflow, link seed, } from 'chainflow';
|
|
306
340
|
|
|
307
|
-
const createUser =
|
|
341
|
+
const createUser = backend.post('/user').body({
|
|
308
342
|
name: required(),
|
|
309
343
|
});
|
|
310
344
|
|
|
@@ -361,12 +395,14 @@ flow1.extend(flow2).run(); // calls endpoint 1, 2 and 3
|
|
|
361
395
|
### `config`
|
|
362
396
|
|
|
363
397
|
`respParser`
|
|
364
|
-
By default,
|
|
398
|
+
By default, a chainflow parses response bodies as JSON objects UNLESS the status code is `204` or the `content-type` header does not contain `application/json` (to avoid errors when parsing an empty body), upon which they will instead parse it as text.
|
|
399
|
+
|
|
400
|
+
To set a specific parsing format, you can call `.config` to change that configuration on an `endpoint` (or on an `Origin`, to apply it to all endpoints created from it) like so:
|
|
365
401
|
|
|
366
402
|
```typescript
|
|
367
403
|
import { RESP_PARSER } from 'chainflow';
|
|
368
404
|
|
|
369
|
-
const getUser =
|
|
405
|
+
const getUser = backend.get('/user').config({
|
|
370
406
|
respParser: RESP_PARSER.TEXT,
|
|
371
407
|
});
|
|
372
408
|
```
|
|
@@ -374,23 +410,22 @@ const getUser = origin.get('/user').config({
|
|
|
374
410
|
or with camelcase in JavaScript:
|
|
375
411
|
|
|
376
412
|
```javascript
|
|
377
|
-
const getUser =
|
|
413
|
+
const getUser = backend.get('/user').config({
|
|
378
414
|
respParser: 'text',
|
|
379
415
|
});
|
|
380
416
|
```
|
|
381
417
|
|
|
382
|
-
There are 4 supported ways to parse response bodies (
|
|
418
|
+
There are 4 supported ways to parse response bodies (provided by the underlying HTTP client `undici`): `arrayBuffer`, `blob`, `json` and `text`.
|
|
383
419
|
|
|
384
420
|
`respValidator`
|
|
385
421
|
Another configuration option is how to validate the response to an endpoint. By default, Chainflow rejects responses that have HTTP status code 400 and above and throws an error. You can pass in a custom `respValidator` to change when a response is rejected.
|
|
386
422
|
|
|
387
423
|
```typescript
|
|
388
|
-
const getUser =
|
|
424
|
+
const getUser = backend.get('/user').config({
|
|
389
425
|
respValidator: (resp) => {
|
|
390
|
-
if (resp.statusCode !== 201)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return { valid: false, msg: "Response did not provide user ID." };
|
|
426
|
+
if (resp.statusCode !== 201) return { valid: false, msg: 'Failed to retrieve users.' };
|
|
427
|
+
if (!Object.keys(resp.body as Record<string, unknown>).includes('id'))
|
|
428
|
+
return { valid: false, msg: 'Response did not provide user ID.' };
|
|
394
429
|
return { valid: true };
|
|
395
430
|
},
|
|
396
431
|
});
|
|
@@ -412,7 +447,7 @@ Instead of direct links between endpoints, you can use a central store to keep v
|
|
|
412
447
|
```typescript
|
|
413
448
|
import { store } from 'chainflow';
|
|
414
449
|
|
|
415
|
-
const createUser =
|
|
450
|
+
const createUser = backend
|
|
416
451
|
.post('/user')
|
|
417
452
|
.body({
|
|
418
453
|
name: 'Tom',
|
|
@@ -422,7 +457,7 @@ const createUser = origin
|
|
|
422
457
|
userId: resp.body.id,
|
|
423
458
|
}));
|
|
424
459
|
|
|
425
|
-
const addRole =
|
|
460
|
+
const addRole = backend.post('/role').body({
|
|
426
461
|
// this endpoint will take `userId` from the store, if available
|
|
427
462
|
userId: store.userId,
|
|
428
463
|
role: 'Engineer',
|
|
@@ -438,7 +473,7 @@ This is usually useful when you have endpoints that could take a value from any
|
|
|
438
473
|
Say we have 2 endpoints, `login` and `createGroup`. We want to login as a user once, then proceed to proceed 3 groups as that same user without having to login 3 times.
|
|
439
474
|
|
|
440
475
|
```typescript
|
|
441
|
-
const createGroup =
|
|
476
|
+
const createGroup = backend
|
|
442
477
|
.post('/group')
|
|
443
478
|
.headers({
|
|
444
479
|
Authorization: login.resp.body.authToken,
|
|
@@ -464,23 +499,30 @@ We run a chainflow that calls `login` first to get a response from the login end
|
|
|
464
499
|
|
|
465
500
|
Using the `continuesFrom` method, `createGroupFlow` will copy the state of source values (i.e. responses) from `loggedInFlow`. This means `createGroupFlow` will now have the logged in user's `authToken` received from calling `login`, and will use it when calling `createGroup` thrice for each group name in the `groupNames` array.
|
|
466
501
|
|
|
467
|
-
### `
|
|
502
|
+
### `events`
|
|
468
503
|
|
|
469
|
-
After running a chainflow, you can retrieve the
|
|
504
|
+
After running a chainflow, you can retrieve the request and response event data from endpoint calls executed during that run via the `events` property on that chainflow.
|
|
470
505
|
|
|
471
506
|
```typescript
|
|
472
507
|
const flow = chainflow().run(createUser).run(getRoles);
|
|
473
508
|
|
|
474
|
-
const
|
|
509
|
+
const events = flow.events;
|
|
475
510
|
```
|
|
476
511
|
|
|
477
|
-
The
|
|
512
|
+
The events will look something like:
|
|
478
513
|
|
|
479
514
|
```typescript
|
|
480
515
|
[
|
|
481
516
|
{
|
|
482
|
-
details: '[POST] /user' // identifies the endpoint called
|
|
483
|
-
|
|
517
|
+
details: '[POST] /user', // identifies the endpoint called
|
|
518
|
+
req: {
|
|
519
|
+
method: 'POST',
|
|
520
|
+
url: ...,
|
|
521
|
+
body: ...,
|
|
522
|
+
headers: ...,
|
|
523
|
+
respParser: ..., // the format used to parse the response body
|
|
524
|
+
},
|
|
525
|
+
resp: {
|
|
484
526
|
statusCode: 200,
|
|
485
527
|
body: ...,
|
|
486
528
|
headers: ...,
|
|
@@ -488,8 +530,9 @@ The responses will look something like:
|
|
|
488
530
|
}
|
|
489
531
|
},
|
|
490
532
|
{
|
|
491
|
-
details: '[GET] /roles'
|
|
492
|
-
|
|
533
|
+
details: '[GET] /roles',
|
|
534
|
+
req: ...,
|
|
535
|
+
resp: ...
|
|
493
536
|
}
|
|
494
537
|
]
|
|
495
538
|
```
|
|
@@ -498,7 +541,13 @@ The responses in the array follow the order in which the respective endpoints ar
|
|
|
498
541
|
|
|
499
542
|
### `logging`
|
|
500
543
|
|
|
501
|
-
|
|
544
|
+
Log requests/responses in your console by setting `ENABLE_CHAINFLOW_LOGS=true` in your environment variables, or by simply importing and calling the `enableLogs` function.
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
import { enableLogs } from 'chainflow';
|
|
548
|
+
|
|
549
|
+
enableLogs();
|
|
550
|
+
```
|
|
502
551
|
|
|
503
552
|
### Misc Behaviors
|
|
504
553
|
|
|
@@ -507,17 +556,21 @@ Enable logs from Chainflow by setting `ENABLE_CHAINFLOW_LOGS=true` in your envir
|
|
|
507
556
|
For example:
|
|
508
557
|
|
|
509
558
|
```typescript
|
|
510
|
-
chainflow()
|
|
559
|
+
chainflow()
|
|
560
|
+
.call(getUser) // 1st call
|
|
561
|
+
.call(addRole)
|
|
562
|
+
.call(getUser) // 2nd call
|
|
563
|
+
.call(createGroup);
|
|
511
564
|
```
|
|
512
565
|
|
|
513
|
-
If an input node on `createGroup` requires a value from a response to `getUser`, then `createGroup` will take that value from the
|
|
566
|
+
If an input node on `createGroup` requires a value from a response to `getUser`, then `createGroup` will take that value from the latest call to `getUser` (i.e. from the response to the 2nd call to `getUser` _after_ the call to `addRole`).
|
|
514
567
|
|
|
515
568
|
## Future Updates
|
|
516
569
|
|
|
517
570
|
Below features are currently not yet supported but are planned in future releases.
|
|
518
571
|
|
|
519
572
|
1. More flexibility to log and return responses
|
|
520
|
-
2. Conditional calls - execute an endpoint call only if some condition is met
|
|
573
|
+
2. Conditional calls - execute an endpoint call only if some condition is met
|
|
521
574
|
3. (Exploratory) API performance measurement
|
|
522
575
|
4. (Exploratory) Possibly some sort of UI/diagram generation
|
|
523
576
|
|
|
@@ -536,6 +589,10 @@ Below features are currently not yet supported but are planned in future release
|
|
|
536
589
|
- Should further explore appropriate degree of detail for logging
|
|
537
590
|
- Truncation of requests/responses with extremely large payloads
|
|
538
591
|
|
|
592
|
+
#### _Proxy performance_
|
|
593
|
+
|
|
594
|
+
- Creating proxies recursively when defining `SourceNode` paths could hit performance - not rigorously tested yet, so not 100% sure of how significant it is
|
|
595
|
+
|
|
539
596
|
### Trivia
|
|
540
597
|
|
|
541
598
|
- You probably noticed that I enjoy using the Builder pattern for its clarity.
|
package/dist/core/chainflow.d.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { SourceValues } from './inputNode';
|
|
2
2
|
import { IStore } from './store';
|
|
3
|
-
export interface CallResult {
|
|
4
|
-
|
|
3
|
+
export interface CallResult<Req, Resp> {
|
|
4
|
+
req: Req;
|
|
5
|
+
resp: Resp;
|
|
5
6
|
store?: IStore<unknown>;
|
|
6
7
|
}
|
|
7
8
|
/** Defines an endpoint that a chainflow can call upon. */
|
|
8
|
-
export interface IEndpoint<
|
|
9
|
-
/**
|
|
9
|
+
export interface IEndpoint<CallOpts, Req, Resp> {
|
|
10
|
+
/** Uniquely identifies this endpoint. */
|
|
10
11
|
id: string;
|
|
11
|
-
/**
|
|
12
|
+
/** Describes the endpoint. */
|
|
12
13
|
details: string;
|
|
13
|
-
call: (sources: SourceValues, opts?:
|
|
14
|
+
call: (sources: SourceValues, opts?: CallOpts) => Promise<CallResult<Req, Resp>>;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
interface IResponse {
|
|
16
|
+
interface CallEvent {
|
|
17
17
|
details: string;
|
|
18
|
-
|
|
18
|
+
req: unknown;
|
|
19
|
+
resp: unknown;
|
|
19
20
|
}
|
|
20
21
|
/** Special object used to link an InputNode to a chainflow seed. */
|
|
21
22
|
export declare const seed: import("./sourceNode").SourceNode;
|
|
@@ -23,14 +24,14 @@ export declare const seed: import("./sourceNode").SourceNode;
|
|
|
23
24
|
export declare const store: import("./sourceNode").SourceNode;
|
|
24
25
|
export declare class Chainflow {
|
|
25
26
|
#private;
|
|
26
|
-
/** Stores accumulated
|
|
27
|
-
|
|
27
|
+
/** Stores accumulated endpoint call events. */
|
|
28
|
+
events: CallEvent[];
|
|
28
29
|
/** Run the set up chain */
|
|
29
30
|
run(): Promise<this>;
|
|
30
31
|
/** Adds a seed to this chainflow. */
|
|
31
32
|
seed(seed: Record<string, any>): this;
|
|
32
33
|
/** Adds an endpoint call to the callchain. */
|
|
33
|
-
call<
|
|
34
|
+
call<CallOpts, Req, Resp>(endpoint: IEndpoint<CallOpts, Req, Resp>, opts?: CallOpts): this;
|
|
34
35
|
/** Resets the chainflow's state by clearing its accumulated sources. */
|
|
35
36
|
reset(): void;
|
|
36
37
|
/** Creates a clone of this chainflow's callqueue and initial sources
|
package/dist/core/chainflow.js
CHANGED
|
@@ -42,8 +42,8 @@ class Chainflow {
|
|
|
42
42
|
/** Stores the sources that this chainflow was initialized with. */
|
|
43
43
|
_Chainflow_initSources.set(this, {});
|
|
44
44
|
_Chainflow_callqueue.set(this, []);
|
|
45
|
-
/** Stores accumulated
|
|
46
|
-
this.
|
|
45
|
+
/** Stores accumulated endpoint call events. */
|
|
46
|
+
this.events = [];
|
|
47
47
|
}
|
|
48
48
|
/** Run the set up chain */
|
|
49
49
|
run() {
|
|
@@ -56,17 +56,14 @@ class Chainflow {
|
|
|
56
56
|
// call endpoint
|
|
57
57
|
const id = endpoint.id;
|
|
58
58
|
(0, logger_1.log)(`Calling endpoint with id "${id}"`);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
(0, logger_1.warn)(`Chainflow stopped at endpoint with id "${id}" and error: ${e}`);
|
|
68
|
-
throw e;
|
|
69
|
-
}
|
|
59
|
+
const { req, resp, store } = yield endpoint.call(__classPrivateFieldGet(this, _Chainflow_sources, "f"), opts).catch((err) => {
|
|
60
|
+
(0, logger_1.warn)(`Chainflow stopped at endpoint with id "${id}" and error: ${err}`);
|
|
61
|
+
throw err;
|
|
62
|
+
});
|
|
63
|
+
if (store && Object.keys(store).length > 0)
|
|
64
|
+
__classPrivateFieldGet(this, _Chainflow_sources, "f")[constants_1.STORE_ID][0] = deepmerge(__classPrivateFieldGet(this, _Chainflow_sources, "f")[constants_1.STORE_ID][0], store);
|
|
65
|
+
__classPrivateFieldGet(this, _Chainflow_sources, "f")[id] = [resp];
|
|
66
|
+
this.events.push({ details: endpoint.details, req, resp });
|
|
70
67
|
}
|
|
71
68
|
(0, logger_1.log)('Finished running chainflow.');
|
|
72
69
|
return this;
|
|
@@ -85,13 +82,16 @@ class Chainflow {
|
|
|
85
82
|
/** Resets the chainflow's state by clearing its accumulated sources. */
|
|
86
83
|
reset() {
|
|
87
84
|
__classPrivateFieldSet(this, _Chainflow_sources, {}, "f");
|
|
88
|
-
this.
|
|
85
|
+
this.events = [];
|
|
89
86
|
}
|
|
90
87
|
/** Creates a clone of this chainflow's callqueue and initial sources
|
|
91
88
|
* which can be extended and run independently. */
|
|
92
89
|
clone() {
|
|
93
90
|
const clone = new Chainflow();
|
|
94
|
-
__classPrivateFieldSet(clone, _Chainflow_initSources,
|
|
91
|
+
__classPrivateFieldSet(clone, _Chainflow_initSources, Object.entries(__classPrivateFieldGet(this, _Chainflow_initSources, "f")).reduce((acc, [sourceId, sourceValues]) => {
|
|
92
|
+
acc[sourceId] = [...sourceValues];
|
|
93
|
+
return acc;
|
|
94
|
+
}, {}), "f");
|
|
95
95
|
__classPrivateFieldSet(clone, _Chainflow_callqueue, [...__classPrivateFieldGet(this, _Chainflow_callqueue, "f")], "f");
|
|
96
96
|
return clone;
|
|
97
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chainflow.js","sourceRoot":"","sources":["../../src/core/chainflow.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,6CAA0C;AAC1C,mEAAgD;AAEhD,qCAAqC;AACrC,iDAAsD;AAEtD,MAAM,SAAS,GAAG,IAAA,mBAAc,GAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"chainflow.js","sourceRoot":"","sources":["../../src/core/chainflow.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,6CAA0C;AAC1C,mEAAgD;AAEhD,qCAAqC;AACrC,iDAAsD;AAEtD,MAAM,SAAS,GAAG,IAAA,mBAAc,GAAE,CAAC;AAgCnC,oEAAoE;AACvD,QAAA,IAAI,GAAG,IAAA,uBAAU,EAAC,mBAAO,CAAC,CAAC;AAExC,uFAAuF;AAC1E,QAAA,KAAK,GAAG,IAAA,uBAAU,EAAC,oBAAQ,CAAC,CAAC;AAE1C,MAAa,SAAS;IAAtB;QACE;iDACyC;QACzC,6BAAyB,EAAE,EAAC;QAC5B,mEAAmE;QACnE,iCAA6B,EAAE,EAAC;QAChC,+BAAwB,EAAE,EAAC;QAC3B,+CAA+C;QAC/C,WAAM,GAAgB,EAAE,CAAC;IAuE3B,CAAC;IArEC,2BAA2B;IACrB,GAAG;;YACP,IAAA,YAAG,EAAC,sBAAsB,CAAC,CAAC;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,uBAAA,IAAI,sBAAY,uBAAA,IAAI,8BAAa,MAAA,CAAC;YAClC,uBAAA,IAAI,0BAAS,CAAC,oBAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAE/B,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,uBAAA,IAAI,4BAAW,EAAE,CAAC;gBACjD,gBAAgB;gBAChB,MAAM,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;gBACvB,IAAA,YAAG,EAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;gBACxC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,uBAAA,IAAI,0BAAS,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBAClF,IAAA,aAAI,EAAC,0CAA0C,EAAE,gBAAgB,GAAG,EAAE,CAAC,CAAC;oBACxE,MAAM,GAAG,CAAC;gBACZ,CAAC,CAAC,CAAC;gBACH,IAAI,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;oBACxC,uBAAA,IAAI,0BAAS,CAAC,oBAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,uBAAA,IAAI,0BAAS,CAAC,oBAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC5E,uBAAA,IAAI,0BAAS,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,CAAC;YACD,IAAA,YAAG,EAAC,6BAA6B,CAAC,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC;KAAA;IAED,qCAAqC;IACrC,IAAI,CAAC,IAAyB;QAC5B,uBAAA,IAAI,8BAAa,CAAC,mBAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8CAA8C;IAC9C,IAAI,CAAsB,QAAwC,EAAE,IAAe;QACjF,uBAAA,IAAI,4BAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wEAAwE;IACxE,KAAK;QACH,uBAAA,IAAI,sBAAY,EAAE,MAAA,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;IAED;uDACmD;IACnD,KAAK;QACH,MAAM,KAAK,GAAG,IAAI,SAAS,EAAE,CAAC;QAC9B,uBAAA,KAAK,0BAAgB,MAAM,CAAC,OAAO,CAAC,uBAAA,IAAI,8BAAa,CAAC,CAAC,MAAM,CAC3D,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,EAAE;YAChC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;YAClC,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAkB,CACnB,MAAA,CAAC;QACF,uBAAA,KAAK,wBAAc,CAAC,GAAG,uBAAA,IAAI,4BAAW,CAAC,MAAA,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oEAAoE;IACpE,MAAM,CAAC,EAAa;QAClB,uBAAA,IAAI,4BAAW,CAAC,IAAI,CAAC,GAAG,uBAAA,EAAE,4BAAW,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;8CAC0C;IAC1C,aAAa,CAAC,EAAa;QACzB,uBAAA,IAAI,0DAAqB,uBAAA,IAAI,8BAAa,GAAK,uBAAA,EAAE,0BAAS,OAAE,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AA/ED,8BA+EC;;AAEM,MAAM,SAAS,GAAG,GAAc,EAAE;IACvC,OAAO,IAAI,SAAS,EAAE,CAAC;AACzB,CAAC,CAAC;AAFW,QAAA,SAAS,aAEpB"}
|
package/dist/core/store.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { CallResult } from './chainflow';
|
|
2
1
|
import { SourceNode } from './sourceNode';
|
|
3
2
|
export type StoreValue<T> = IStore<T> | T;
|
|
4
3
|
export interface IStore<T> {
|
|
@@ -10,5 +9,5 @@ export declare class Store {
|
|
|
10
9
|
/** Definition of values to be stored from responses to this endpoint. */
|
|
11
10
|
def: IStore<SourceNode>;
|
|
12
11
|
/** Assigns values to be put in the chainflow's store. */
|
|
13
|
-
storeValues(resp: unknown):
|
|
12
|
+
storeValues(resp: unknown): IStore<unknown>;
|
|
14
13
|
}
|
package/dist/core/store.js
CHANGED
package/dist/core/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ export interface SourceInfo {
|
|
|
6
6
|
source: SourceNode;
|
|
7
7
|
callback: ((val: any) => any) | undefined;
|
|
8
8
|
}
|
|
9
|
-
/** Overload signatures for `
|
|
9
|
+
/** Overload signatures for `link` function. */
|
|
10
10
|
interface Link {
|
|
11
11
|
/**
|
|
12
12
|
* Link a Source node to an Input node.
|
package/dist/http/endpoint.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InputNode, SourceValues, NODE_VALUE } from '../core/inputNode';
|
|
2
|
-
import {
|
|
2
|
+
import { IHttpReq, RESP_PARSER, ParsedHttpResp } from './utils/client';
|
|
3
3
|
import { CallResult, IEndpoint } from '../core/chainflow';
|
|
4
4
|
import { SourceNode } from '../core/sourceNode';
|
|
5
5
|
import { nodeValueIdentifier } from '../core/utils/symbols';
|
|
@@ -19,21 +19,11 @@ export interface HttpInputNodes {
|
|
|
19
19
|
/** Configurations for the endpoint. */
|
|
20
20
|
export interface EndpointConfig {
|
|
21
21
|
respParser?: `${RESP_PARSER}`;
|
|
22
|
-
respValidator?: (resp:
|
|
22
|
+
respValidator?: (resp: ParsedHttpResp) => {
|
|
23
23
|
valid: boolean;
|
|
24
24
|
msg?: string;
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
type ParsedResponse = Omit<Dispatcher.ResponseData, 'body'> & {
|
|
28
|
-
body: unknown;
|
|
29
|
-
};
|
|
30
|
-
/** Formats to parse the response body. */
|
|
31
|
-
export declare enum RESP_PARSER {
|
|
32
|
-
ARRAY_BUFFER = "arrayBuffer",
|
|
33
|
-
BLOB = "blob",
|
|
34
|
-
JSON = "json",
|
|
35
|
-
TEXT = "text"
|
|
36
|
-
}
|
|
37
27
|
/** Options for configuring an endpoint call. */
|
|
38
28
|
export interface HTTPCallOpts {
|
|
39
29
|
headers?: Record<string, string>;
|
|
@@ -45,15 +35,15 @@ export interface HTTPCallOpts {
|
|
|
45
35
|
* Manages request and response nodes,
|
|
46
36
|
* as well as calls to that endpoint
|
|
47
37
|
*/
|
|
48
|
-
export declare class Endpoint implements IEndpoint<HTTPCallOpts> {
|
|
38
|
+
export declare class Endpoint implements IEndpoint<HTTPCallOpts, IHttpReq, ParsedHttpResp> {
|
|
49
39
|
#private;
|
|
50
40
|
id: string;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
url: string;
|
|
42
|
+
method: SUPPORTED_METHOD;
|
|
43
|
+
constructor({ url, method }: {
|
|
44
|
+
url: string;
|
|
45
|
+
method: SUPPORTED_METHOD;
|
|
55
46
|
});
|
|
56
|
-
get method(): SUPPORTED_METHOD;
|
|
57
47
|
/** @todo Update this when there is a better implementation of id. */
|
|
58
48
|
get details(): string;
|
|
59
49
|
/** Configures this endpoint. */
|
|
@@ -72,8 +62,7 @@ export declare class Endpoint implements IEndpoint<HTTPCallOpts> {
|
|
|
72
62
|
/** Declare values to store from responses to this endpoint. */
|
|
73
63
|
store(callback: (resp: SourceNode) => IStore<SourceNode>): this;
|
|
74
64
|
/** Calls this endpoint with responses provided from earlier requests in the chain. */
|
|
75
|
-
call(responses: SourceValues, opts?: HTTPCallOpts): Promise<CallResult
|
|
65
|
+
call(responses: SourceValues, opts?: HTTPCallOpts): Promise<CallResult<IHttpReq, ParsedHttpResp>>;
|
|
76
66
|
/** Passes the request input nodes of this endpoint to a callback. */
|
|
77
67
|
set(setter: (nodes: HttpInputNodes) => void): this;
|
|
78
68
|
}
|
|
79
|
-
export {};
|