react 0.0.0-f42431abe → 0.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.
package/LICENSE CHANGED
@@ -1,6 +1,4 @@
1
- MIT License
2
-
3
- Copyright (c) Facebook, Inc. and its affiliates.
1
+ Copyright (c) 2011 Jeff Barczewski
4
2
 
5
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
4
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +7,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
7
  copies of the Software, and to permit persons to whom the Software is
10
8
  furnished to do so, subject to the following conditions:
11
9
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
14
12
 
15
13
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
14
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
15
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
16
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
17
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,148 @@
1
- # `react`
1
+ # React.js
2
2
 
3
- React is a JavaScript library for creating user interfaces.
3
+ React is a javascript module to make it easier to work with asynchronous code,
4
+ by reducing boilerplate code and improving error and exception handling while
5
+ allowing variable and task dependencies when defining flow.
4
6
 
5
- The `react` package contains only the functionality necessary to define React components. It is typically used together with a React renderer like `react-dom` for the web, or `react-native` for the native environments.
7
+ This async flow control module is initially designed to work with Node.js but
8
+ is planned to be extended to browser and other environments.
6
9
 
7
- **Note:** by default, React will be in development mode. The development version includes extra warnings about common mistakes, whereas the production version includes extra performance optimizations and strips all error messages. Don't forget to use the [production build](https://reactjs.org/docs/optimizing-performance.html#use-the-production-build) when deploying your application.
10
+ It takes inspiration from several projects including:
8
11
 
9
- ## Example Usage
12
+ - Tim Caswell and Elijah Insua's [conductor](https://github.com/creationix/conductor) - [Article](http://howtonode.org/step-of-conductor)
13
+ - Caolan McMahon's [async](https://github.com/caolan/async)
10
14
 
11
- ```js
12
- var React = require('react');
13
- ```
15
+ React gets its name from similarities with how "chain reactions" work in the physical world. You start the reaction and then it cascades and continues until complete.
16
+
17
+ ## Goals
18
+
19
+ - Improved error and exception handling
20
+ - Provide useful stack traces and context information for easier debugging
21
+ - Minimize boilerplate code needed for working with asynchronous functions
22
+ - Make code more readable and easier to understand which should translate to less defects
23
+ - Provide the right level of abstraction to make it easier to refactor code, without being too magical
24
+ - Allow the mixing of pure functions, method calls, and callback style functions in the flow
25
+ - Minimize the need to customize your code simply to use async flow control. The use of a flow control module ideally should not affect the way you write your code, it should only help take over some of the burden.
26
+
27
+ ## Concept
28
+
29
+ Borrowing heavily from Tim and Elijah's ideas for conductor, this async flow control module provides a way to construct a flow from a collection of functions or methods (referred to as _tasks_ in this module). It allows dependencies to be defined between the tasks so they can run in parallel as their dependencies are satisfied. React can us both variable dependencies and task dependencies.
30
+
31
+ As tasks complete, React watches the dependencies and kicks off additional tasks that have all their dependencies met and are ready to execute. This allows the flow to run at maximum speed without needing to arbitrarily block tasks into groups of parallel and serial flow.
32
+
33
+ To reduce the boilerplate code needed and improve error handling, React automatically provides callback functions for your asynchronous code. These React-provided callback functions perform these steps:
34
+
35
+ 1. check for error and handle by calling outer callback function with this error after augmenting it with additional context information for easier debugging
36
+ 2. save the callback variables into a context for future reference
37
+ 3. call back into React (and it will kick off additional tasks that are now ready to go)
38
+
39
+ ## Design
40
+
41
+ - Optional parse step to create flow AST (TODO allow pluggable parsers to allow many interfaces)
42
+ - Validate the flow AST - determine if dependencies can all be met as defined such that flow will complete (TODO)
43
+ - Execute the flow AST
44
+
45
+ ## Installing
46
+
47
+ npm install react
48
+
49
+ OR
50
+
51
+ Pull from github - http://github.com/jeffbski/react
52
+
53
+ ## Examples
54
+
55
+ 1. [Direct AST](#directAST)
56
+ 2. [Using Simple DSL](#simpleDSL)
57
+
58
+ <a name="directAST"/>
59
+ ### Example directly using AST
60
+
61
+ var react = require('react').react;
62
+
63
+ function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
64
+ function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
65
+ function markdown(filedata) { return 'html'+filedata; }
66
+ function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
67
+ function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
68
+ function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
69
+ function customizeEmail(user, emailHtml, cb) { return 'cust-'+user+emailHtml; }
70
+ function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
71
+
72
+ function useHtml(err, html, user, bytesWritten) {
73
+ if(err) {
74
+ console.log('***Error: %s', err);
75
+ return;
76
+ }
77
+ console.log('final result: %s, user: %s, written:%s', html, user, bytesWritten);
78
+ }
79
+
80
+ var r = react();
81
+ r.ast.inputNames = ['filename', 'uid', 'outDirname', 'cb'];
82
+ r.ast.taskDefs = [
83
+ { f:loadUser, a:['uid'], cb:['user'] },
84
+ { f:loadFile, a:['filename'], cb:['filedata'] },
85
+ { f:markdown, a:['filedata'], ret:['html'] },
86
+ { f:prepareDirectory, a:['outDirname'], cb:['dircreated'] },
87
+ { f:writeOutput, a:['html', 'user'], cb:['bytesWritten'], after:['prepareDirectory'] },
88
+ { f:loadEmailTemplate, a:[], cb:['emailmd'] },
89
+ { f:markdown, a:['emailmd'], ret:['emailHtml'] },
90
+ { f:customizeEmail, a:['user', 'emailHtml'], ret:['custEmailHtml'] },
91
+ { f:deliverEmail, a:['custEmailHtml'], cb:['deliveredEmail'], after:['writeOutput'] }
92
+ ];
93
+ r.ast.finalOutputNames = ['html', 'user', 'bytesWritten'];
94
+
95
+ r.exec("hello.txt", 100, 'outHello', useHtml);
96
+ r.exec("small.txt", 200, 'outSmall', useHtml);
97
+
98
+ <a name="simpleDSL"/>
99
+ ### Example using simple DSL interface
100
+
101
+ var react = require('react').react;
102
+
103
+ function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
104
+ function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
105
+ function markdown(filedata) { return 'html'+filedata; }
106
+ function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
107
+ function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
108
+ function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
109
+ function customizeEmail(user, emailHtml, cb) { return 'cust-'+user+emailHtml; }
110
+ function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
111
+
112
+ function useHtml(err, html, user, bytesWritten) {
113
+ if(err) {
114
+ console.log('***Error: %s', err);
115
+ return;
116
+ }
117
+ console.log('final result: %s, user: %s, written:%s', html, user, bytesWritten);
118
+ }
119
+
120
+ var r = react('filename, uid, outDirname, cb').define(
121
+ loadUser, 'uid -> err, user',
122
+ loadFile, 'filename -> err, filedata',
123
+ markdown, 'filedata -> returns html',
124
+ prepareDirectory, 'outDirname -> err, dircreated',
125
+ writeOutput, 'html, user -> err, bytesWritten', { after:prepareDirectory },
126
+ loadEmailTemplate,' -> err, emailmd',
127
+ markdown, 'emailmd -> returns emailHtml',
128
+ customizeEmail, 'user, emailHtml -> returns custEmailHtml',
129
+ deliverEmail, 'custEmailHtml -> err, deliveredEmail', { after: writeOutput }
130
+ ).callbackDef('err, html, user, bytesWritten');
131
+
132
+ ## Status
133
+
134
+ - 2011-10-26 - React is in active development and interface may change frequently in these early stages. Current code is functional but does not perform validation yet. Additional interfaces are planned to make it easy to define flows in a variety of ways. Documentation and examples forthcoming.
135
+
136
+ ## License
137
+
138
+ - [MIT license](http://github.com/jeffbski/react/raw/master/LICENSE)
139
+
140
+ ## Contributors
141
+
142
+ - Author: Jeff Barczewski (@jeffbski)
143
+
144
+ ## Contributing
145
+
146
+ - Source code repository: http://github.com/jeffbski/react
147
+ - Ideas and pull requests are encouraged - http://github.com/jeffbski/react/issues
148
+ - You may contact me at @jeffbski or through github at http://github.com/jeffbski
package/lib/react.js ADDED
@@ -0,0 +1,254 @@
1
+ 'use strict';
2
+
3
+ var util = require('util');
4
+
5
+ var reactOptions = {
6
+ debugOutput: false,
7
+ stackTraceLimitMin: 30
8
+ };
9
+
10
+
11
+
12
+ /**
13
+ @example
14
+ var reactMod = require('react');
15
+ reactMod.reactOptions.debugOutput = true;
16
+ var react = reactMod.react;
17
+
18
+ var r = react('filename, uid, cb').define(
19
+ loadUser, 'uid -> err, user',
20
+ loadFile, 'filename -> err, filedata',
21
+ markdown, 'filedata -> returns html',
22
+ prepareDirectory, 'outDirname -> err, dircreated',
23
+ sendOutput, 'html, user -> err, html, bytesWritten', {after:prepareDirectory}
24
+ ).callbackDef('err, html, user, bytesWritten');
25
+
26
+ r.exec(filename, uid, function(err, html, user, bytesWritten){
27
+ //use html
28
+ });
29
+
30
+ */
31
+
32
+ function fName(fn){
33
+ return (typeof(fn) === 'string') ? fn : fn.name;
34
+ }
35
+
36
+ function ensureStackTraceLimitSet(){
37
+ if (!Error.stackTraceLimit || Error.stackTraceLimit < reactOptions.stackTraceLimitMin) {
38
+ Error.stackTraceLimit = reactOptions.stackTraceLimitMin;
39
+ }
40
+ }
41
+
42
+ function formatErrorMeta(err){
43
+ if (!err.meta) return;
44
+ var vcon = err.meta.vcon;
45
+ var task = err.meta.task;
46
+ return '\n\n' +
47
+ 'Error occurs in Task function: ' + fName(task.f) + '(' + task.a.join(',') + ')\n\n' +
48
+ 'Variable Context: \n' +
49
+ util.inspect(vcon) + '\n\n' +
50
+ 'Task Source:\n\n' +
51
+ task.f.toString() + '\n\n'; //TODO need to pretty print function, gets collapsed
52
+ }
53
+
54
+ function augmentError(err, meta){
55
+ if (typeof(err) === 'string' ) { err = new Error(err); } //props will be lost on non-objects
56
+ var origMsg = err.toString();
57
+ err.meta = meta;
58
+ err.toString = function() { return origMsg + formatErrorMeta(err); };
59
+ return err;
60
+ }
61
+
62
+ function splitTrimFilterArgs(commaSepArgs){ //parse 'one, two' into ['one', 'two']
63
+ return commaSepArgs.split( ',' ) //split on commas
64
+ .map(function(s){ return s.trim(); }) //trim
65
+ .filter(function(s){ return (s); }); //filter out empty strings
66
+ }
67
+
68
+ function parseInOutDef(inOutDef){ //'a, b -> err, c, d' into { inDef: ['a','b'], outDef: ['c', 'd'] }
69
+ var match = /^([^-]*)(->)?\s*(er{0,2}\s*,)?(.*)$/.exec(inOutDef);
70
+ if (match) {
71
+ return { inDef: splitTrimFilterArgs(match[1]),
72
+ outDef: splitTrimFilterArgs(match[4]) };
73
+ }
74
+ throw "error parsing in/out def: "+inOutDef;
75
+ }
76
+
77
+ function nameTasks(tasks){ //name tasks that are not already named, validation done elsewhere, ret map
78
+ var namesMap = tasks.reduce(function(map, t){
79
+ if (t.name) { map[t.name] = t; }
80
+ return map; }, {});
81
+ tasks.forEach(function(t, idx){
82
+ if (!t.name) { //not already named
83
+ var name = fName(t.f);
84
+ if (!name || namesMap[name]) name = ''+name+'_'+idx; //if empty or already used, postfix with _idx
85
+ t.name = name;
86
+ namesMap[name] = t;
87
+ }
88
+ });
89
+ return namesMap;
90
+ }
91
+
92
+ function parseTaskDefs(arrArgs){ // [fun, strArgsCbArgs, optObj]
93
+ var taskDefs = [];
94
+ var CHECK_RETURNS_RE = /^returns?\s+(\w+)\s*;?$/;
95
+ while(arrArgs.length){
96
+ var fn = arrArgs.shift();
97
+ var strArgsCbArgs = arrArgs.shift();
98
+ var optObj = (typeof(arrArgs[0]) === 'object') ? arrArgs.shift() : { };
99
+ if (typeof(strArgsCbArgs) !== 'string') throw "eror parsing taskdef, expected str, got:"+strArgsCbArgs;
100
+ var inOutDef = parseInOutDef(strArgsCbArgs);
101
+ var taskDef = { f:fn, a:inOutDef.inDef, cb:inOutDef.outDef };
102
+ Object.keys(optObj).forEach(function(k){ taskDef[k] = optObj[k]; })
103
+ if (taskDef.after) {
104
+ if (!Array.isArray(taskDef.after)) { taskDef.after = [taskDef.after]; } //ensure arr
105
+ taskDef.after = taskDef.after.map(function(a){ return fName(a); });
106
+ }
107
+ var matchReturn = CHECK_RETURNS_RE.exec(taskDef.cb[0]);
108
+ if (matchReturn) { // found return(s) varname, so change this to return type fn
109
+ delete taskDef.cb; taskDef.ret = matchReturn[1]; } // del cb, add ret:varname
110
+ taskDefs.push( taskDef );
111
+ }
112
+ return taskDefs;
113
+ }
114
+
115
+
116
+ function react(inputDef){
117
+ ensureStackTraceLimitSet();
118
+ inputDef = Array.prototype.slice.call(arguments).join(', '); //convert 'a', 'b', 'c' into 'a, b, c'
119
+ var reactObj;
120
+ var inOutDef = parseInOutDef(inputDef);
121
+ var ast = { inputNames: inOutDef.inDef,
122
+ finalOutputNames: inOutDef.outDef, //this might be set later
123
+ taskDefs: [] }; //set in define()
124
+ var STATUS = { READY: 'ready', RUNNING: 'running', ERRORED: 'errored', COMPLETE: 'complete' };
125
+
126
+ function define(arg1, arg2, argN){
127
+ ast.taskDefs = ast.taskDefs.concat(parseTaskDefs(Array.prototype.slice.call(arguments)));
128
+ nameTasks(ast.taskDefs); //set names in ast.taskDefs so that ast can be inspected before exec
129
+ return reactObj;
130
+ }
131
+
132
+ function callbackDef(argDef){ //define the callback output names
133
+ argDef = Array.prototype.slice.call(arguments).join(', '); //convert 'a', 'b', 'c' into 'a, b, c'
134
+ argDef = ( argDef && /^\s*->/.test(argDef)) ? argDef : '-> '+argDef; //prefix with -> before parse
135
+ var inOutDef = parseInOutDef(argDef); //should be '-> a,b,c'
136
+ ast.finalOutputNames = inOutDef.outDef;
137
+ return reactObj;
138
+ }
139
+
140
+ function exec(arg1, arg2, argN){
141
+ var args = Array.prototype.slice.call(arguments);
142
+ var cbFinal = args.pop(); //pop off final callback from end
143
+ var vCon = { }; //create variable context
144
+ args.forEach(function(x, idx){ vCon[ast.inputNames[idx]] = x; });
145
+ var firstError; //will be set to the err of first task that errors
146
+ var contExec; //function defined later
147
+ var tasksByName = {}; //set later, by calling nameTasks
148
+
149
+ function handleTaskError(task, err){
150
+ task.status = STATUS.ERRORED;
151
+ if (!firstError) { //no prev error, only calling final callback with error once
152
+ var errWithMeta = augmentError(err, {task:task, vcon:vCon});
153
+ firstError = errWithMeta; //save this, stop other tasks from being launched
154
+ cbFinal.call(null, errWithMeta); //call the final callback with the first error hit
155
+ }
156
+ }
157
+
158
+ function createCallback(task) {
159
+ return function(err, arg0, arg1, argn){
160
+ var args = Array.prototype.slice.call(arguments,1);
161
+ if(err){ handleTaskError(task, err); return; } //handle error and return, we are done
162
+
163
+ //no error, save callback args to vCon context, then continue execution
164
+ task.cb.forEach(function(k, idx){ //save cb args to v context
165
+ vCon[k] = (args[idx] !== undefined) ? args[idx] : null; //upgrade any undefined to null
166
+ });
167
+ task.status = STATUS.COMPLETE;
168
+ if (reactOptions.debugOutput) console.log('in callback: %s cb:', fName(task.f), args, vCon);
169
+ contExec();
170
+ };
171
+ }
172
+
173
+ var tasks = ast.taskDefs.map(function(ot){ //create working task copies
174
+ var t = Object.create(ot);
175
+ if(t.cb) t.cbFun = createCallback(t); //if is callback type fn, create callback
176
+ return t;
177
+ });
178
+ tasksByName = nameTasks(tasks); //remap names to exec task copies instead of taskDefs
179
+
180
+ function getVar(name) { //get the name from the variable context, name might be simple or obj.prop
181
+ var nameAndProps = name.split('.');
182
+ return nameAndProps.reduce(function(accObj, prop){ return accObj[prop]; }, vCon); // vCon['foo']['bar']
183
+ }
184
+
185
+
186
+ function execTask(t){
187
+ t.status = STATUS.RUNNING;
188
+ var args = t.a.map(function(k){ return getVar(k); }); //get args from vCon
189
+ if (t.cbFun) args.push(t.cbFun); //push custom callback to back if fn uses cb
190
+ if (reactOptions.debugOutput) console.log('starting task: %s', fName(t.f), args);
191
+ try {
192
+ var func;
193
+ var bindObj = null; //start as global object
194
+ if (typeof(t.f) === 'string') { //object method call
195
+ var match = /(\w+)\.(\w+)/.exec(t.f);
196
+ if (match) {
197
+ var objName = match[1];
198
+ var methName = match[2];
199
+ bindObj = vCon[objName];
200
+ func = bindObj[methName];
201
+ }
202
+ if (!func) throw new Error('Object or method not found: '+t.f);
203
+ } else { //function call
204
+ func = t.f;
205
+ }
206
+ var ret = func.apply(bindObj, args);
207
+ if (t.ret) { //if non-cb fn/method,
208
+ vCon[t.ret] = ret; // save retval
209
+ t.status = STATUS.COMPLETE; // mark complete
210
+ contExec(); // continue since no callback to run this
211
+ }
212
+ } catch (e) { handleTaskError(t, e); } //catch and handle the task error, calling final cb
213
+ }
214
+
215
+ function stripProp(objectWithOptionalPropertyName) { //return only object if object.property
216
+ return ( /\w+/ ).exec(objectWithOptionalPropertyName)[0];
217
+ }
218
+
219
+ function isTaskReady(t, idx, arr){
220
+ return !t.status && // filter for not started AND
221
+ t.a.every(function(k){ return (vCon[stripProp(k)] !== undefined); }) && // all dep vars defined AND
222
+ (!t.after || // (no dep tasks OR
223
+ t.after.every( function(n){ return tasksByName[n].status === STATUS.COMPLETE; })); //alldone
224
+ }
225
+
226
+ contExec = function contExec(){
227
+ if (firstError) { return; } //stop execution, we already hit an error
228
+ if (tasks.every(function(t){ return (t.status === STATUS.COMPLETE); })) { //all completed
229
+ //we are done, call final callback
230
+ var finalArgs = ast.finalOutputNames.map(function(k){ return vCon[k]; });
231
+ finalArgs.unshift(null); //unshift err=null to front
232
+ cbFinal.apply(null, finalArgs);
233
+ return;
234
+ }
235
+ var tasksReady = tasks.filter(isTaskReady); //if we are here then we stil have tasks to run
236
+ tasksReady.forEach(function(t){ t.status = STATUS.READY; }); //set ready before call, no double exec
237
+ tasksReady.forEach(function(t){ execTask(t); });
238
+ };
239
+ contExec(); //now kick off the execution for exec()
240
+ }
241
+
242
+ reactObj = {
243
+ define: define,
244
+ callbackDef: callbackDef,
245
+ exec: exec,
246
+ ast: ast,
247
+ };
248
+
249
+ return reactObj;
250
+ }
251
+
252
+ exports.react = react;
253
+ exports.reactOptions = reactOptions;
254
+
package/package.json CHANGED
@@ -1,38 +1,13 @@
1
1
  {
2
2
  "name": "react",
3
- "description": "React is a JavaScript library for building user interfaces.",
4
- "keywords": [
5
- "react"
6
- ],
7
- "version": "0.0.0-f42431abe",
8
- "homepage": "https://reactjs.org/",
9
- "bugs": "https://github.com/facebook/react/issues",
10
- "license": "MIT",
11
- "files": [
12
- "LICENSE",
13
- "README.md",
14
- "build-info.json",
15
- "index.js",
16
- "cjs/",
17
- "umd/"
18
- ],
19
- "main": "index.js",
20
- "repository": {
21
- "type": "git",
22
- "url": "https://github.com/facebook/react.git",
23
- "directory": "packages/react"
24
- },
25
- "engines": {
26
- "node": ">=0.10.0"
27
- },
28
- "dependencies": {
29
- "loose-envify": "^1.1.0",
30
- "object-assign": "^4.1.1",
31
- "prop-types": "^15.6.2"
32
- },
33
- "browserify": {
34
- "transform": [
35
- "loose-envify"
36
- ]
37
- }
3
+ "description": "React is a javascript module to make it easier to work with asynchronous code, by reducing boilerplate code and improving error and exception handling while allowing variable and task dependencies when defining flow.",
4
+ "version": "0.0.3",
5
+ "author": "Jeff Barczewski <jeff.barczewski@gmail.com>",
6
+ "repository": { "type": "git", "url": "http://github.com/jeffbski/react.git" },
7
+ "bugs" : { "url": "http://github.com/jeffbski/react/issues" },
8
+ "licenses": [{ "type": "MIT", "url" : "http://github.com/jeffbski/react/raw/master/LICENSE" }],
9
+ "main": "react",
10
+ "engines": { "node": "~v0.4.12" },
11
+ "dependencies": {},
12
+ "devDependencies": {}
38
13
  }