├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── assets └── orangesphere_web.gif ├── package-lock.json ├── package.json ├── src ├── Desolver.ts ├── ResolverBuilder.ts └── index.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ], 7 | "env": { 8 | "test": { 9 | "plugins": [ 10 | "@babel/plugin-transform-runtime" 11 | ] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # TypeScript cache 47 | *.tsbuildinfo 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Microbundle cache 56 | .rpt2_cache/ 57 | .rts2_cache_cjs/ 58 | .rts2_cache_es/ 59 | .rts2_cache_umd/ 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # Next.js build output 78 | .next 79 | 80 | # Nuxt.js build / generate output 81 | .nuxt 82 | dist 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | # Redis RDB 106 | *.rdb 107 | 108 | # DS Store 109 | .DS_store 110 | 111 | # compliled TS files 112 | /lib 113 | .npmignore -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | assets 4 | tsconfig.json 5 | .babelrc 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

DeSolver

6 |   7 | 8 | ## Table of Contents 9 | 10 | - [About](https://github.com/oslabs-beta/DeSolver/#about) 11 | - [Getting Started](https://github.com/oslabs-beta/DeSolver/#gettingstarted) 12 | - [How to Use](https://github.com/oslabs-beta/DeSolver/#howtouse) 13 | - [DeSolver Instance](https://github.com/oslabs-beta/DeSolver/#desolverinstance) 14 | - [Cache](https://github.com/oslabs-beta/DeSolver/#cache) 15 | - [Desolver Fragments](https://github.com/oslabs-beta/DeSolver/#desolverfragments) 16 | - [Creating Middleware Pipeline](https://github.com/oslabs-beta/DeSolver/#pipeline) 17 | - [Targeting a specific resolver type](https://github.com/oslabs-beta/DeSolver/#targetatype) 18 | - [Define resolvers as multiple fragments](https://github.com/oslabs-beta/DeSolver/#multiplefragments) 19 | - [Contributors](https://github.com/oslabs-beta/DeSolver/#team) 20 | - [License](https://github.com/oslabs-beta/DeSolver/#license) 21 | 22 |


23 |

24 | 25 | # About 26 | 27 | DeSolver for [GraphQL](https://graphql.org/): a lightweight, minimalist, unopinionated Node.js GraphQL framework providing a powerful yet approachable API for composing modular and reusable resolver business logic. DeSolver utilizes the middleware pattern as seen in other popular frameworks as a way to create "routing" for your resolvers. 28 | 29 | The DeSolver instance methods allow a pipeline to be loaded with mini-resolver functions, and a wrapper forms around the resolver map object, permitting a series of functions in a chain or "pre-hook" functions to execute prior to a query or mutation. This minimizes the need to wrap individual resolver functions manually, thereby reducing templating and additional boilerplate. Composing resolvers in this way can help keep resolver code more maintainable, scalable and testable. DeSolver follows the "write once, run everywhere" mantra. 30 | 31 |


32 | 33 |

34 | 35 | # Getting Started 36 | 37 | 1. Installing DeSolver 38 | 39 | - Start by running the npm command: 40 | 41 | ```javascript 42 | npm install desolver 43 | ``` 44 | 45 | - The DeSolver framework works best when combined with [GraphQL tools](https://www.graphql-tools.com/docs/generate-schema) Executable Schema package. It is recommended to download that package and use that to generate your type definitions, resolver map and schema. 46 | 47 | - The DeSolver framework is compatible with the popular Apollo Server API. DeSolver can be utilized when combined with the resolver map object in Apollo Server. 48 | 49 | - Redis is used for caching resolvers. Check out [Redis](https://redis.io) and [node-redis](https://github.com/redis/node-redis) for installation details. When not using Redis or when using custom caching logic, DeSolver provides a configuration option to disable this default behavior. 50 | 51 |


52 | 53 |

54 | 55 | # How to use 56 | 57 |

58 | 59 | ### **Desolver Instance and Configuration** 60 | 61 | In your GraphQL server or resolvers file create a new instance of DeSolver: 62 | 63 | ```javascript 64 | const desolver = new Desolver(desolverConfig) 65 | ``` 66 | 67 | The following optional configuration object can be declared as the argument of the DeSolver constructor: 68 | 69 | ```javascript 70 | const desolverConfig = { 71 | cacheDesolver?: boolean, // set to true to enable Redis caching 72 | applyResolverType?: string // resolver type to target for running prehooks 73 | } 74 | ``` 75 | 76 | - `cacheDesolver`: Set to `true` to enable Redis caching, by default if nothing is passed, the default redis instance will be started. Set to `false` to disable this behavior. 77 | 78 | The desolverConfig object can also be defined with configuration options from Redis. See [node-redis](https://github.com/redis/node-redis) for a list of additional custom configuration options that can be defined from Redis. 79 |

80 | 81 |

82 | 83 | ### **Cache** 84 | 85 | DeSolver utilizes Redis caching for greater query optimization. If `cacheDesolver` option is set to `true`, this will enable automated caching of resolver queries. DeSolver will automatically generate a unique key for the Redis cache based on path property from within the info argument. Set the `cacheDesolver` property to `false` if you would like to disable this default behavior and provide your own caching logic. 86 |

87 | 88 |

89 | 90 | ### **DeSolver Fragments** 91 | 92 | DeSolver Fragments are used as versions of middleware functions. Each resolver can be decomposed into a series of "fragment" functions. To maintain full functionality of a normal resolver, it provides the current field resolvers first four arguments (root/parent, arguments, context, and info), as well as three additional custom parameters (next, escape, and ds). 93 | 94 | ```javascript 95 | const desolverFragment = (parent, args, context, info, next, escapeHatch, ds) => { 96 | // write your resolver business logic here 97 | // parent, args, context and info are available for use here 98 | // ds context object is also provided by this framework 99 | 100 | // @return 101 | // next() - calls the next function in the middleware chain 102 | // escapeHatch(input) - pass a value to input to resolve immediately 103 | } 104 | ``` 105 | 106 | The DeSolver Parameters are as follows: 107 | 108 | The first four parameters are the normal parameters for any resolver: 109 | - `parent`: Sometimes referred to as the root object. The same parent/root object which is the result of the previous parent/type. 110 | - `arguments`: Arguments provided to the root or field resolver. 111 | - `context`: an object that is provided to all resolvers. 112 | - `info`: field specific information relevant to the query. 113 | 114 | The final three parameters are additional parameters provided by the DeSolver framework: 115 | - `next`: Calls the next DeSolver Fragment in the middleware chain. 116 | - `escapeHatch`: Immediately resolves the current root or field resolver. 117 | - `ds`: A context object that can be passed from one DeSolver Fragment to the next. Similar in functionality to res.locals in the Express framework. 118 |

119 |

120 | 121 | ### **Creating the middleware pipeline** 122 | 123 | Utilize the `desolver.use()` method on the desolver instance to generate your pipeline of prehook functions. 124 | 125 | The `desolver.use()` method has the following parameters: 126 | - `ResolverType`: A string which matches the Resolver Type in your Resolver Map object. Pass the string `'All'` if you want to chain the prehook functions to all your resolvers. 127 | - `DeSolverFragments`: Add any number of functions in the form of DeSolver Fragments to chain to the target Resolver Type. 128 | 129 | Multiple successive invocations of `desolver.use()` will also add additional functions to the pipeline. 130 | 131 | The following is an example use case for the desolver middleware pipeline involving guarding root queries with authentication logic: 132 | 133 | ```javascript 134 | const desolver = new Desolver() 135 | 136 | // Declare authentication Desolver Fragment Function 137 | const authentication = (parent, args, context, info, next, escapeHatch, ds) => { 138 | // Define some authentication logic here using args or context 139 | // throw error if not authenticated 140 | } 141 | 142 | // Add the authentication function to pipeline with desolver.use() 143 | // This function will execute prior to all Query resolvers 144 | desolver.use('Query', authentication) 145 | 146 | // Invoke desolver.apply() method with the resolver map object passed 147 | const resolvers = desolver.apply({ 148 | Query: { 149 | getUserById: (parent, args, context, info) => { 150 | // this root query is now guarded by the authentication function 151 | }, 152 | 153 | getPostsById: (parent, args, context, info) => { 154 | // this root query is now guarded by the authentication function 155 | }, 156 | } 157 | 158 | // Additional resolvers here 159 | }) 160 | ``` 161 |

162 |

163 | 164 | ### **Define resolvers as multiple DeSolver fragments** 165 | 166 | Defining root or field level resolvers as a chain of DeSolver fragments is a resolver function declared utilizing `desolver.useRoute()`. The `useRoute` method takes any number of Desolver Fragment middleware functions and forms a "route" for the root or field resolver. 167 | 168 | All methods `desolver.use()`, `desolver.apply()`, and `desolver.useRoute()` can be utilized together to form a more modular and scalable way to compose resolvers. 169 | 170 | See the example below: 171 | 172 | ```javascript 173 | // desolver.use(), desolver.apply() and desolver.useRoute() can 174 | // be used together to create longer chains 175 | 176 | desolver.use('Query', authentication) 177 | 178 | const resolvers = desolver.apply({ 179 | Query: { 180 | // Define your resolver using desolver.useRoute() and add 181 | // any number of desolver fragments to modularize resolver logic 182 | // The authentication function will execute followed by desolverFragment1 183 | // and desolverFragment2 184 | getUserById: desolver.useRoute(desolverFragment1, desolverFragment2), 185 | 186 | // desolver.use(), desolver.apply(), desolver.useRoute() and 187 | // normal resolver functions used together seamlessly throughout the 188 | // resolver map 189 | getPostsById: (parent, { id }, context, info) => { 190 | return ctx.db.findPosts(id) 191 | }, 192 | } 193 | }) 194 | ``` 195 |

196 | 197 |

198 | 199 | ### **Targeting a specific resolver type** 200 | 201 | To chain Desolver Fragments to a specific root type or field resolvers, multiple invocations of `desolver.use()` can be called with different Resolver Type targets. 202 | 203 | See the example below: 204 | 205 | ```javascript 206 | desolver.use('Query', authentication) 207 | 208 | desolver.use('Mutation', authentication, authorization) 209 | 210 | const resolvers = desolver.apply({ 211 | Query: { 212 | // Query resolvers are guarded only by authentication function 213 | getUserById: desolver.useRoute(desolverFragment1, desolverFragment2), 214 | 215 | getPostsById: (parent, { id }, context, info) => { 216 | return ctx.db.findPosts(id) 217 | }, 218 | } 219 | 220 | Mutation: { 221 | createUser: (parent, root, args, context, info) => { 222 | // This mutation resolver is now guarded by both authentication and authorization functions 223 | } 224 | } 225 | }) 226 | ``` 227 |


228 |

229 | 230 | # **Contributors** 231 | 232 | DeSolver is an open-source community project on Github and accelerated by [OS Labs](https://opensourcelabs.io/). We are maintained by a small group of dedicated software engineers. We appreciate your participation, feedback, bug fixes and feature developments. 233 | 234 |


235 | 236 | | | GitHub | LinkedIn | 237 | | ------------------- | -------------------------------- | ----------------------------------------------- | 238 | | Michael Chan | https://github.com/mckchan13 | https://www.linkedin.com/in/michael-ck-chan/ | 239 | | Mia Kang | https://github.com/jcmiakang | https://www.linkedin.com/in/mia-kang/ | 240 | | Alexander Gurfinkel | https://github.com/AlexGurfinkel | https://www.linkedin.com/in/alexandergurfinkel/ | 241 | | Julia Hickey | https://github.com/hijulia1136 | https://www.linkedin.com/in/juliahickey/ | 242 | 243 |


244 | 245 | If you are interested in creating an open-source project that builds on top of DeSolver, please don't hesitate to reach out, and we'd be happy to provide feedback and support here or via [DeSolver LinkedIn](https://www.linkedin.com/company/desolver/). 246 | 247 |


248 |

249 | 250 | # License 251 | 252 | This product is licensed under the MIT License - see the LICENSE.md file for details. 253 | 254 | This is an open source product. 255 | 256 | This product is accelerated by [OS Labs](https://github.com/oslabs-beta). 257 | -------------------------------------------------------------------------------- /assets/orangesphere_web.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/DeSolver/215ca2e71df467e0e61f0d71605c98eaac3cb920/assets/orangesphere_web.gif -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "desolver", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "desolver", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@types/node": "^17.0.34" 13 | }, 14 | "devDependencies": { 15 | "@types/uuid": "^8.3.4", 16 | "graphql": "^16.5.0", 17 | "redis": "^4.1.0", 18 | "ts-node": "^10.8.1", 19 | "typescript": "^4.6.4", 20 | "uuid": "^8.3.2" 21 | } 22 | }, 23 | "node_modules/@cspotcode/source-map-support": { 24 | "version": "0.8.1", 25 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 26 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 27 | "dev": true, 28 | "dependencies": { 29 | "@jridgewell/trace-mapping": "0.3.9" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@jridgewell/resolve-uri": { 36 | "version": "3.0.7", 37 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", 38 | "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", 39 | "dev": true, 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/sourcemap-codec": { 45 | "version": "1.4.13", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", 47 | "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", 48 | "dev": true 49 | }, 50 | "node_modules/@jridgewell/trace-mapping": { 51 | "version": "0.3.9", 52 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 53 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 54 | "dev": true, 55 | "dependencies": { 56 | "@jridgewell/resolve-uri": "^3.0.3", 57 | "@jridgewell/sourcemap-codec": "^1.4.10" 58 | } 59 | }, 60 | "node_modules/@redis/bloom": { 61 | "version": "1.0.2", 62 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", 63 | "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", 64 | "dev": true, 65 | "peerDependencies": { 66 | "@redis/client": "^1.0.0" 67 | } 68 | }, 69 | "node_modules/@redis/client": { 70 | "version": "1.1.0", 71 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.1.0.tgz", 72 | "integrity": "sha512-xO9JDIgzsZYDl3EvFhl6LC52DP3q3GCMUer8zHgKV6qSYsq1zB+pZs9+T80VgcRogrlRYhi4ZlfX6A+bHiBAgA==", 73 | "dev": true, 74 | "dependencies": { 75 | "cluster-key-slot": "1.1.0", 76 | "generic-pool": "3.8.2", 77 | "yallist": "4.0.0" 78 | }, 79 | "engines": { 80 | "node": ">=14" 81 | } 82 | }, 83 | "node_modules/@redis/graph": { 84 | "version": "1.0.1", 85 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", 86 | "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", 87 | "dev": true, 88 | "peerDependencies": { 89 | "@redis/client": "^1.0.0" 90 | } 91 | }, 92 | "node_modules/@redis/json": { 93 | "version": "1.0.3", 94 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", 95 | "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", 96 | "dev": true, 97 | "peerDependencies": { 98 | "@redis/client": "^1.0.0" 99 | } 100 | }, 101 | "node_modules/@redis/search": { 102 | "version": "1.0.6", 103 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", 104 | "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", 105 | "dev": true, 106 | "peerDependencies": { 107 | "@redis/client": "^1.0.0" 108 | } 109 | }, 110 | "node_modules/@redis/time-series": { 111 | "version": "1.0.3", 112 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", 113 | "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", 114 | "dev": true, 115 | "peerDependencies": { 116 | "@redis/client": "^1.0.0" 117 | } 118 | }, 119 | "node_modules/@tsconfig/node10": { 120 | "version": "1.0.9", 121 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 122 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 123 | "dev": true 124 | }, 125 | "node_modules/@tsconfig/node12": { 126 | "version": "1.0.10", 127 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz", 128 | "integrity": "sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==", 129 | "dev": true 130 | }, 131 | "node_modules/@tsconfig/node14": { 132 | "version": "1.0.2", 133 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz", 134 | "integrity": "sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==", 135 | "dev": true 136 | }, 137 | "node_modules/@tsconfig/node16": { 138 | "version": "1.0.3", 139 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 140 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 141 | "dev": true 142 | }, 143 | "node_modules/@types/node": { 144 | "version": "17.0.34", 145 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.34.tgz", 146 | "integrity": "sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==" 147 | }, 148 | "node_modules/@types/uuid": { 149 | "version": "8.3.4", 150 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", 151 | "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", 152 | "dev": true 153 | }, 154 | "node_modules/acorn": { 155 | "version": "8.7.1", 156 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 157 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 158 | "dev": true, 159 | "bin": { 160 | "acorn": "bin/acorn" 161 | }, 162 | "engines": { 163 | "node": ">=0.4.0" 164 | } 165 | }, 166 | "node_modules/acorn-walk": { 167 | "version": "8.2.0", 168 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 169 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 170 | "dev": true, 171 | "engines": { 172 | "node": ">=0.4.0" 173 | } 174 | }, 175 | "node_modules/arg": { 176 | "version": "4.1.3", 177 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 178 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 179 | "dev": true 180 | }, 181 | "node_modules/cluster-key-slot": { 182 | "version": "1.1.0", 183 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 184 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", 185 | "dev": true, 186 | "engines": { 187 | "node": ">=0.10.0" 188 | } 189 | }, 190 | "node_modules/create-require": { 191 | "version": "1.1.1", 192 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 193 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 194 | "dev": true 195 | }, 196 | "node_modules/diff": { 197 | "version": "4.0.2", 198 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 199 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 200 | "dev": true, 201 | "engines": { 202 | "node": ">=0.3.1" 203 | } 204 | }, 205 | "node_modules/generic-pool": { 206 | "version": "3.8.2", 207 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 208 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", 209 | "dev": true, 210 | "engines": { 211 | "node": ">= 4" 212 | } 213 | }, 214 | "node_modules/graphql": { 215 | "version": "16.5.0", 216 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz", 217 | "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==", 218 | "dev": true, 219 | "engines": { 220 | "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" 221 | } 222 | }, 223 | "node_modules/make-error": { 224 | "version": "1.3.6", 225 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 226 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 227 | "dev": true 228 | }, 229 | "node_modules/redis": { 230 | "version": "4.1.0", 231 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.1.0.tgz", 232 | "integrity": "sha512-5hvJ8wbzpCCiuN1ges6tx2SAh2XXCY0ayresBmu40/SGusWHFW86TAlIPpbimMX2DFHOX7RN34G2XlPA1Z43zg==", 233 | "dev": true, 234 | "dependencies": { 235 | "@redis/bloom": "1.0.2", 236 | "@redis/client": "1.1.0", 237 | "@redis/graph": "1.0.1", 238 | "@redis/json": "1.0.3", 239 | "@redis/search": "1.0.6", 240 | "@redis/time-series": "1.0.3" 241 | } 242 | }, 243 | "node_modules/ts-node": { 244 | "version": "10.8.1", 245 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", 246 | "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", 247 | "dev": true, 248 | "dependencies": { 249 | "@cspotcode/source-map-support": "^0.8.0", 250 | "@tsconfig/node10": "^1.0.7", 251 | "@tsconfig/node12": "^1.0.7", 252 | "@tsconfig/node14": "^1.0.0", 253 | "@tsconfig/node16": "^1.0.2", 254 | "acorn": "^8.4.1", 255 | "acorn-walk": "^8.1.1", 256 | "arg": "^4.1.0", 257 | "create-require": "^1.1.0", 258 | "diff": "^4.0.1", 259 | "make-error": "^1.1.1", 260 | "v8-compile-cache-lib": "^3.0.1", 261 | "yn": "3.1.1" 262 | }, 263 | "bin": { 264 | "ts-node": "dist/bin.js", 265 | "ts-node-cwd": "dist/bin-cwd.js", 266 | "ts-node-esm": "dist/bin-esm.js", 267 | "ts-node-script": "dist/bin-script.js", 268 | "ts-node-transpile-only": "dist/bin-transpile.js", 269 | "ts-script": "dist/bin-script-deprecated.js" 270 | }, 271 | "peerDependencies": { 272 | "@swc/core": ">=1.2.50", 273 | "@swc/wasm": ">=1.2.50", 274 | "@types/node": "*", 275 | "typescript": ">=2.7" 276 | }, 277 | "peerDependenciesMeta": { 278 | "@swc/core": { 279 | "optional": true 280 | }, 281 | "@swc/wasm": { 282 | "optional": true 283 | } 284 | } 285 | }, 286 | "node_modules/typescript": { 287 | "version": "4.6.4", 288 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", 289 | "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", 290 | "dev": true, 291 | "bin": { 292 | "tsc": "bin/tsc", 293 | "tsserver": "bin/tsserver" 294 | }, 295 | "engines": { 296 | "node": ">=4.2.0" 297 | } 298 | }, 299 | "node_modules/uuid": { 300 | "version": "8.3.2", 301 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 302 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 303 | "dev": true, 304 | "bin": { 305 | "uuid": "dist/bin/uuid" 306 | } 307 | }, 308 | "node_modules/v8-compile-cache-lib": { 309 | "version": "3.0.1", 310 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 311 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 312 | "dev": true 313 | }, 314 | "node_modules/yallist": { 315 | "version": "4.0.0", 316 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 317 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 318 | "dev": true 319 | }, 320 | "node_modules/yn": { 321 | "version": "3.1.1", 322 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 323 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 324 | "dev": true, 325 | "engines": { 326 | "node": ">=6" 327 | } 328 | } 329 | }, 330 | "dependencies": { 331 | "@cspotcode/source-map-support": { 332 | "version": "0.8.1", 333 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 334 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 335 | "dev": true, 336 | "requires": { 337 | "@jridgewell/trace-mapping": "0.3.9" 338 | } 339 | }, 340 | "@jridgewell/resolve-uri": { 341 | "version": "3.0.7", 342 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", 343 | "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", 344 | "dev": true 345 | }, 346 | "@jridgewell/sourcemap-codec": { 347 | "version": "1.4.13", 348 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", 349 | "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", 350 | "dev": true 351 | }, 352 | "@jridgewell/trace-mapping": { 353 | "version": "0.3.9", 354 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 355 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 356 | "dev": true, 357 | "requires": { 358 | "@jridgewell/resolve-uri": "^3.0.3", 359 | "@jridgewell/sourcemap-codec": "^1.4.10" 360 | } 361 | }, 362 | "@redis/bloom": { 363 | "version": "1.0.2", 364 | "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", 365 | "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", 366 | "dev": true, 367 | "requires": {} 368 | }, 369 | "@redis/client": { 370 | "version": "1.1.0", 371 | "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.1.0.tgz", 372 | "integrity": "sha512-xO9JDIgzsZYDl3EvFhl6LC52DP3q3GCMUer8zHgKV6qSYsq1zB+pZs9+T80VgcRogrlRYhi4ZlfX6A+bHiBAgA==", 373 | "dev": true, 374 | "requires": { 375 | "cluster-key-slot": "1.1.0", 376 | "generic-pool": "3.8.2", 377 | "yallist": "4.0.0" 378 | } 379 | }, 380 | "@redis/graph": { 381 | "version": "1.0.1", 382 | "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", 383 | "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", 384 | "dev": true, 385 | "requires": {} 386 | }, 387 | "@redis/json": { 388 | "version": "1.0.3", 389 | "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", 390 | "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", 391 | "dev": true, 392 | "requires": {} 393 | }, 394 | "@redis/search": { 395 | "version": "1.0.6", 396 | "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", 397 | "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", 398 | "dev": true, 399 | "requires": {} 400 | }, 401 | "@redis/time-series": { 402 | "version": "1.0.3", 403 | "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", 404 | "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", 405 | "dev": true, 406 | "requires": {} 407 | }, 408 | "@tsconfig/node10": { 409 | "version": "1.0.9", 410 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 411 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 412 | "dev": true 413 | }, 414 | "@tsconfig/node12": { 415 | "version": "1.0.10", 416 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz", 417 | "integrity": "sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==", 418 | "dev": true 419 | }, 420 | "@tsconfig/node14": { 421 | "version": "1.0.2", 422 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz", 423 | "integrity": "sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==", 424 | "dev": true 425 | }, 426 | "@tsconfig/node16": { 427 | "version": "1.0.3", 428 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 429 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 430 | "dev": true 431 | }, 432 | "@types/node": { 433 | "version": "17.0.34", 434 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.34.tgz", 435 | "integrity": "sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==" 436 | }, 437 | "@types/uuid": { 438 | "version": "8.3.4", 439 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", 440 | "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", 441 | "dev": true 442 | }, 443 | "acorn": { 444 | "version": "8.7.1", 445 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 446 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 447 | "dev": true 448 | }, 449 | "acorn-walk": { 450 | "version": "8.2.0", 451 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 452 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 453 | "dev": true 454 | }, 455 | "arg": { 456 | "version": "4.1.3", 457 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 458 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 459 | "dev": true 460 | }, 461 | "cluster-key-slot": { 462 | "version": "1.1.0", 463 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 464 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", 465 | "dev": true 466 | }, 467 | "create-require": { 468 | "version": "1.1.1", 469 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 470 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 471 | "dev": true 472 | }, 473 | "diff": { 474 | "version": "4.0.2", 475 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 476 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 477 | "dev": true 478 | }, 479 | "generic-pool": { 480 | "version": "3.8.2", 481 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 482 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", 483 | "dev": true 484 | }, 485 | "graphql": { 486 | "version": "16.5.0", 487 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz", 488 | "integrity": "sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA==", 489 | "dev": true 490 | }, 491 | "make-error": { 492 | "version": "1.3.6", 493 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 494 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 495 | "dev": true 496 | }, 497 | "redis": { 498 | "version": "4.1.0", 499 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.1.0.tgz", 500 | "integrity": "sha512-5hvJ8wbzpCCiuN1ges6tx2SAh2XXCY0ayresBmu40/SGusWHFW86TAlIPpbimMX2DFHOX7RN34G2XlPA1Z43zg==", 501 | "dev": true, 502 | "requires": { 503 | "@redis/bloom": "1.0.2", 504 | "@redis/client": "1.1.0", 505 | "@redis/graph": "1.0.1", 506 | "@redis/json": "1.0.3", 507 | "@redis/search": "1.0.6", 508 | "@redis/time-series": "1.0.3" 509 | } 510 | }, 511 | "ts-node": { 512 | "version": "10.8.1", 513 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", 514 | "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", 515 | "dev": true, 516 | "requires": { 517 | "@cspotcode/source-map-support": "^0.8.0", 518 | "@tsconfig/node10": "^1.0.7", 519 | "@tsconfig/node12": "^1.0.7", 520 | "@tsconfig/node14": "^1.0.0", 521 | "@tsconfig/node16": "^1.0.2", 522 | "acorn": "^8.4.1", 523 | "acorn-walk": "^8.1.1", 524 | "arg": "^4.1.0", 525 | "create-require": "^1.1.0", 526 | "diff": "^4.0.1", 527 | "make-error": "^1.1.1", 528 | "v8-compile-cache-lib": "^3.0.1", 529 | "yn": "3.1.1" 530 | } 531 | }, 532 | "typescript": { 533 | "version": "4.6.4", 534 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", 535 | "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", 536 | "dev": true 537 | }, 538 | "uuid": { 539 | "version": "8.3.2", 540 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 541 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 542 | "dev": true 543 | }, 544 | "v8-compile-cache-lib": { 545 | "version": "3.0.1", 546 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 547 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 548 | "dev": true 549 | }, 550 | "yallist": { 551 | "version": "4.0.0", 552 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 553 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 554 | "dev": true 555 | }, 556 | "yn": { 557 | "version": "3.1.1", 558 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 559 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 560 | "dev": true 561 | } 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "desolver", 3 | "version": "1.0.0", 4 | "description": "GraphQL framework providing a powerful API to modularize resolver business logic", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "tsc", 10 | "prepre": "npm run build" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/oslabs-beta/DeSolver.git" 15 | }, 16 | "keywords": [ 17 | "graphQL", 18 | "Redis", 19 | "resolver", 20 | "cache", 21 | "modular", 22 | "abstraction", 23 | "caching" 24 | ], 25 | "author": "Mia Kang, Julia Hickey, Michael Chan, Alexander Gurfinkel", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/oslabs-beta/DeSolver/issues" 29 | }, 30 | "homepage": "https://github.com/oslabs-beta/DeSolver#readme", 31 | "devDependencies": { 32 | "@types/uuid": "^8.3.4", 33 | "graphql": "^16.5.0", 34 | "redis": "^4.1.0", 35 | "ts-node": "^10.8.1", 36 | "typescript": "^4.6.4", 37 | "uuid": "^8.3.2" 38 | }, 39 | "files": [ 40 | "lib/**/*" 41 | ], 42 | "dependencies": { 43 | "@types/node": "^17.0.34" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Desolver.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import { 3 | ResolverBuilder, 4 | DesolverConfig, 5 | DesolverFragment, 6 | ResolverWrapper, 7 | ResolverType, 8 | ResolversMap, 9 | } from './ResolverBuilder'; 10 | 11 | export class Desolver { 12 | private resolverBuilder: ResolverBuilder; 13 | private preHooksPipelineStore: Record = {}; 14 | private idCachedDesolvers: Record = {}; 15 | 16 | constructor(public config?: DesolverConfig) { 17 | this.resolverBuilder = new ResolverBuilder(config); 18 | } 19 | 20 | // Specify a resolver type as a string, and then define the middleware Desolver Fragments 21 | public use(typeName: ResolverType, ...desolvers: DesolverFragment[]): void { 22 | if (!this.preHooksPipelineStore[typeName]) { 23 | this.preHooksPipelineStore[typeName] = []; 24 | } 25 | this.preHooksPipelineStore[typeName].push(...desolvers); 26 | } 27 | 28 | public useRoute(...desolvers: DesolverFragment[]): ResolverWrapper { 29 | // Error handling in case some other value other than a function is loaded into useRoute 30 | for (const desolverFragment of desolvers) { 31 | if (typeof desolverFragment !== 'function') { 32 | throw new Error('Desolver Fragment must be a function.'); 33 | } 34 | } 35 | 36 | const newId = uuidv4(); 37 | 38 | // Builds the pipeline with the all the desolvers that have been wrapped into useRoute 39 | const newResolver = this.resolverBuilder 40 | .loadPreHooks(...desolvers) 41 | .buildResolverWrapper(); 42 | 43 | // Rename the function with the uuid, allows for checking if the useRoute has been called when using the apply method 44 | Object.defineProperty(newResolver, 'name', { 45 | value: newId, 46 | writable: false, 47 | }); 48 | 49 | // Save these desolvers so that they can be appended later during the apply method 50 | this.idCachedDesolvers[newId] = desolvers; 51 | 52 | return newResolver; 53 | } 54 | 55 | // Iterate over all properties of the resolver map and transform the resolver map object by building new resolver functions with prehook functions 56 | public apply(resolversMap: ResolversMap): ResolversMap { 57 | for (const type in resolversMap) { 58 | // Currently appending functionality to subscriptions isn't supported in Desolver, skip any types not found in the store 59 | if (type === 'Subscription') { 60 | continue; 61 | } 62 | 63 | // Iterate over all the fields in the resolver map and build new Resolvers with the prehook functions 64 | for (const field in resolversMap[type]) { 65 | const currentResolver = resolversMap[type][field]; 66 | 67 | // Checks to see if any 'All' preHooks were loaded, returns empty array if none exists 68 | const allPrehooks = this.preHooksPipelineStore['All'] 69 | ? this.preHooksPipelineStore['All'] 70 | : []; 71 | 72 | // Checks the current type of Resolver ('Query', 'Mutation', etc) has been loaded from desolver.use(), returns empty array if not 73 | const typePrehooks = this.preHooksPipelineStore[type] 74 | ? this.preHooksPipelineStore[type] 75 | : []; 76 | 77 | // Check name of the current resolver function in the idCachedDesolvers store, if it exists 78 | // Replace the existing wrapped function with a new invocation of useRoute, and re-wrap the function but with preHooks appended to the idCachedDesolvers 79 | if (this.idCachedDesolvers[resolversMap[type][field].name]) { 80 | resolversMap[type][field] = this.useRoute( 81 | ...allPrehooks, 82 | ...typePrehooks, 83 | ...this.idCachedDesolvers[currentResolver.name] 84 | ); 85 | continue; 86 | } 87 | 88 | // Otherwise if a name does not exist, it means the resolver is defined as a singular function 89 | // Append the preHooks and the singular function 90 | resolversMap[type][field] = this.useRoute( 91 | ...allPrehooks, 92 | ...typePrehooks, 93 | currentResolver 94 | ); 95 | } 96 | } 97 | return resolversMap; 98 | } 99 | 100 | // TO DO: Hook up the error handler 101 | private errorLogger(error: any): void { 102 | let errorObj = { 103 | Error: error.toString(), 104 | 'Error Name': error.name, 105 | 'Error Message': error.message, 106 | }; 107 | 108 | throw new Error(`failed to resolve: ${errorObj}`); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/ResolverBuilder.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql'; 2 | import { createClient, RedisClientType, RedisClientOptions } from 'redis'; 3 | 4 | export interface ResolvedObject { 5 | resolved: boolean; 6 | value: unknown; 7 | } 8 | 9 | // Type annotation for individual resolvers. 10 | export type ResolverWrapper = ( 11 | parent: Record, 12 | args: Record, 13 | context: Record, 14 | info: GraphQLResolveInfo 15 | ) => unknown | Promise; 16 | 17 | // This is the type for the DeSolver middleware function passed into useRoute() and use(). 18 | // DeSolverFragment mimics the structure of an individual resolver object. 19 | export type DesolverFragment = ( 20 | parent: Record, 21 | args: Record, 22 | context: Record, 23 | info: GraphQLResolveInfo, 24 | next?: (err?: string, resolvedObject?: T) => void, 25 | escapeHatch?: (resolvedObject: T) => T | void, 26 | ds?: Record 27 | ) => unknown | Promise; 28 | 29 | export type ResolverType = 'Query' | 'Mutation' | 'Root' | 'All' | string; 30 | 31 | export interface DesolverConfig extends RedisClientOptions { 32 | cacheDesolver?: boolean; 33 | applyResolverType?: ResolverType; 34 | } 35 | 36 | export interface ResolversMap { 37 | [index: string]: { [index: string]: DesolverFragment }; 38 | } 39 | 40 | export class ResolverBuilder { 41 | private cache: RedisClientType; 42 | private desolverPipeline: DesolverFragment[] = []; 43 | 44 | constructor(public config?: DesolverConfig) { 45 | if (this.config?.cacheDesolver === true) { 46 | // Redis cache starting with custom config 47 | this.cache = createClient(this.config); 48 | this.cache.connect(); 49 | this.cache.on('error', (err) => console.log('Redis Client Error', err)); 50 | } 51 | } 52 | 53 | // Call this method after building a Resolver Wrapper to reset the pipeline store 54 | public reset(): void { 55 | this.desolverPipeline = []; 56 | } 57 | 58 | // Method to load DesolverFragments into the pipeline 59 | // Return 'this' so that multiple load methods and buildResolverWrapper method can be chained 60 | public loadPreHooks(...preHookDesolvers: DesolverFragment[]): this { 61 | this.desolverPipeline.push(...preHookDesolvers); 62 | return this; 63 | } 64 | 65 | // Builds the resolver wrapper with the loaded pipeline 66 | public buildResolverWrapper(): ResolverWrapper { 67 | // Save the pipeline in the ResolverWrapper's closure 68 | const pipeline = this.desolverPipeline; 69 | 70 | // Pipeline can be safely reset after saving reference to the built pipeline 71 | this.reset(); 72 | 73 | // Return a function that wraps around the execution of the desolver pipeline 74 | return async (parent, args, context, info) => { 75 | try { 76 | if (this.config.cacheDesolver === true) { 77 | const cachedValue = await getCachedValue(this.cache, info); 78 | if (cachedValue) { 79 | // Cache hit 80 | return JSON.parse(cachedValue); 81 | } 82 | } 83 | 84 | let nextIdx = 0; 85 | 86 | // Scope a variable that will keep track if the escapehatch was invoked 87 | const resolvedObject: ResolvedObject = { resolved: false, value: null }; 88 | 89 | // Scope a context object that can be used to pass data between Desolver Fragments 90 | const ds = {}; 91 | 92 | const next = (err?: string, resolvedValue?: T): void | T => { 93 | try { 94 | if (err) throw new Error(err); 95 | if (resolvedValue) { 96 | resolvedObject.resolved = true; 97 | return (resolvedObject.value = resolvedValue); 98 | } 99 | nextIdx += 1; 100 | } catch (e) { 101 | throw new Error(err); 102 | } 103 | }; 104 | 105 | const escapeHatch = (resolvedValue: T): void | T => { 106 | try { 107 | resolvedObject.resolved = true; 108 | return (resolvedObject.value = resolvedValue); 109 | } catch (e) { 110 | throw new Error(e.message); 111 | } 112 | }; 113 | 114 | while (nextIdx <= pipeline.length - 1) { 115 | // keep track of the current index so that calling next can be tracked 116 | const currIdx = nextIdx; 117 | 118 | if (nextIdx === pipeline.length - 1) { 119 | // Always resolve the returned value of the final function in the pipeline 120 | resolvedObject.value = await pipeline[nextIdx]( 121 | parent, 122 | args, 123 | context, 124 | info, 125 | next, 126 | escapeHatch, 127 | ds 128 | ); 129 | resolvedObject.resolved = true; 130 | break; 131 | } 132 | 133 | await pipeline[nextIdx]( 134 | parent, 135 | args, 136 | context, 137 | info, 138 | next, 139 | escapeHatch, 140 | ds 141 | ); 142 | 143 | // This if statement will be true is escapeHatch is called within the desolver fragments 144 | // Hence if escapeHatch is invoked, break out of the while loop, then proceed to save to cache if caching enabled 145 | if (resolvedObject.resolved) break; 146 | 147 | // Warn that next must be called 148 | // If after execution of the Desolver Fragment middlewares, nextIdx should be greater than currIdx if next is called 149 | // If they are equal, then that means next was not called, throw error to warn 150 | if (currIdx === nextIdx) { 151 | throw new Error('Next was not called'); 152 | } 153 | } 154 | 155 | if (this.config.cacheDesolver === true) { 156 | // Sets the resolved value to the cache 157 | await setCachedValue(this.cache, info, resolvedObject.value); 158 | } 159 | 160 | return resolvedObject.value; 161 | } catch (e) { 162 | throw new Error(e.message); 163 | } 164 | }; 165 | } 166 | } 167 | 168 | // Helper Functions for getting and setting of the cached values 169 | async function getCachedValue( 170 | cache: RedisClientType, 171 | info: GraphQLResolveInfo 172 | ): Promise { 173 | // Add AST parse logic here to create a unique key and pass into the cache to fetch 174 | const cachedValue = await cache.hGet('Query', JSON.stringify(info.path)); 175 | if (cachedValue !== null) return cachedValue; 176 | } 177 | 178 | async function setCachedValue( 179 | cache: RedisClientType, 180 | info: GraphQLResolveInfo, 181 | resolvedValue: unknown 182 | ): Promise { 183 | // Add AST parse logic here to create a unique key and pass into the cache to be set 184 | await cache.hSet( 185 | 'Query', 186 | JSON.stringify(info.path), 187 | JSON.stringify(resolvedValue) 188 | ); 189 | } 190 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Desolver'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "declaration": true, 5 | "noImplicitAny": true, 6 | "module": "commonjs", 7 | "target": "es6", 8 | "jsx": "react", 9 | "allowJs": false, 10 | "moduleResolution": "node", 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules"] 16 | } 17 | --------------------------------------------------------------------------------