chainflow 0.1.9 → 5.0.3

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.

Potentially problematic release.


This version of chainflow might be problematic. Click here for more details.

Files changed (77) hide show
  1. package/.idea/codeStyles/Project.xml +27 -0
  2. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  3. package/.idea/dotenv-master.iml +12 -0
  4. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/.vscode/settings.json +2 -0
  8. package/CHANGELOG.md +431 -0
  9. package/LICENSE +23 -21
  10. package/README-es.md +442 -0
  11. package/README.md +633 -599
  12. package/config.d.ts +1 -0
  13. package/config.js +9 -0
  14. package/index.js +35 -0
  15. package/lib/cli-options.js +11 -0
  16. package/lib/env-options.js +24 -0
  17. package/lib/main.d.ts +156 -0
  18. package/lib/main.js +314 -0
  19. package/package.json +56 -52
  20. package/pk.json +64 -0
  21. package/dist/core/chainflow.d.ts +0 -47
  22. package/dist/core/chainflow.js +0 -116
  23. package/dist/core/chainflow.js.map +0 -1
  24. package/dist/core/inputNode.d.ts +0 -34
  25. package/dist/core/inputNode.js +0 -218
  26. package/dist/core/inputNode.js.map +0 -1
  27. package/dist/core/logger.d.ts +0 -4
  28. package/dist/core/logger.js +0 -19
  29. package/dist/core/logger.js.map +0 -1
  30. package/dist/core/sourceNode.d.ts +0 -24
  31. package/dist/core/sourceNode.js +0 -37
  32. package/dist/core/sourceNode.js.map +0 -1
  33. package/dist/core/store.d.ts +0 -13
  34. package/dist/core/store.js +0 -91
  35. package/dist/core/store.js.map +0 -1
  36. package/dist/core/utils/config.d.ts +0 -16
  37. package/dist/core/utils/config.js +0 -13
  38. package/dist/core/utils/config.js.map +0 -1
  39. package/dist/core/utils/constants.d.ts +0 -2
  40. package/dist/core/utils/constants.js +0 -6
  41. package/dist/core/utils/constants.js.map +0 -1
  42. package/dist/core/utils/initializers.d.ts +0 -12
  43. package/dist/core/utils/initializers.js +0 -18
  44. package/dist/core/utils/initializers.js.map +0 -1
  45. package/dist/core/utils/link.d.ts +0 -65
  46. package/dist/core/utils/link.js +0 -34
  47. package/dist/core/utils/link.js.map +0 -1
  48. package/dist/core/utils/symbols.d.ts +0 -7
  49. package/dist/core/utils/symbols.js +0 -11
  50. package/dist/core/utils/symbols.js.map +0 -1
  51. package/dist/http/endpoint.d.ts +0 -68
  52. package/dist/http/endpoint.js +0 -222
  53. package/dist/http/endpoint.js.map +0 -1
  54. package/dist/http/errors.d.ts +0 -16
  55. package/dist/http/errors.js +0 -36
  56. package/dist/http/errors.js.map +0 -1
  57. package/dist/http/logger.d.ts +0 -4
  58. package/dist/http/logger.js +0 -21
  59. package/dist/http/logger.js.map +0 -1
  60. package/dist/http/origin.d.ts +0 -29
  61. package/dist/http/origin.js +0 -62
  62. package/dist/http/origin.js.map +0 -1
  63. package/dist/http/reqBuilder.d.ts +0 -14
  64. package/dist/http/reqBuilder.js +0 -50
  65. package/dist/http/reqBuilder.js.map +0 -1
  66. package/dist/http/utils/client.d.ts +0 -27
  67. package/dist/http/utils/client.js +0 -88
  68. package/dist/http/utils/client.js.map +0 -1
  69. package/dist/http/utils/constants.d.ts +0 -2
  70. package/dist/http/utils/constants.js +0 -12
  71. package/dist/http/utils/constants.js.map +0 -1
  72. package/dist/http/utils/id.d.ts +0 -5
  73. package/dist/http/utils/id.js +0 -9
  74. package/dist/http/utils/id.js.map +0 -1
  75. package/dist/index.d.ts +0 -10
  76. package/dist/index.js +0 -30
  77. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,599 +1,633 @@
1
- <h1 align="center" style="border-bottom: none;">🌊hainflow</h1>
2
- <h3 align="center">An Open Source library to create dynamic and composable API call workflows in TypeScript.</h3>
3
- <div align="center">
4
-
5
- [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/edwinlzs/chainflow/blob/main/LICENSE)
6
- &nbsp;
7
- [![NPM version](https://img.shields.io/npm/v/chainflow.svg?style=flat-square)](https://www.npmjs.com/package/chainflow)
8
- &nbsp;
9
- [![GitHub Actions](https://img.shields.io/github/actions/workflow/status/edwinlzs/chainflow/ci.yml?style=flat-square&branch=main)](https://github.com/edwinlzs/chainflow/actions)
10
- &nbsp;
11
- [![codecov](https://img.shields.io/codecov/c/gh/edwinlzs/chainflow?token=O55JNRTCM5&style=flat-square&color=23a133)](https://codecov.io/gh/edwinlzs/chainflow)
12
- </div>
13
-
14
- ## 📖 Documentation
15
-
16
- ### Read the guides over at [Chainflow Docs](https://edwinlzs.github.io/chainflow-docs/) to get started!
17
-
18
- ## ⚠️ (Important!) Note
19
-
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
-
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
-
24
- Drop them as a [Github issue](https://github.com/edwinlzs/chainflow/issues) or email me at <edwinlzscode@gmail.com>!
25
-
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.
41
-
42
- ## Basic Usage
43
-
44
- _Note: Chainflow may not be compatible with Node versions past end-of-life (v16.x and below)._
45
-
46
- ```console
47
- npm install --save-dev chainflow
48
- ```
49
-
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.
51
-
52
- ```typescript
53
- import { origin } from chainflow;
54
-
55
- const backend = origin('127.0.0.1:5000');
56
-
57
- const createUser = backend.post('/user').body({
58
- name: 'Tom',
59
- details: {
60
- age: 40,
61
- },
62
- });
63
-
64
- const createRole = backend.post('/role').body({
65
- type: 'Engineer',
66
- userId: createUser.resp.body.id,
67
- });
68
-
69
- const getUser = backend.get('/user').query({
70
- roleType: createRole.resp.body.type,
71
- });
72
- ```
73
-
74
- Use a `chainflow` to define a sequence of endpoint calls that take advantage of the values and links provided above.
75
-
76
- ```typescript
77
- import { chainflow } from Chainflow;
78
-
79
- const flow = chainflow()
80
- .call(createUser)
81
- .call(createRole)
82
- .call(getUser);
83
-
84
- flow.run();
85
- ```
86
-
87
- ---
88
-
89
- \
90
- The above setup will result in the following endpoint calls:
91
-
92
- 1. `POST` Request to `/user` with body:
93
-
94
- ```json
95
- {
96
- "name": "Tom",
97
- "details": {
98
- "age": 40
99
- }
100
- }
101
- ```
102
-
103
- 2. `POST` Request to `/role` with body:
104
-
105
- ```json
106
- {
107
- "type": "Engineer",
108
- "userId": "['userId' from response to step 1]"
109
- }
110
- ```
111
-
112
- 3. `GET` Request to `/user?roleType=['type' from response to step 2]`
113
-
114
- &nbsp;
115
-
116
- ## More Features
117
-
118
- ### Query params
119
-
120
- Define query params with the `query` method on an endpoint.
121
-
122
- ```typescript
123
- const getUsersInGroup = backend.get('/user').query({ groupId: 'some-id' });
124
- ```
125
-
126
- ### Path params
127
-
128
- Define path params by wrapping a param name with `{}` in the endpoint path.
129
-
130
- ```typescript
131
- const getGroupsWithUser = backend.get('/groups/{userId}');
132
- ```
133
-
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.
135
-
136
- ```typescript
137
- const getGroupsWithUser = backend.get('/groups/{userId}').pathParams({
138
- userId: 'user123',
139
- });
140
- ```
141
-
142
- ### Headers
143
-
144
- Specify headers with `headers` method on endpoints.
145
-
146
- ```typescript
147
- const getInfo = backend.get('/info').headers({ token: 'some-token' });
148
- ```
149
-
150
- You can also use `headers` on an `Origin` to have all endpoints made for that origin bear those headers.
151
-
152
- ```typescript
153
- const backend = origin('127.0.0.1:3001').headers({ token: 'some-token' });
154
-
155
- const getInfo = backend.get('/info'); // getInfo endpoint will have the headers defined above
156
- ```
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
-
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.
180
-
181
- However, you can also use the following features to more flexibly define the values used in a request.
182
-
183
- ### `required`
184
-
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.
186
-
187
- ```typescript
188
- const createUser = backend.post('/user').body({
189
- name: required(),
190
- });
191
- ```
192
-
193
- ### `gen`
194
-
195
- Provide a callback that generates values for building requests.
196
-
197
- ```typescript
198
- const randAge = () => Math.floor(Math.random() * 100);
199
-
200
- const createUser = backend.post('/user').body({
201
- name: 'Tom',
202
- details: {
203
- age: gen(randAge),
204
- },
205
- });
206
- ```
207
-
208
- ### `link`
209
-
210
- You can use the `link` function to specify a callback to transform the response value before it is passed to the input node.
211
-
212
- ```typescript
213
- const addGreeting = (name: string) => `Hello ${name}`;
214
-
215
- const createMessage = backend.post('message').body({
216
- msg: link(getUser.resp.body.name, addGreeting);
217
- });
218
- ```
219
-
220
- ### `set`
221
-
222
- The `link` has another function signature.
223
-
224
- You can use the `set` method on an endpoint to expose its input nodes, then use the 2nd function signature of `link` as shown below: pass in the input node first (`msg`), then the source node second and optionally a callback third.
225
-
226
- ```typescript
227
- createMessage.set(({ body: { msg } }) => {
228
- link(msg, getUser.resp.body.name);
229
- link(msg, createUser.resp.body.name);
230
- });
231
- ```
232
-
233
- With a callback:
234
-
235
- ```typescript
236
- createMessage.set(({ body: { msg } }) => {
237
- link(msg, getUser.resp.body.name, addGreeting);
238
- link(msg, createUser.resp.body.name, addGreeting);
239
- });
240
- ```
241
-
242
- ### `linkMerge`
243
-
244
- Link multiple response values to a single request node with an optional callback to merge the values into a single input value. This has 4 function signatures:
245
-
246
- For the argument containing the source nodes, you can either pass an _array_ of SourceNodes:
247
-
248
- ```typescript
249
- // note the callback has an array parameter
250
- const mergeValues = ([name, favAnimal]: [string, string]) =>
251
- `${name} likes ${favAnimal}.`;
252
-
253
- const createMessage = backend.post('message').body({
254
- msg: linkMerge(
255
- // array of source nodes
256
- [getUser.resp.body.name, getFavAnimal.resp.body.favAnimal],
257
- mergeValues,
258
- );
259
- });
260
- ```
261
-
262
- or you can pass an _object_ with SourceNodes as the values:
263
-
264
- ```typescript
265
- // note the callback has an object parameter
266
- const mergeValues = ({
267
- userName,
268
- favAnimal,
269
- }: {
270
- userName: string;
271
- favAnimal: string;
272
- }) => `${userName} likes ${favAnimal}.`;
273
-
274
-
275
- const createMessage = backend.post('message').body({
276
- msg: linkMerge(
277
- // object of source nodes
278
- {
279
- userName: getUser.resp.body.name,
280
- favAnimal: getFavAnimal.resp.body.favAnimal,
281
- },
282
- mergeValues,
283
- );
284
- });
285
- ```
286
-
287
- alternatively, you can use the `set` method in addition with the other function signature of `linkMerge` (similar to how `link` above has overloads to work with `set`).
288
-
289
- with array:
290
-
291
- ```typescript
292
- createMessage.set(({ body: { msg } }) => {
293
- linkMerge(
294
- msg, // the input node
295
- [getUser.resp.body.name, getFavAnimal.resp.body.favAnimal],
296
- mergeValues,
297
- );
298
- });
299
- ```
300
-
301
- with object:
302
-
303
- ```typescript
304
- createMessage.set(({ body: { msg } }) => {
305
- linkMerge(
306
- msg, // the input node
307
- {
308
- userName: getUser.resp.body.name,
309
- favAnimal: getFavAnimal.resp.body.favAnimal,
310
- },
311
- mergeValues,
312
- );
313
- });
314
- ```
315
-
316
- Note that the merging link created by this method will only be used if ALL the source nodes specified are available i.e. if either one of `getUser.resp.body.name` or `getFavAnimal.resp.body.favAnimal` does not have a value, this link will not be used at all.
317
-
318
- ### Call Options
319
-
320
- You can declare manual values for an endpoint call in the chainflow itself, should you need to do so, by passing in a Call Options object as a second argument in the `call` method.
321
-
322
- `body`, `pathParams`, `query` and `headers` can be set this way.
323
-
324
- ```typescript
325
- const createUser = backend.post('/user').body({
326
- name: 'Tom',
327
- });
328
-
329
- chainflow()
330
- .call(createUser, { body: { name: 'Harry' } })
331
- .run();
332
- ```
333
-
334
- ### `seed`
335
-
336
- You can specify request nodes to take values from the chainflow 'seed' by importing the `seed` object and linking nodes to it. Provide actual seed values by calling the `seed` method on a chainflow before you `run` it, like below.
337
-
338
- ```typescript
339
- import { chainflow, link seed, } from 'chainflow';
340
-
341
- const createUser = backend.post('/user').body({
342
- name: required(),
343
- });
344
-
345
- createUser.set(({ body: { name }}) => {
346
- link(name, seed.username);
347
- });
348
-
349
- chainflow()
350
- .call()
351
- .seed({ username: 'Tom' })
352
- .run();
353
- ```
354
-
355
- ### Allow Undefined Sources Values
356
-
357
- By default, an input node will reject and skip a source node's value if it is unavailable or `undefined`. However, you can change this by passing a source node into the `config` utility function and passing an options object as the second parameter like below. This informs an input node to use the source node's value regardless of whether the value is `undefined` or not.
358
-
359
- ```typescript
360
- import { config } from 'chainflow';
361
-
362
- createUser.set(({ body: { name } }) => {
363
- link(name, config(seed.username, { allowUndefined: true }));
364
- });
365
- ```
366
-
367
- This has important implications - it means that as long as the source (e.g. a response from an endpoint call) is available, then the linked source node's value will be taken and used (even if that value is unavailable, which would be taken as `undefined`). Therefore, any other linked sources will not be used UNLESS 1. they have a higher priority or 2. the source providing the linked node that allows `undefined` is unavailable.
368
-
369
- &nbsp;
370
-
371
- ### `clone`
372
-
373
- You can create chainflow "templates" with the use of `clone` to create a copy of a chainflow and its endpoint callqueue. The clone can have endpoint calls added to it without modifying the initial flow.
374
-
375
- ```typescript
376
- const initialFlow = chainflow().call(endpoint1).call(endpoint2);
377
-
378
- const clonedFlow = initialFlow.clone();
379
-
380
- clonedFlow.call(endpoint3).run(); // calls endpoint 1, 2 and 3
381
- initialFlow.call(endpoint4).run(); // calls endpoint 1, 2 and 4
382
- ```
383
-
384
- ### `extend`
385
-
386
- You can connect multiple different chainflows together into a longer chainflow using `extend`.
387
-
388
- ```typescript
389
- const flow1 = chainflow().call(endpoint1).call(endpoint2);
390
- const flow2 = chainflow().call(endpoint3);
391
-
392
- flow1.extend(flow2).run(); // calls endpoint 1, 2 and 3
393
- ```
394
-
395
- ### `config`
396
-
397
- `respParser`
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:
401
-
402
- ```typescript
403
- import { RESP_PARSER } from 'chainflow';
404
-
405
- const getUser = backend.get('/user').config({
406
- respParser: RESP_PARSER.TEXT,
407
- });
408
- ```
409
-
410
- or with camelcase in JavaScript:
411
-
412
- ```javascript
413
- const getUser = backend.get('/user').config({
414
- respParser: 'text',
415
- });
416
- ```
417
-
418
- There are 4 supported ways to parse response bodies (provided by the underlying HTTP client `undici`): `arrayBuffer`, `blob`, `json` and `text`.
419
-
420
- `respValidator`
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.
422
-
423
- ```typescript
424
- const getUser = backend.get('/user').config({
425
- respValidator: (resp) => {
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.' };
429
- return { valid: true };
430
- },
431
- });
432
- ```
433
-
434
- Your custom validator callback should have a return type:
435
-
436
- ```typescript
437
- {
438
- valid: boolean; // false if response should be rejected
439
- msg?: string; // error message
440
- }
441
- ```
442
-
443
- ### `store`
444
-
445
- Instead of direct links between endpoints, you can use a central store to keep values from some endpoints and have other endpoints take from it via the special `store` object.
446
-
447
- ```typescript
448
- import { store } from 'chainflow';
449
-
450
- const createUser = backend
451
- .post('/user')
452
- .body({
453
- name: 'Tom',
454
- })
455
- .store((resp) => ({
456
- // this endpoint will store `id` from a response to `userId` in the store
457
- userId: resp.body.id,
458
- }));
459
-
460
- const addRole = backend.post('/role').body({
461
- // this endpoint will take `userId` from the store, if available
462
- userId: store.userId,
463
- role: 'Engineer',
464
- });
465
-
466
- chainflow().call(createUser).call(addRole).run();
467
- ```
468
-
469
- This is usually useful when you have endpoints that could take a value from any one of many other endpoints for the same input node. Having a store to centralise these many-to-many relationships (like an API Gateway) can improve the developer experience.
470
-
471
- ### `continuesFrom` - transferring Chainflow states
472
-
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.
474
-
475
- ```typescript
476
- const createGroup = backend
477
- .post('/group')
478
- .headers({
479
- Authorization: login.resp.body.authToken,
480
- })
481
- .body({
482
- groupName: seed.groupName,
483
- });
484
-
485
- // loggedInFlow will contain a response from the `login` endpoint
486
- const loggedInFlow = chainflow().call(login).run();
487
-
488
- // createGroupFlow will take the response that
489
- // loggedInFlow received and carry on from there
490
- const createGroupFlow = chainflow().call(createGroup).continuesFrom(loggedInFlow);
491
-
492
- const groupNames = ['RapGPT', 'Averageexpedition', 'Shaky Osmosis'];
493
- for (const groupName in groupNames) {
494
- createGroupFlow.seed({ groupName }).run();
495
- }
496
- ```
497
-
498
- We run a chainflow that calls `login` first to get a response from the login endpoint.
499
-
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.
501
-
502
- ### `events`
503
-
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.
505
-
506
- ```typescript
507
- const flow = chainflow().run(createUser).run(getRoles);
508
-
509
- const events = flow.events;
510
- ```
511
-
512
- The events will look something like:
513
-
514
- ```typescript
515
- [
516
- {
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: {
526
- statusCode: 200,
527
- body: ...,
528
- headers: ...,
529
- ...
530
- }
531
- },
532
- {
533
- details: '[GET] /roles',
534
- req: ...,
535
- resp: ...
536
- }
537
- ]
538
- ```
539
-
540
- The responses in the array follow the order in which the respective endpoints are called.
541
-
542
- ### `logging`
543
-
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
- ```
551
-
552
- ### Misc Behaviors
553
-
554
- - If you have multiple endpoint calls to the same endpoint on one chainflow and they are linked to other endpoints' input nodes further down the flow, the latest endpoint call's values will be used.
555
-
556
- For example:
557
-
558
- ```typescript
559
- chainflow()
560
- .call(getUser) // 1st call
561
- .call(addRole)
562
- .call(getUser) // 2nd call
563
- .call(createGroup);
564
- ```
565
-
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`).
567
-
568
- ## Future Updates
569
-
570
- Below features are currently not yet supported but are planned in future releases.
571
-
572
- 1. More flexibility to log and return responses
573
- 2. Conditional calls - execute an endpoint call only if some condition is met
574
- 3. (Exploratory) API performance measurement
575
- 4. (Exploratory) Possibly some sort of UI/diagram generation
576
-
577
- ## Development
578
-
579
- ### Areas that could be better (non-exhaustive)
580
-
581
- #### _Encoding endpoint IDs_
582
-
583
- - Currently assumes that URLs of endpoints do not contain unencoded `|` and `[]` characters. `[]` used to wrap around HTTP method in the encoded ID. Linkmerge uses `|` to separate different encoded IDs.
584
- - Current implementation also leads to ID collision if multiple endpoints with the same method and path are created (but perhaps with different configuration) and are called on the same chainflow.
585
- - Idea: Have a centralized service to issue unique IDs to deconflict endpoints - but still somehow encode the method/path info of an endpoint into it.
586
-
587
- #### _Logging_
588
-
589
- - Should further explore appropriate degree of detail for logging
590
- - Truncation of requests/responses with extremely large payloads
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
-
596
- ### Trivia
597
-
598
- - You probably noticed that I enjoy using the Builder pattern for its clarity.
599
- - I'm praying the wave 🌊 emoji remains sufficiently shaped like a "C" to avoid confusion. Please let me know if there is some system where it does not!
1
+ <div align="center">
2
+
3
+ <p>
4
+ <sup>
5
+ <a href="https://github.com/sponsors/motdotla">Dotenv is supported by the community.</a>
6
+ </sup>
7
+ </p>
8
+ <sup>Special thanks to:</sup>
9
+ <br>
10
+ <br>
11
+ <a href="https://www.warp.dev/?utm_source=github&utm_medium=referral&utm_campaign=dotenv_p_20220831">
12
+ <div>
13
+ <img src="https://res.cloudinary.com/dotenv-org/image/upload/v1661980709/warp_hi8oqj.png" width="230" alt="Warp">
14
+ </div>
15
+ <b>Warp is a blazingly fast, Rust-based terminal reimagined to work like a modern app.</b>
16
+ <div>
17
+ <sup>Get more done in the CLI with real text editing, block-based output, and AI command search.</sup>
18
+ </div>
19
+ </a>
20
+ <br>
21
+ <a href="https://retool.com/?utm_source=sponsor&utm_campaign=dotenv">
22
+ <div>
23
+ <img src="https://res.cloudinary.com/dotenv-org/image/upload/c_scale,w_300/v1664466968/logo-full-black_vidfqf.png" width="270" alt="Retool">
24
+ </div>
25
+ <b>Retool helps developers build custom internal software, like CRUD apps and admin panels, really fast.</b>
26
+ <div>
27
+ <sup>Build UIs visually with flexible components, connect to any data source, and write business logic in JavaScript.</sup>
28
+ </div>
29
+ </a>
30
+ <br>
31
+ <a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=dotenv&utm_source=github">
32
+ <div>
33
+ <img src="https://res.cloudinary.com/dotenv-org/image/upload/c_scale,w_400/v1665605496/68747470733a2f2f73696e647265736f726875732e636f6d2f6173736574732f7468616e6b732f776f726b6f732d6c6f676f2d77686974652d62672e737667_zdmsbu.svg" width="270" alt="WorkOS">
34
+ </div>
35
+ <b>Your App, Enterprise Ready.</b>
36
+ <div>
37
+ <sup>Add Single Sign-On, Multi-Factor Auth, and more, in minutes instead of months.</sup>
38
+ </div>
39
+ </a>
40
+ <hr>
41
+ </div>
42
+
43
+ # dotenv [![NPM version](https://img.shields.io/npm/v/dotenv.svg?style=flat-square)](https://www.npmjs.com/package/dotenv)
44
+
45
+ <img src="https://raw.githubusercontent.com/motdotla/dotenv/master/dotenv.svg" alt="dotenv" align="right" width="200" />
46
+
47
+ Dotenv is a zero-dependency module that loads environment variables from a `.env` file into [`process.env`](https://nodejs.org/docs/latest/api/process.html#process_process_env). Storing configuration in the environment separate from code is based on [The Twelve-Factor App](https://12factor.net/config) methodology.
48
+
49
+ [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
50
+ [![LICENSE](https://img.shields.io/github/license/motdotla/dotenv.svg)](LICENSE)
51
+ [![dotenv-vault](https://badge.dotenv.org/works-with.svg?r=1)](https://www.dotenv.org/r/github.com/dotenv-org/dotenv-vault?r=1)
52
+
53
+ * [🌱 Install](#-install)
54
+ * [🏗️ Usage (.env)](#%EF%B8%8F-usage)
55
+ * [🚀 Deploying (.env.vault) 🆕](#-deploying)
56
+ * [🌴 Multiple Environments 🆕](#-manage-multiple-environments)
57
+ * [📚 Examples](#-examples)
58
+ * [📖 Docs](#-documentation)
59
+ * [❓ FAQ](#-faq)
60
+ * [⏱️ Changelog](./CHANGELOG.md)
61
+
62
+ ## 🌱 Install
63
+
64
+ ```bash
65
+ # install locally (recommended)
66
+ npm install dotenv --save
67
+ ```
68
+
69
+ Or installing with yarn? `yarn add dotenv`
70
+
71
+ ## 🏗️ Usage
72
+
73
+ <a href="https://www.youtube.com/watch?v=YtkZR0NFd1g">
74
+ <div align="right">
75
+ <img src="https://img.youtube.com/vi/YtkZR0NFd1g/hqdefault.jpg" alt="how to use dotenv video tutorial" align="right" width="330" />
76
+ <img src="https://simpleicons.vercel.app/youtube/ff0000" alt="youtube/@dotenvorg" align="right" width="24" />
77
+ </div>
78
+ </a>
79
+
80
+ Create a `.env` file in the root of your project:
81
+
82
+ ```dosini
83
+ S3_BUCKET="YOURS3BUCKET"
84
+ SECRET_KEY="YOURSECRETKEYGOESHERE"
85
+ ```
86
+
87
+ As early as possible in your application, import and configure dotenv:
88
+
89
+ ```javascript
90
+ require('dotenv').config()
91
+ console.log(process.env) // remove this after you've confirmed it is working
92
+ ```
93
+
94
+ .. [or using ES6?](#how-do-i-use-dotenv-with-import)
95
+
96
+ ```javascript
97
+ import 'dotenv/config'
98
+ ```
99
+
100
+ That's it. `process.env` now has the keys and values you defined in your `.env` file:
101
+
102
+ ```javascript
103
+ require('dotenv').config()
104
+
105
+ ...
106
+
107
+ s3.getBucketCors({Bucket: process.env.S3_BUCKET}, function(err, data) {})
108
+ ```
109
+
110
+ ### Multiline values
111
+
112
+ If you need multiline variables, for example private keys, those are now supported (`>= v15.0.0`) with line breaks:
113
+
114
+ ```dosini
115
+ PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
116
+ ...
117
+ Kh9NV...
118
+ ...
119
+ -----END RSA PRIVATE KEY-----"
120
+ ```
121
+
122
+ Alternatively, you can double quote strings and use the `\n` character:
123
+
124
+ ```dosini
125
+ PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nKh9NV...\n-----END RSA PRIVATE KEY-----\n"
126
+ ```
127
+
128
+ ### Comments
129
+
130
+ Comments may be added to your file on their own line or inline:
131
+
132
+ ```dosini
133
+ # This is a comment
134
+ SECRET_KEY=YOURSECRETKEYGOESHERE # comment
135
+ SECRET_HASH="something-with-a-#-hash"
136
+ ```
137
+
138
+ Comments begin where a `#` exists, so if your value contains a `#` please wrap it in quotes. This is a breaking change from `>= v15.0.0` and on.
139
+
140
+ ### Parsing
141
+
142
+ The engine which parses the contents of your file containing environment variables is available to use. It accepts a String or Buffer and will return an Object with the parsed keys and values.
143
+
144
+ ```javascript
145
+ const dotenv = require('dotenv')
146
+ const buf = Buffer.from('BASIC=basic')
147
+ const config = dotenv.parse(buf) // will return an object
148
+ console.log(typeof config, config) // object { BASIC : 'basic' }
149
+ ```
150
+
151
+ ### Preload
152
+
153
+ You can use the `--require` (`-r`) [command line option](https://nodejs.org/api/cli.html#-r---require-module) to preload dotenv. By doing this, you do not need to require and load dotenv in your application code.
154
+
155
+ ```bash
156
+ $ node -r dotenv/config your_script.js
157
+ ```
158
+
159
+ The configuration options below are supported as command line arguments in the format `dotenv_config_<option>=value`
160
+
161
+ ```bash
162
+ $ node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env dotenv_config_debug=true
163
+ ```
164
+
165
+ Additionally, you can use environment variables to set configuration options. Command line arguments will precede these.
166
+
167
+ ```bash
168
+ $ DOTENV_CONFIG_<OPTION>=value node -r dotenv/config your_script.js
169
+ ```
170
+
171
+ ```bash
172
+ $ DOTENV_CONFIG_ENCODING=latin1 DOTENV_CONFIG_DEBUG=true node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env
173
+ ```
174
+
175
+ ### Variable Expansion
176
+
177
+ You need to add the value of another variable in one of your variables? Use [dotenv-expand](https://github.com/motdotla/dotenv-expand).
178
+
179
+ ### Syncing
180
+
181
+ You need to keep `.env` files in sync between machines, environments, or team members? Use [dotenv-vault](https://github.com/dotenv-org/dotenv-vault).
182
+
183
+ ### Deploying
184
+
185
+ You need to deploy your secrets in a cloud-agnostic manner? Use a `.env.vault` file.
186
+
187
+ ### Multiple Environments
188
+
189
+ You need to manage your secrets across different environments and apply them as needed? Use a `.env.vault` file with a `DOTENV_KEY`.
190
+
191
+ ## 🚀 Deploying
192
+
193
+ *Note: Requires dotenv >= 16.1.0*
194
+
195
+ Encrypt your `.env.vault` file.
196
+
197
+ ```bash
198
+ $ npx dotenv-vault build
199
+ ```
200
+
201
+ Fetch your production `DOTENV_KEY`.
202
+
203
+ ```bash
204
+ $ npx dotenv-vault keys production
205
+ ```
206
+
207
+ Set `DOTENV_KEY` on your server.
208
+
209
+ ```bash
210
+ # heroku example
211
+ heroku config:set DOTENV_KEY=dotenv://:key_1234…@dotenv.org/vault/.env.vault?environment=production
212
+ ```
213
+
214
+ That's it! On deploy, your `.env.vault` file will be decrypted and its secrets injected as environment variables – just in time.
215
+
216
+ *ℹ️ A note from [Mot](https://github.com/motdotla): Until recently, we did not have an opinion on how and where to store your secrets in production. We now strongly recommend generating a `.env.vault` file. It's the best way to prevent your secrets from being scattered across multiple servers and cloud providers – protecting you from breaches like the [CircleCI breach](https://techcrunch.com/2023/01/05/circleci-breach/). Also it unlocks interoperability WITHOUT native third-party integrations. Third-party integrations are [increasingly risky](https://coderpad.io/blog/development/heroku-github-breach/) to our industry. They may be the 'du jour' of today, but we imagine a better future.*
217
+
218
+ <a href="https://github.com/dotenv-org/dotenv-vault#-deploying">Learn more at dotenv-vault: Deploying</a>
219
+
220
+ ## 🌴 Manage Multiple Environments
221
+
222
+ Edit your production environment variables.
223
+
224
+ ```bash
225
+ $ npx dotenv-vault open production
226
+ ```
227
+
228
+ Regenerate your `.env.vault` file.
229
+
230
+ ```bash
231
+ $ npx dotenv-vault build
232
+ ```
233
+
234
+ *ℹ️ 🔐 Vault Managed vs 💻 Locally Managed: The above example, for brevity's sake, used the 🔐 Vault Managed solution to manage your `.env.vault` file. You can instead use the 💻 Locally Managed solution. [Read more here](https://github.com/dotenv-org/dotenv-vault#how-do-i-use--locally-managed-dotenv-vault). Our vision is that other platforms and orchestration tools adopt the `.env.vault` standard as they did the `.env` standard. We don't expect to be the only ones providing tooling to manage and generate `.env.vault` files.*
235
+
236
+ <a href="https://github.com/dotenv-org/dotenv-vault#-manage-multiple-environments">Learn more at dotenv-vault: Manage Multiple Environments</a>
237
+
238
+ ## 📚 Examples
239
+
240
+ See [examples](https://github.com/dotenv-org/examples) of using dotenv with various frameworks, languages, and configurations.
241
+
242
+ * [nodejs](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-nodejs)
243
+ * [nodejs (debug on)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-nodejs-debug)
244
+ * [nodejs (override on)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-nodejs-override)
245
+ * [nodejs (processEnv override)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-custom-target)
246
+ * [nodejs (DOTENV_KEY override)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-vault-custom-target)
247
+ * [esm](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-esm)
248
+ * [esm (preload)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-esm-preload)
249
+ * [typescript](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-typescript)
250
+ * [typescript parse](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-typescript-parse)
251
+ * [typescript config](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-typescript-config)
252
+ * [webpack](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-webpack)
253
+ * [webpack (plugin)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-webpack2)
254
+ * [react](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-react)
255
+ * [react (typescript)](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-react-typescript)
256
+ * [express](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-express)
257
+ * [nestjs](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-nestjs)
258
+ * [fastify](https://github.com/dotenv-org/examples/tree/master/usage/dotenv-fastify)
259
+
260
+ ## 📖 Documentation
261
+
262
+ Dotenv exposes four functions:
263
+
264
+ * `config`
265
+ * `parse`
266
+ * `populate`
267
+ * `decrypt`
268
+
269
+ ### Config
270
+
271
+ `config` will read your `.env` file, parse the contents, assign it to
272
+ [`process.env`](https://nodejs.org/docs/latest/api/process.html#process_process_env),
273
+ and return an Object with a `parsed` key containing the loaded content or an `error` key if it failed.
274
+
275
+ ```js
276
+ const result = dotenv.config()
277
+
278
+ if (result.error) {
279
+ throw result.error
280
+ }
281
+
282
+ console.log(result.parsed)
283
+ ```
284
+
285
+ You can additionally, pass options to `config`.
286
+
287
+ #### Options
288
+
289
+ ##### path
290
+
291
+ Default: `path.resolve(process.cwd(), '.env')`
292
+
293
+ Specify a custom path if your file containing environment variables is located elsewhere.
294
+
295
+ ```js
296
+ require('dotenv').config({ path: '/custom/path/to/.env' })
297
+ ```
298
+
299
+ ##### encoding
300
+
301
+ Default: `utf8`
302
+
303
+ Specify the encoding of your file containing environment variables.
304
+
305
+ ```js
306
+ require('dotenv').config({ encoding: 'latin1' })
307
+ ```
308
+
309
+ ##### debug
310
+
311
+ Default: `false`
312
+
313
+ Turn on logging to help debug why certain keys or values are not being set as you expect.
314
+
315
+ ```js
316
+ require('dotenv').config({ debug: process.env.DEBUG })
317
+ ```
318
+
319
+ ##### override
320
+
321
+ Default: `false`
322
+
323
+ Override any environment variables that have already been set on your machine with values from your .env file.
324
+
325
+ ```js
326
+ require('dotenv').config({ override: true })
327
+ ```
328
+
329
+ ##### processEnv
330
+
331
+ Default: `process.env`
332
+
333
+ Specify an object to write your secrets to. Defaults to `process.env` environment variables.
334
+
335
+ ```js
336
+ const myObject = {}
337
+ require('dotenv').config({ processEnv: myObject })
338
+
339
+ console.log(myObject) // values from .env or .env.vault live here now.
340
+ console.log(process.env) // this was not changed or written to
341
+ ```
342
+
343
+ ##### DOTENV_KEY
344
+
345
+ Default: `process.env.DOTENV_KEY`
346
+
347
+ Pass the `DOTENV_KEY` directly to config options. Defaults to looking for `process.env.DOTENV_KEY` environment variable. Note this only applies to decrypting `.env.vault` files. If passed as null or undefined, or not passed at all, dotenv falls back to its traditional job of parsing a `.env` file.
348
+
349
+ ```js
350
+ require('dotenv').config({ DOTENV_KEY: 'dotenv://:key_1234…@dotenv.org/vault/.env.vault?environment=production' })
351
+ ```
352
+
353
+ ### Parse
354
+
355
+ The engine which parses the contents of your file containing environment
356
+ variables is available to use. It accepts a String or Buffer and will return
357
+ an Object with the parsed keys and values.
358
+
359
+ ```js
360
+ const dotenv = require('dotenv')
361
+ const buf = Buffer.from('BASIC=basic')
362
+ const config = dotenv.parse(buf) // will return an object
363
+ console.log(typeof config, config) // object { BASIC : 'basic' }
364
+ ```
365
+
366
+ #### Options
367
+
368
+ ##### debug
369
+
370
+ Default: `false`
371
+
372
+ Turn on logging to help debug why certain keys or values are not being set as you expect.
373
+
374
+ ```js
375
+ const dotenv = require('dotenv')
376
+ const buf = Buffer.from('hello world')
377
+ const opt = { debug: true }
378
+ const config = dotenv.parse(buf, opt)
379
+ // expect a debug message because the buffer is not in KEY=VAL form
380
+ ```
381
+
382
+ ### Populate
383
+
384
+ The engine which populates the contents of your .env file to `process.env` is available for use. It accepts a target, a source, and options. This is useful for power users who want to supply their own objects.
385
+
386
+ For example, customizing the source:
387
+
388
+ ```js
389
+ const dotenv = require('dotenv')
390
+ const parsed = { HELLO: 'world' }
391
+
392
+ dotenv.populate(process.env, parsed)
393
+
394
+ console.log(process.env.HELLO) // world
395
+ ```
396
+
397
+ For example, customizing the source AND target:
398
+
399
+ ```js
400
+ const dotenv = require('dotenv')
401
+ const parsed = { HELLO: 'universe' }
402
+ const target = { HELLO: 'world' } // empty object
403
+
404
+ dotenv.populate(target, parsed, { override: true, debug: true })
405
+
406
+ console.log(target) // { HELLO: 'universe' }
407
+ ```
408
+
409
+ #### options
410
+
411
+ ##### Debug
412
+
413
+ Default: `false`
414
+
415
+ Turn on logging to help debug why certain keys or values are not being populated as you expect.
416
+
417
+ ##### override
418
+
419
+ Default: `false`
420
+
421
+ Override any environment variables that have already been set.
422
+
423
+ ### Decrypt
424
+
425
+ The engine which decrypts the ciphertext contents of your .env.vault file is available for use. It accepts a ciphertext and a decryption key. It uses AES-256-GCM encryption.
426
+
427
+ For example, decrypting a simple ciphertext:
428
+
429
+ ```js
430
+ const dotenv = require('dotenv')
431
+ const ciphertext = 's7NYXa809k/bVSPwIAmJhPJmEGTtU0hG58hOZy7I0ix6y5HP8LsHBsZCYC/gw5DDFy5DgOcyd18R'
432
+ const decryptionKey = 'ddcaa26504cd70a6fef9801901c3981538563a1767c297cb8416e8a38c62fe00'
433
+
434
+ const decrypted = dotenv.decrypt(ciphertext, decryptionKey)
435
+
436
+ console.log(decrypted) // # development@v6\nALPHA="zeta"
437
+ ```
438
+
439
+ ## FAQ
440
+
441
+ ### Why is the `.env` file not loading my environment variables successfully?
442
+
443
+ Most likely your `.env` file is not in the correct place. [See this stack overflow](https://stackoverflow.com/questions/42335016/dotenv-file-is-not-loading-environment-variables).
444
+
445
+ Turn on debug mode and try again..
446
+
447
+ ```js
448
+ require('dotenv').config({ debug: true })
449
+ ```
450
+
451
+ You will receive a helpful error outputted to your console.
452
+
453
+ ### Should I commit my `.env` file?
454
+
455
+ No. We **strongly** recommend against committing your `.env` file to version
456
+ control. It should only include environment-specific values such as database
457
+ passwords or API keys. Your production database should have a different
458
+ password than your development database.
459
+
460
+ ### Should I have multiple `.env` files?
461
+
462
+ No. We **strongly** recommend against having a "main" `.env` file and an "environment" `.env` file like `.env.test`. Your config should vary between deploys, and you should not be sharing values between environments.
463
+
464
+ > In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as “environments”, but instead are independently managed for each deploy. This is a model that scales up smoothly as the app naturally expands into more deploys over its lifetime.
465
+ >
466
+ > – [The Twelve-Factor App](http://12factor.net/config)
467
+
468
+ ### What rules does the parsing engine follow?
469
+
470
+ The parsing engine currently supports the following rules:
471
+
472
+ - `BASIC=basic` becomes `{BASIC: 'basic'}`
473
+ - empty lines are skipped
474
+ - lines beginning with `#` are treated as comments
475
+ - `#` marks the beginning of a comment (unless when the value is wrapped in quotes)
476
+ - empty values become empty strings (`EMPTY=` becomes `{EMPTY: ''}`)
477
+ - inner quotes are maintained (think JSON) (`JSON={"foo": "bar"}` becomes `{JSON:"{\"foo\": \"bar\"}"`)
478
+ - whitespace is removed from both ends of unquoted values (see more on [`trim`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)) (`FOO= some value ` becomes `{FOO: 'some value'}`)
479
+ - single and double quoted values are escaped (`SINGLE_QUOTE='quoted'` becomes `{SINGLE_QUOTE: "quoted"}`)
480
+ - single and double quoted values maintain whitespace from both ends (`FOO=" some value "` becomes `{FOO: ' some value '}`)
481
+ - double quoted values expand new lines (`MULTILINE="new\nline"` becomes
482
+
483
+ ```
484
+ {MULTILINE: 'new
485
+ line'}
486
+ ```
487
+
488
+ - backticks are supported (`` BACKTICK_KEY=`This has 'single' and "double" quotes inside of it.` ``)
489
+
490
+ ### What happens to environment variables that were already set?
491
+
492
+ By default, we will never modify any environment variables that have already been set. In particular, if there is a variable in your `.env` file which collides with one that already exists in your environment, then that variable will be skipped.
493
+
494
+ If instead, you want to override `process.env` use the `override` option.
495
+
496
+ ```javascript
497
+ require('dotenv').config({ override: true })
498
+ ```
499
+
500
+ ### How come my environment variables are not showing up for React?
501
+
502
+ Your React code is run in Webpack, where the `fs` module or even the `process` global itself are not accessible out-of-the-box. `process.env` can only be injected through Webpack configuration.
503
+
504
+ If you are using [`react-scripts`](https://www.npmjs.com/package/react-scripts), which is distributed through [`create-react-app`](https://create-react-app.dev/), it has dotenv built in but with a quirk. Preface your environment variables with `REACT_APP_`. See [this stack overflow](https://stackoverflow.com/questions/42182577/is-it-possible-to-use-dotenv-in-a-react-project) for more details.
505
+
506
+ If you are using other frameworks (e.g. Next.js, Gatsby...), you need to consult their documentation for how to inject environment variables into the client.
507
+
508
+ ### Can I customize/write plugins for dotenv?
509
+
510
+ Yes! `dotenv.config()` returns an object representing the parsed `.env` file. This gives you everything you need to continue setting values on `process.env`. For example:
511
+
512
+ ```js
513
+ const dotenv = require('dotenv')
514
+ const variableExpansion = require('dotenv-expand')
515
+ const myEnv = dotenv.config()
516
+ variableExpansion(myEnv)
517
+ ```
518
+
519
+ ### How do I use dotenv with `import`?
520
+
521
+ Simply..
522
+
523
+ ```javascript
524
+ // index.mjs (ESM)
525
+ import 'dotenv/config' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
526
+ import express from 'express'
527
+ ```
528
+
529
+ A little background..
530
+
531
+ > When you run a module containing an `import` declaration, the modules it imports are loaded first, then each module body is executed in a depth-first traversal of the dependency graph, avoiding cycles by skipping anything already executed.
532
+ >
533
+ > – [ES6 In Depth: Modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/)
534
+
535
+ What does this mean in plain language? It means you would think the following would work but it won't.
536
+
537
+ `errorReporter.mjs`:
538
+ ```js
539
+ import { Client } from 'best-error-reporting-service'
540
+
541
+ export default new Client(process.env.API_KEY)
542
+ ```
543
+ `index.mjs`:
544
+ ```js
545
+ // Note: this is INCORRECT and will not work
546
+ import * as dotenv from 'dotenv'
547
+ dotenv.config()
548
+
549
+ import errorReporter from './errorReporter.mjs'
550
+ errorReporter.report(new Error('documented example'))
551
+ ```
552
+
553
+ `process.env.API_KEY` will be blank.
554
+
555
+ Instead, `index.mjs` should be written as..
556
+
557
+ ```js
558
+ import 'dotenv/config'
559
+
560
+ import errorReporter from './errorReporter.mjs'
561
+ errorReporter.report(new Error('documented example'))
562
+ ```
563
+
564
+ Does that make sense? It's a bit unintuitive, but it is how importing of ES6 modules work. Here is a [working example of this pitfall](https://github.com/dotenv-org/examples/tree/master/dotenv-es6-import-pitfall).
565
+
566
+ There are two alternatives to this approach:
567
+
568
+ 1. Preload dotenv: `node --require dotenv/config index.js` (_Note: you do not need to `import` dotenv with this approach_)
569
+ 2. Create a separate file that will execute `config` first as outlined in [this comment on #133](https://github.com/motdotla/dotenv/issues/133#issuecomment-255298822)
570
+
571
+ ### Why am I getting the error `Module not found: Error: Can't resolve 'crypto|os|path'`?
572
+
573
+ You are using dotenv on the front-end and have not included a polyfill. Webpack < 5 used to include these for you. Do the following:
574
+
575
+ ```bash
576
+ npm install node-polyfill-webpack-plugin
577
+ ```
578
+
579
+ Configure your `webpack.config.js` to something like the following.
580
+
581
+ ```js
582
+ require('dotenv').config()
583
+
584
+ const path = require('path');
585
+ const webpack = require('webpack')
586
+
587
+ const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
588
+
589
+ module.exports = {
590
+ mode: 'development',
591
+ entry: './src/index.ts',
592
+ output: {
593
+ filename: 'bundle.js',
594
+ path: path.resolve(__dirname, 'dist'),
595
+ },
596
+ plugins: [
597
+ new NodePolyfillPlugin(),
598
+ new webpack.DefinePlugin({
599
+ 'process.env': {
600
+ HELLO: JSON.stringify(process.env.HELLO)
601
+ }
602
+ }),
603
+ ]
604
+ };
605
+ ```
606
+
607
+ Alternatively, just use [dotenv-webpack](https://github.com/mrsteele/dotenv-webpack) which does this and more behind the scenes for you.
608
+
609
+ ### What about variable expansion?
610
+
611
+ Try [dotenv-expand](https://github.com/motdotla/dotenv-expand)
612
+
613
+ ### What about syncing and securing .env files?
614
+
615
+ Use [dotenv-vault](https://github.com/dotenv-org/dotenv-vault)
616
+
617
+ ### What is a `.env.vault` file?
618
+
619
+ A `.env.vault` file is an encrypted version of your development (and ci, staging, production, etc) environment variables. It is paired with a `DOTENV_KEY` to deploy your secrets more securely than scattering them across multiple platforms and tools. Use [dotenv-vault](https://github.com/dotenv-org/dotenv-vault) to manage and generate them.
620
+
621
+ ## Contributing Guide
622
+
623
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
624
+
625
+ ## CHANGELOG
626
+
627
+ See [CHANGELOG.md](CHANGELOG.md)
628
+
629
+ ## Who's using dotenv?
630
+
631
+ [These npm modules depend on it.](https://www.npmjs.com/browse/depended/dotenv)
632
+
633
+ Projects that expand it often use the [keyword "dotenv" on npm](https://www.npmjs.com/search?q=keywords:dotenv).