├── .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 |
--------------------------------------------------------------------------------