@nsxbet/react-relay-network-modern 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE.md +10 -0
  2. package/README.md +324 -0
  3. package/dist/RRNLError.cjs +9 -0
  4. package/dist/RRNLError.d.cts +6 -0
  5. package/dist/RRNLError.d.ts +6 -0
  6. package/dist/RRNLError.js +9 -0
  7. package/dist/RelayNetworkLayer.cjs +54 -0
  8. package/dist/RelayNetworkLayer.d.cts +21 -0
  9. package/dist/RelayNetworkLayer.d.ts +21 -0
  10. package/dist/RelayNetworkLayer.js +54 -0
  11. package/dist/RelayRequest.cjs +90 -0
  12. package/dist/RelayRequest.d.cts +26 -0
  13. package/dist/RelayRequest.d.ts +26 -0
  14. package/dist/RelayRequest.js +90 -0
  15. package/dist/RelayRequestBatch.cjs +56 -0
  16. package/dist/RelayRequestBatch.d.cts +23 -0
  17. package/dist/RelayRequestBatch.d.ts +23 -0
  18. package/dist/RelayRequestBatch.js +56 -0
  19. package/dist/RelayResponse.cjs +56 -0
  20. package/dist/RelayResponse.d.cts +27 -0
  21. package/dist/RelayResponse.d.ts +27 -0
  22. package/dist/RelayResponse.js +56 -0
  23. package/dist/createRequestError.cjs +48 -0
  24. package/dist/createRequestError.d.cts +18 -0
  25. package/dist/createRequestError.d.ts +18 -0
  26. package/dist/createRequestError.js +46 -0
  27. package/dist/definition.d.cts +95 -0
  28. package/dist/definition.d.ts +95 -0
  29. package/dist/express-middleware/graphqlBatchHTTPWrapper.cjs +60 -0
  30. package/dist/express-middleware/graphqlBatchHTTPWrapper.d.cts +5 -0
  31. package/dist/express-middleware/graphqlBatchHTTPWrapper.d.ts +5 -0
  32. package/dist/express-middleware/graphqlBatchHTTPWrapper.js +60 -0
  33. package/dist/fetchWithMiddleware.cjs +43 -0
  34. package/dist/fetchWithMiddleware.js +43 -0
  35. package/dist/index.cjs +49 -0
  36. package/dist/index.d.cts +21 -0
  37. package/dist/index.d.ts +21 -0
  38. package/dist/index.js +20 -0
  39. package/dist/middlewares/auth.cjs +47 -0
  40. package/dist/middlewares/auth.d.cts +18 -0
  41. package/dist/middlewares/auth.d.ts +18 -0
  42. package/dist/middlewares/auth.js +46 -0
  43. package/dist/middlewares/batch.cjs +136 -0
  44. package/dist/middlewares/batch.d.cts +34 -0
  45. package/dist/middlewares/batch.d.ts +34 -0
  46. package/dist/middlewares/batch.js +135 -0
  47. package/dist/middlewares/cache.cjs +43 -0
  48. package/dist/middlewares/cache.d.cts +17 -0
  49. package/dist/middlewares/cache.d.ts +17 -0
  50. package/dist/middlewares/cache.js +43 -0
  51. package/dist/middlewares/error.cjs +71 -0
  52. package/dist/middlewares/error.d.cts +11 -0
  53. package/dist/middlewares/error.d.ts +11 -0
  54. package/dist/middlewares/error.js +71 -0
  55. package/dist/middlewares/logger.cjs +43 -0
  56. package/dist/middlewares/logger.d.cts +9 -0
  57. package/dist/middlewares/logger.d.ts +9 -0
  58. package/dist/middlewares/logger.js +43 -0
  59. package/dist/middlewares/perf.cjs +13 -0
  60. package/dist/middlewares/perf.d.cts +9 -0
  61. package/dist/middlewares/perf.d.ts +9 -0
  62. package/dist/middlewares/perf.js +13 -0
  63. package/dist/middlewares/persistedQueries.cjs +33 -0
  64. package/dist/middlewares/persistedQueries.d.cts +9 -0
  65. package/dist/middlewares/persistedQueries.d.ts +9 -0
  66. package/dist/middlewares/persistedQueries.js +33 -0
  67. package/dist/middlewares/progress.cjs +35 -0
  68. package/dist/middlewares/progress.d.cts +10 -0
  69. package/dist/middlewares/progress.d.ts +10 -0
  70. package/dist/middlewares/progress.js +35 -0
  71. package/dist/middlewares/retry.cjs +146 -0
  72. package/dist/middlewares/retry.d.cts +34 -0
  73. package/dist/middlewares/retry.d.ts +34 -0
  74. package/dist/middlewares/retry.js +145 -0
  75. package/dist/middlewares/upload.cjs +32 -0
  76. package/dist/middlewares/upload.d.cts +6 -0
  77. package/dist/middlewares/upload.d.ts +6 -0
  78. package/dist/middlewares/upload.js +32 -0
  79. package/dist/middlewares/url.cjs +19 -0
  80. package/dist/middlewares/url.d.cts +19 -0
  81. package/dist/middlewares/url.d.ts +19 -0
  82. package/dist/middlewares/url.js +19 -0
  83. package/dist/utils.cjs +6 -0
  84. package/dist/utils.js +6 -0
  85. package/package.json +88 -0
package/LICENSE.md ADDED
@@ -0,0 +1,10 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016-present Pavel Chertorogov (original author)
4
+ Copyright (c) 2026-present NSXBet (@nsxbet fork)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # ReactRelayNetworkModern (for Relay Modern)
2
+
3
+ [![npm](https://img.shields.io/npm/v/@nsxbet/react-relay-network-modern.svg)](https://www.npmjs.com/package/@nsxbet/react-relay-network-modern)
4
+ [![CI](https://github.com/NSXBet/react-relay-network-modern/actions/workflows/ci.yml/badge.svg)](https://github.com/NSXBet/react-relay-network-modern/actions/workflows/ci.yml)
5
+ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
6
+ ![TypeScript](https://img.shields.io/badge/types-TypeScript-blue.svg)
7
+
8
+ > `@nsxbet/react-relay-network-modern` is a fork of [`react-relay-network-modern`](https://github.com/relay-tools/react-relay-network-modern) by Pavel Chertorogov (@nodkz), modernized to TypeScript. We are not the owners of the original package.
9
+
10
+ The `ReactRelayNetworkModern` is a [Network Layer for Relay Modern](https://relay.dev/docs/guides/network-layer/)
11
+ with various middlewares which can manipulate requests/responses on the fly (change auth headers, request url or perform some fallback if request fails), batch several relay request by timeout into one http request, cache queries and server-side rendering.
12
+
13
+ `ReactRelayNetworkModern` can be used in browser, react-native or node server for rendering. Under the hood this module uses global `fetch` method. So if your client is too old, please import explicitly proper polyfill to your code (eg. `whatwg-fetch`, `node-fetch` or `fetch-everywhere`).
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ bun add @nsxbet/react-relay-network-modern
19
+ # or
20
+ npm install @nsxbet/react-relay-network-modern
21
+ ```
22
+
23
+ ### Builds
24
+
25
+ This package ships ES modules and CommonJS builds with TypeScript declarations, resolved
26
+ automatically through the package `exports` field. No `core-js`/`regenerator-runtime` polyfills
27
+ are bundled — it targets modern runtimes and relies on the global `fetch`.
28
+
29
+ ```js
30
+ import { RelayNetworkLayer } from '@nsxbet/react-relay-network-modern';
31
+ ```
32
+
33
+ ## Middlewares
34
+
35
+ ### Built-in middlewares
36
+
37
+ - **your custom inline middleware** - [see example](https://github.com/NSXBet/react-relay-network-modern#example-of-injecting-networklayer-with-middlewares-on-the-client-side) below where added `credentials` and `headers` to the `fetch` method.
38
+ - `next => req => { /* your modification of 'req' object */ return next(req); }`
39
+ - **urlMiddleware** - for manipulating fetch `url` on fly via thunk.
40
+ - `url` - string for single request. Can be Promise or function(req). (default: `/graphql`).
41
+ - `method` - string, for request method type (default: `POST`)
42
+ - headers - Object with headers for fetch. Can be Promise or function(req).
43
+ - credentials - string, setting for fetch method, eg. 'same-origin' (default: empty).
44
+ - also you may provide `mode`, `cache`, `redirect` options for fetch method, for details see [fetch spec](https://fetch.spec.whatwg.org/#requests).
45
+ - **cacheMiddleware** - for caching same queries you may use this middleware. It will skip (do not cache) mutations and FormData requests.
46
+ - `size` - max number of request in cache, least-recently _updated_ entries purged first (default: `100`).
47
+ - `ttl` - number in milliseconds, how long records stay valid in cache (default: `900000`, 15 minutes).
48
+ - `onInit` - function(cache) which will be called once when cache is created. As first argument you receive `QueryResponseCache` instance from `relay-runtime`.
49
+ - `allowMutations` - allow to cache Mutation requests (default: `false`)
50
+ - `allowFormData` - allow to cache FormData requests (default: `false`)
51
+ - `clearOnMutation` - clear the cache on any Mutation (default: `false`)
52
+ - `cacheErrors` - cache responses with errors (default: `false`)
53
+ - `updateTTLOnGet` - refresh cache ttl on queries on successful cache get (default: `false`)
54
+ - **authMiddleware** - for adding auth token, and refreshing it if gets 401 response from server.
55
+ - `token` - string which returns token. Can be function(req) or Promise. If function is provided, then it will be called for every request (so you may change tokens on fly).
56
+ - `tokenRefreshPromise`: - function(req, res) which must return promise or regular value with a new token. This function is called when server returns 401 status code. After receiving a new token, middleware re-run query to the server seamlessly for Relay.
57
+ - `allowEmptyToken` - allow made a request without Authorization header if token is empty (default: `false`).
58
+ - `prefix` - prefix before token (default: `'Bearer '`).
59
+ - `header` - name of the HTTP header to pass the token in (default: `'Authorization'`).
60
+ - If you use `auth` middleware with `retry`, `retry` must be used before `auth`. Eg. if token expired when retries apply, then `retry` can call `auth` middleware again.
61
+ - **retryMiddleware** - for request retry if the initial request fails.
62
+ - `fetchTimeout` - number in milliseconds that defines in how much time will request timeout after it has been sent to the server again (default: `15000`). Or it may be a function `(attempt: number) => number` which returns a timeout in milliseconds (`attempt` starts from 0).
63
+ - `retryDelays` - array of millisecond that defines the values on which retries are based on (default: `[1000, 3000]`). Or it may be a function `(attempt: number) => number | false` which returns a timeout in milliseconds for retry or false for disabling retry (`attempt` starts from 0).
64
+ - `statusCodes` - array of response status codes which will fire up retryMiddleware. Or it may be a function `(statusCode: number, req, res) => boolean` which makes retry if returned true. (default: `status < 200 or status > 300`).
65
+ - `beforeRetry` - function(meta: { forceRetry: Function, abort: Function, delay: number, attempt: number, lastError: ?Error, req: RelayRequest }) called before every retry attempt. You get one argument with following properties:
66
+ - `forceRetry()` - for proceeding request immediately
67
+ - `abort(abortMsg: string)` - for aborting retry request. (Default abort message is: `"Aborted in beforeRetry() callback"`)
68
+ - `attempt` - number of the attemp (starts from 1)
69
+ - `delay` - number of milliseconds when next retry will be called
70
+ - `lastError` - will keep Error from previous request
71
+ - `req` - retriable Request object
72
+ - `allowMutations` - by default retries disabled for mutations, you may allow process retries for them passing `true`. (default: `false`)
73
+ - `allowFormData` - by default retries disabled for file Uploads, you may enable it passing `true` (default: `false`)
74
+ - `forceRetry` - deprecated, use `beforeRetry` instead (default: `false`).
75
+ - **batchMiddleware** - gather some period of time relay-requests and sends it as one http-request. You server must support batch request and return results in the same order they were requested. [See how to setup your server.](https://github.com/NSXBet/react-relay-network-modern#example-how-to-enable-batching)
76
+ - `batchUrl` - string. Url of the server endpoint for batch request execution. Can be function(requestList) or Promise. (default: `/graphql/batch`)
77
+ - `batchTimeout` - integer in milliseconds, period of time for gathering multiple requests before sending them to the server. Will delay sending of the requests on specified in this option period of time, so be careful and keep this value small. (default: `0`)
78
+ - `maxBatchSize` - integer representing maximum size of request to be sent in a single batch. Once a request hits the provided size in length a new batch request is ran. Actual for hardcoded limit in 100kb per request in [express-graphql](https://github.com/graphql/express-graphql/blob/master/src/parseBody.js#L112) module. (default: `102400` characters, roughly 100kb for 1-byte characters or 200kb for 2-byte characters)
79
+ - `allowMutations` - by default batching disabled for mutations, you may enable it passing `true` (default: `false`)
80
+ - `method` - string, for request method type (default: `POST`)
81
+ - `headers` - Object with headers for fetch. Can be Promise or function(req).
82
+ - `credentials` - string, setting for fetch method, eg. 'same-origin' (default: empty).
83
+ - also you may provide `mode`, `cache`, `redirect` options for fetch method, for details see [fetch spec](https://fetch.spec.whatwg.org/#requests).
84
+ - **loggerMiddleware** - for logging requests and responses.
85
+ - `logger` - log function (default: `console.log.bind(console, '[RELAY-NETWORK]')`)
86
+ - An example of req/res output in console: <img width="968" alt="screen shot 2017-11-19 at 23 05 19" src="https://user-images.githubusercontent.com/1946920/33159466-557517e0-d03d-11e7-9711-ebdfe6e789c8.png">
87
+ - **perfMiddleware** - simple time measure for network request.
88
+ - `logger` - log function (default: `console.log.bind(console, '[RELAY-NETWORK]')`)
89
+ - **errorMiddleware** - display `errors` data to console from graphql response. If you want see stackTrace for errors, you should provide `formatError` to `express-graphql` (see example below where `graphqlServer` accept `formatError` function).
90
+ - `logger` - log function (default: `console.error.bind(console)`)
91
+ - `prefix` - prefix message (default: `[RELAY-NETWORK] GRAPHQL SERVER ERROR:`)
92
+ - **progressMiddleware** - enable onProgress callback for modern browsers with support for Stream API.
93
+ - `onProgress` - on progress callback function (`function(bytesCurrent: number, bytesTotal: number | null) => void`, total size will be null if size header is not set)
94
+ - `sizeHeader` - response header with total size of response (default: `Content-Length`, useful when `Transfer-Encoding: chunked` is set)
95
+ - **uploadMiddleware** - extracts [`File`](https://developer.mozilla.org/docs/web/api/file), [`Blob`](https://developer.mozilla.org/docs/web/api/blob) and [`ReactNativeFile`](#class-reactnativefile) instances from query variables to be consumed with [graphql-upload](https://github.com/jaydenseric/graphql-upload)
96
+
97
+ ### Standalone package middlewares
98
+
99
+ - [**react-relay-network-modern-ssr**](https://github.com/relay-tools/react-relay-network-modern-ssr) - client/server middleware for server-side rendering (SSR). On server side it makes requests directly via `graphql-js` and your `schema`, cache payloads and serialize them for putting to HTML. On client side it loads provided payloads and renders them in sync mode without visible flashes and loaders.
100
+
101
+ ### Example of injecting NetworkLayer with middlewares on the **client side**
102
+
103
+ ```js
104
+ import { Environment, RecordSource, Store } from 'relay-runtime';
105
+ import {
106
+ RelayNetworkLayer,
107
+ urlMiddleware,
108
+ batchMiddleware,
109
+ loggerMiddleware,
110
+ errorMiddleware,
111
+ perfMiddleware,
112
+ retryMiddleware,
113
+ authMiddleware,
114
+ cacheMiddleware,
115
+ progressMiddleware,
116
+ uploadMiddleware,
117
+ } from 'react-relay-network-modern';
118
+
119
+ const network = new RelayNetworkLayer(
120
+ [
121
+ cacheMiddleware({
122
+ size: 100, // max 100 requests
123
+ ttl: 900000, // 15 minutes
124
+ }),
125
+ urlMiddleware({
126
+ url: (req) => Promise.resolve('/graphql'),
127
+ }),
128
+ batchMiddleware({
129
+ batchUrl: (requestList) => Promise.resolve('/graphql/batch'),
130
+ batchTimeout: 10,
131
+ }),
132
+ __DEV__ ? loggerMiddleware() : null,
133
+ __DEV__ ? errorMiddleware() : null,
134
+ __DEV__ ? perfMiddleware() : null,
135
+ retryMiddleware({
136
+ fetchTimeout: 15000,
137
+ retryDelays: (attempt) => Math.pow(2, attempt + 4) * 100, // or simple array [3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600],
138
+ beforeRetry: ({ forceRetry, abort, delay, attempt, lastError, req }) => {
139
+ if (attempt > 10) abort();
140
+ window.forceRelayRetry = forceRetry;
141
+ console.log('call `forceRelayRetry()` for immediately retry! Or wait ' + delay + ' ms.');
142
+ },
143
+ statusCodes: [500, 503, 504],
144
+ }),
145
+ authMiddleware({
146
+ token: () => store.get('jwt'),
147
+ tokenRefreshPromise: (req) => {
148
+ console.log('[client.js] resolve token refresh', req);
149
+ return fetch('/jwt/refresh')
150
+ .then((res) => res.json())
151
+ .then((json) => {
152
+ const token = json.token;
153
+ store.set('jwt', token);
154
+ return token;
155
+ })
156
+ .catch((err) => console.log('[client.js] ERROR can not refresh token', err));
157
+ },
158
+ }),
159
+ progressMiddleware({
160
+ onProgress: (current, total) => {
161
+ console.log('Downloaded: ' + current + ' B, total: ' + total + ' B');
162
+ },
163
+ }),
164
+ uploadMiddleware(),
165
+
166
+ // example of the custom inline middleware
167
+ (next) => async (req) => {
168
+ req.fetchOpts.method = 'GET'; // change default POST request method to GET
169
+ req.fetchOpts.headers['X-Request-ID'] = uuid.v4(); // add `X-Request-ID` to request headers
170
+ req.fetchOpts.credentials = 'same-origin'; // allow to send cookies (sending credentials to same domains)
171
+ // req.fetchOpts.credentials = 'include'; // allow to send cookies for CORS (sending credentials to other domains)
172
+
173
+ console.log('RelayRequest', req);
174
+
175
+ const res = await next(req);
176
+ console.log('RelayResponse', res);
177
+
178
+ return res;
179
+ },
180
+ ],
181
+ opts
182
+ ); // as second arg you may pass advanced options for RRNL
183
+
184
+ const source = new RecordSource();
185
+ const store = new Store(source);
186
+ const environment = new Environment({ network, store });
187
+ ```
188
+
189
+ ## Advanced options (2nd argument after middlewares)
190
+
191
+ RelayNetworkLayer may accept additional options:
192
+
193
+ ```js
194
+ const middlewares = []; // array of middlewares
195
+ const options = {}; // optional advanced options
196
+ const network = new RelayNetworkLayer(middlewares, options);
197
+ ```
198
+
199
+ Available options:
200
+
201
+ - **subscribeFn** - if you use subscriptions in your app, you may provide this function which will be passed to [RelayNetwork](https://github.com/facebook/relay/blob/master/packages/relay-runtime/network/RelayNetwork.js).
202
+ - **noThrow** - **EXPERIMENTAL (May be deprecated in the future)** set true to not throw when an error response is given by the server, and to instead handle errors in your app code.
203
+
204
+ ## Server-side rendering (SSR)
205
+
206
+ See [react-relay-network-modern-ssr](https://github.com/relay-tools/react-relay-network-modern-ssr) for SSR middleware.
207
+
208
+ ## How middlewares work internally
209
+
210
+ Middlewares on bottom layer use [fetch](https://github.com/github/fetch) method. So `req` is compliant with a `fetch()` options. And `res` can be obtained via `resPromise.then(res => ...)`, which returned by `fetch()`.
211
+
212
+ Middleware that needs access to the raw response body from fetch (before it has been consumed) can set `isRawMiddleware = true`, see `progressMiddleware` for example. It is important to note that `response.body` can only be consumed once, so make sure to `clone()` the response first.
213
+
214
+ Middlewares have 3 phases:
215
+
216
+ - `setup phase`, which runs only once, when middleware added to the NetworkLayer
217
+ - `capturing phase`, when you may change request object, and pass it down via `next(req)`
218
+ - `bubbling phase`, when you may change response promise, made re-request or pass it up unchanged
219
+
220
+ Basic skeleton of middleware:
221
+
222
+ ```js
223
+ export default function skeletonMiddleware(opts = {}) {
224
+ // [SETUP PHASE]: here you can process `opts`, when you create Middleware
225
+
226
+ return (next) => async (req) => {
227
+ // [CAPTURING PHASE]: here you can change `req` object, before it will pass to following middlewares.
228
+ // ...some code which modify `req`
229
+
230
+ const res = await next(req); // pass request to following middleware and get response promise from it
231
+
232
+ // [BUBBLING PHASE]: here you may change response of underlying middlewares, via promise syntax
233
+ // ...some code, which process `req`
234
+
235
+ return res; // return response to upper middleware
236
+ };
237
+ }
238
+ ```
239
+
240
+ Middlewares use LIFO (last in, first out) stack. Or simply put - use `compose` function. So if you pass such middlewares [M1(opts), M2(opts)] to NetworkLayer it will be work such way:
241
+
242
+ - call setup phase of `M1` with its opts
243
+ - call setup phase of `M2` with its opts
244
+ - for each request
245
+ - call capture phase of `M1`
246
+ - call capture phase of `M2`
247
+ - call `fetch` method
248
+ - call bubbling phase of `M2`
249
+ - call bubbling phase of `M1`
250
+ - chain to `resPromise.then(res => res.json())` and pass this promise for resolving/rejecting Relay requests.
251
+
252
+ ## Batching several requests into one
253
+
254
+ Joseph Savona [wrote](https://github.com/facebook/relay/issues/1058#issuecomment-213592051): For legacy reasons, Relay splits "plural" root queries into individual queries. In general we want to diff each root value separately, since different fields may be missing for different root values.
255
+
256
+ Also if you use [react-relay-router](https://github.com/relay-tools/react-router-relay) and have multiple root queries in one route pass, you may notice that default network layer will produce several http requests.
257
+
258
+ So for avoiding multiple http-requests, the `ReactRelayNetworkModern` is the right way to combine it in single http-request.
259
+
260
+ ### Example how to enable batching
261
+
262
+ #### ...on server
263
+
264
+ Firstly, you should prepare **server** to process the batch request:
265
+
266
+ ```js
267
+ import express from 'express';
268
+ import graphqlHTTP from 'express-graphql';
269
+ import { graphqlBatchHTTPWrapper } from 'react-relay-network-modern';
270
+ import bodyParser from 'body-parser';
271
+ import myGraphqlSchema from './graphqlSchema';
272
+
273
+ const port = 3000;
274
+ const server = express();
275
+
276
+ // setup standart `graphqlHTTP` express-middleware
277
+ const graphqlServer = graphqlHTTP({
278
+ schema: myGraphqlSchema,
279
+ formatError: (error) => ({
280
+ // better errors for development. `stack` used in `gqErrors` middleware
281
+ message: error.message,
282
+ stack: process.env.NODE_ENV === 'development' ? error.stack.split('\n') : null,
283
+ }),
284
+ });
285
+
286
+ // declare route for batch query
287
+ server.use('/graphql/batch', bodyParser.json(), graphqlBatchHTTPWrapper(graphqlServer));
288
+
289
+ // declare standard graphql route
290
+ server.use('/graphql', graphqlServer);
291
+
292
+ server.listen(port, () => {
293
+ console.log(`The server is running at http://localhost:${port}/`);
294
+ });
295
+ ```
296
+
297
+ [More complex example](https://github.com/NSXBet/react-relay-network-modern/blob/main/examples/dataLoaderPerBatchRequest.js) of how you can use a single [DataLoader](https://github.com/facebook/dataloader) for all (batched) queries within a one HTTP-request.
298
+
299
+ If you are on Koa@2, [koa-graphql-batch](https://github.com/mattecapu/koa-graphql-batch) provides the same functionality as `graphqlBatchHTTPWrapper` (see its docs for usage example).
300
+
301
+ #### ...on client
302
+
303
+ And right after server side ready to accept batch queries, you may enable batching on the **client**:
304
+
305
+ ```js
306
+ const network = new RelayNetworkLayer([
307
+ batchMiddleware({
308
+ batchUrl: '/graphql/batch', // <--- route for batch queries
309
+ }),
310
+ ]);
311
+ ```
312
+
313
+ ### How batching works internally
314
+
315
+ Internally batching in `NetworkLayer` prepare list of queries `[ {query, variables}, ...]` sends it to server. And server returns list of results `[ {data}, ...]`. The server is expected to return the results in the same order as the requests.
316
+
317
+ ## Contribute
318
+
319
+ I actively welcome pull requests with code and doc fixes.
320
+ Also if you made great middleware and want share it within this module, please feel free to open PR.
321
+
322
+ ## License
323
+
324
+ [MIT](https://github.com/NSXBet/react-relay-network-modern/blob/main/LICENSE.md)
@@ -0,0 +1,9 @@
1
+ //#region src/RRNLError.ts
2
+ var RRNLError = class extends Error {
3
+ constructor(msg) {
4
+ super(msg);
5
+ this.name = "RRNLError";
6
+ }
7
+ };
8
+ //#endregion
9
+ exports.default = RRNLError;
@@ -0,0 +1,6 @@
1
+ //#region src/RRNLError.d.ts
2
+ declare class RRNLError extends Error {
3
+ constructor(msg: string);
4
+ }
5
+ //#endregion
6
+ export { RRNLError };
@@ -0,0 +1,6 @@
1
+ //#region src/RRNLError.d.ts
2
+ declare class RRNLError extends Error {
3
+ constructor(msg: string);
4
+ }
5
+ //#endregion
6
+ export { RRNLError };
@@ -0,0 +1,9 @@
1
+ //#region src/RRNLError.ts
2
+ var RRNLError = class extends Error {
3
+ constructor(msg) {
4
+ super(msg);
5
+ this.name = "RRNLError";
6
+ }
7
+ };
8
+ //#endregion
9
+ export { RRNLError as default };
@@ -0,0 +1,54 @@
1
+ const require_RelayRequest = require("./RelayRequest.cjs");
2
+ const require_fetchWithMiddleware = require("./fetchWithMiddleware.cjs");
3
+ let relay_runtime = require("relay-runtime");
4
+ //#region src/RelayNetworkLayer.ts
5
+ var RelayNetworkLayer = class {
6
+ _middlewares;
7
+ _rawMiddlewares;
8
+ _middlewaresSync;
9
+ execute;
10
+ executeWithEvents;
11
+ fetchFn;
12
+ subscribeFn;
13
+ noThrow;
14
+ constructor(middlewares, opts) {
15
+ this._middlewares = [];
16
+ this._rawMiddlewares = [];
17
+ this._middlewaresSync = [];
18
+ this.noThrow = false;
19
+ (Array.isArray(middlewares) ? middlewares : [middlewares]).forEach((mw) => {
20
+ if (mw) if (mw.execute) this._middlewaresSync.push(mw.execute);
21
+ else if (mw.isRawMiddleware) this._rawMiddlewares.push(mw);
22
+ else this._middlewares.push(mw);
23
+ });
24
+ if (opts) {
25
+ this.subscribeFn = opts.subscribeFn;
26
+ this.noThrow = opts.noThrow === true;
27
+ if (opts.beforeFetch) this._middlewaresSync.push(opts.beforeFetch);
28
+ }
29
+ this.fetchFn = (operation, variables, cacheConfig, uploadables) => {
30
+ for (let i = 0; i < this._middlewaresSync.length; i++) {
31
+ const res = this._middlewaresSync[i](operation, variables, cacheConfig, uploadables);
32
+ if (res) return res;
33
+ }
34
+ return { subscribe: (sink) => {
35
+ const req = new require_RelayRequest.default(operation, variables, cacheConfig, uploadables);
36
+ require_fetchWithMiddleware.default(req, this._middlewares, this._rawMiddlewares, this.noThrow).then((value) => {
37
+ sink.next(value);
38
+ sink.complete();
39
+ }, (error) => {
40
+ if (error && error.name && error.name === "AbortError") sink.complete();
41
+ else sink.error(error);
42
+ }).catch(() => {});
43
+ return () => {
44
+ req.cancel();
45
+ };
46
+ } };
47
+ };
48
+ const network = relay_runtime.Network.create(this.fetchFn, this.subscribeFn);
49
+ this.execute = network.execute;
50
+ this.executeWithEvents = network.executeWithEvents;
51
+ }
52
+ };
53
+ //#endregion
54
+ exports.default = RelayNetworkLayer;
@@ -0,0 +1,21 @@
1
+ import { FetchFunction, FetchHookFunction, Middleware, MiddlewareRaw, MiddlewareSync, RNLExecuteFunction, SubscribeFunction } from "./definition.cjs";
2
+
3
+ //#region src/RelayNetworkLayer.d.ts
4
+ interface RelayNetworkLayerOpts {
5
+ subscribeFn?: SubscribeFunction;
6
+ beforeFetch?: FetchHookFunction;
7
+ noThrow?: boolean;
8
+ }
9
+ declare class RelayNetworkLayer {
10
+ _middlewares: Middleware[];
11
+ _rawMiddlewares: MiddlewareRaw[];
12
+ _middlewaresSync: RNLExecuteFunction[];
13
+ execute: RNLExecuteFunction;
14
+ executeWithEvents: any;
15
+ readonly fetchFn: FetchFunction;
16
+ readonly subscribeFn?: SubscribeFunction;
17
+ readonly noThrow: boolean;
18
+ constructor(middlewares: Array<Middleware | MiddlewareSync | MiddlewareRaw | null>, opts?: RelayNetworkLayerOpts);
19
+ }
20
+ //#endregion
21
+ export { RelayNetworkLayer, RelayNetworkLayerOpts };
@@ -0,0 +1,21 @@
1
+ import { FetchFunction, FetchHookFunction, Middleware, MiddlewareRaw, MiddlewareSync, RNLExecuteFunction, SubscribeFunction } from "./definition.js";
2
+
3
+ //#region src/RelayNetworkLayer.d.ts
4
+ interface RelayNetworkLayerOpts {
5
+ subscribeFn?: SubscribeFunction;
6
+ beforeFetch?: FetchHookFunction;
7
+ noThrow?: boolean;
8
+ }
9
+ declare class RelayNetworkLayer {
10
+ _middlewares: Middleware[];
11
+ _rawMiddlewares: MiddlewareRaw[];
12
+ _middlewaresSync: RNLExecuteFunction[];
13
+ execute: RNLExecuteFunction;
14
+ executeWithEvents: any;
15
+ readonly fetchFn: FetchFunction;
16
+ readonly subscribeFn?: SubscribeFunction;
17
+ readonly noThrow: boolean;
18
+ constructor(middlewares: Array<Middleware | MiddlewareSync | MiddlewareRaw | null>, opts?: RelayNetworkLayerOpts);
19
+ }
20
+ //#endregion
21
+ export { RelayNetworkLayer, RelayNetworkLayerOpts };
@@ -0,0 +1,54 @@
1
+ import RelayRequest from "./RelayRequest.js";
2
+ import fetchWithMiddleware from "./fetchWithMiddleware.js";
3
+ import { Network } from "relay-runtime";
4
+ //#region src/RelayNetworkLayer.ts
5
+ var RelayNetworkLayer = class {
6
+ _middlewares;
7
+ _rawMiddlewares;
8
+ _middlewaresSync;
9
+ execute;
10
+ executeWithEvents;
11
+ fetchFn;
12
+ subscribeFn;
13
+ noThrow;
14
+ constructor(middlewares, opts) {
15
+ this._middlewares = [];
16
+ this._rawMiddlewares = [];
17
+ this._middlewaresSync = [];
18
+ this.noThrow = false;
19
+ (Array.isArray(middlewares) ? middlewares : [middlewares]).forEach((mw) => {
20
+ if (mw) if (mw.execute) this._middlewaresSync.push(mw.execute);
21
+ else if (mw.isRawMiddleware) this._rawMiddlewares.push(mw);
22
+ else this._middlewares.push(mw);
23
+ });
24
+ if (opts) {
25
+ this.subscribeFn = opts.subscribeFn;
26
+ this.noThrow = opts.noThrow === true;
27
+ if (opts.beforeFetch) this._middlewaresSync.push(opts.beforeFetch);
28
+ }
29
+ this.fetchFn = (operation, variables, cacheConfig, uploadables) => {
30
+ for (let i = 0; i < this._middlewaresSync.length; i++) {
31
+ const res = this._middlewaresSync[i](operation, variables, cacheConfig, uploadables);
32
+ if (res) return res;
33
+ }
34
+ return { subscribe: (sink) => {
35
+ const req = new RelayRequest(operation, variables, cacheConfig, uploadables);
36
+ fetchWithMiddleware(req, this._middlewares, this._rawMiddlewares, this.noThrow).then((value) => {
37
+ sink.next(value);
38
+ sink.complete();
39
+ }, (error) => {
40
+ if (error && error.name && error.name === "AbortError") sink.complete();
41
+ else sink.error(error);
42
+ }).catch(() => {});
43
+ return () => {
44
+ req.cancel();
45
+ };
46
+ } };
47
+ };
48
+ const network = Network.create(this.fetchFn, this.subscribeFn);
49
+ this.execute = network.execute;
50
+ this.executeWithEvents = network.executeWithEvents;
51
+ }
52
+ };
53
+ //#endregion
54
+ export { RelayNetworkLayer as default };
@@ -0,0 +1,90 @@
1
+ const require_RRNLError = require("./RRNLError.cjs");
2
+ //#region src/RelayRequest.ts
3
+ function getFormDataInterface() {
4
+ return typeof window !== "undefined" && window.FormData || global && global.FormData;
5
+ }
6
+ var RelayRequest = class {
7
+ static lastGenId;
8
+ id;
9
+ fetchOpts;
10
+ operation;
11
+ variables;
12
+ cacheConfig;
13
+ uploadables;
14
+ controller;
15
+ constructor(operation, variables, cacheConfig, uploadables) {
16
+ this.operation = operation;
17
+ this.variables = variables;
18
+ this.cacheConfig = cacheConfig;
19
+ this.uploadables = uploadables;
20
+ this.id = this.operation.id || this.operation.name || this._generateID();
21
+ const fetchOpts = {
22
+ method: "POST",
23
+ headers: {},
24
+ body: this.prepareBody()
25
+ };
26
+ this.controller = typeof window !== "undefined" && window.AbortController ? new window.AbortController() : null;
27
+ if (this.controller) fetchOpts.signal = this.controller.signal;
28
+ this.fetchOpts = fetchOpts;
29
+ }
30
+ getBody() {
31
+ return this.fetchOpts.body;
32
+ }
33
+ prepareBody() {
34
+ const { uploadables } = this;
35
+ if (uploadables) {
36
+ const _FormData_ = getFormDataInterface();
37
+ if (!_FormData_) throw new require_RRNLError.default("Uploading files without `FormData` interface does not supported.");
38
+ const formData = new _FormData_();
39
+ formData.append("id", this.getID());
40
+ formData.append("query", this.getQueryString());
41
+ formData.append("variables", JSON.stringify(this.getVariables()));
42
+ Object.keys(uploadables).forEach((key) => {
43
+ if (Object.prototype.hasOwnProperty.call(uploadables, key)) formData.append(key, uploadables[key]);
44
+ });
45
+ return formData;
46
+ }
47
+ return JSON.stringify({
48
+ id: this.getID(),
49
+ query: this.getQueryString(),
50
+ variables: this.getVariables()
51
+ });
52
+ }
53
+ getID() {
54
+ return this.id;
55
+ }
56
+ _generateID() {
57
+ const ctor = this.constructor;
58
+ if (!ctor.lastGenId) ctor.lastGenId = 0;
59
+ ctor.lastGenId += 1;
60
+ return ctor.lastGenId.toString();
61
+ }
62
+ getQueryString() {
63
+ return this.operation.text || "";
64
+ }
65
+ getVariables() {
66
+ return this.variables;
67
+ }
68
+ isMutation() {
69
+ return this.operation.operationKind === "mutation";
70
+ }
71
+ isFormData() {
72
+ const _FormData_ = getFormDataInterface();
73
+ return !!_FormData_ && this.fetchOpts.body instanceof _FormData_;
74
+ }
75
+ cancel() {
76
+ if (this.controller) {
77
+ this.controller.abort();
78
+ return true;
79
+ }
80
+ return false;
81
+ }
82
+ clone() {
83
+ const newRequest = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
84
+ newRequest.fetchOpts = { ...this.fetchOpts };
85
+ newRequest.fetchOpts.headers = { ...this.fetchOpts.headers };
86
+ return newRequest;
87
+ }
88
+ };
89
+ //#endregion
90
+ exports.default = RelayRequest;
@@ -0,0 +1,26 @@
1
+ import { CacheConfig, ConcreteBatch, FetchOpts, UploadableMap, Variables } from "./definition.cjs";
2
+
3
+ //#region src/RelayRequest.d.ts
4
+ declare class RelayRequest {
5
+ static lastGenId: number;
6
+ id: string;
7
+ fetchOpts: FetchOpts;
8
+ operation: ConcreteBatch;
9
+ variables: Variables;
10
+ cacheConfig: CacheConfig;
11
+ uploadables?: UploadableMap | null;
12
+ controller: AbortController | null;
13
+ constructor(operation: ConcreteBatch, variables: Variables, cacheConfig: CacheConfig, uploadables?: UploadableMap | null);
14
+ getBody(): string | FormData;
15
+ prepareBody(): string | FormData;
16
+ getID(): string;
17
+ _generateID(): string;
18
+ getQueryString(): string;
19
+ getVariables(): Variables;
20
+ isMutation(): boolean;
21
+ isFormData(): boolean;
22
+ cancel(): boolean;
23
+ clone(): RelayRequest;
24
+ }
25
+ //#endregion
26
+ export { RelayRequest };
@@ -0,0 +1,26 @@
1
+ import { CacheConfig, ConcreteBatch, FetchOpts, UploadableMap, Variables } from "./definition.js";
2
+
3
+ //#region src/RelayRequest.d.ts
4
+ declare class RelayRequest {
5
+ static lastGenId: number;
6
+ id: string;
7
+ fetchOpts: FetchOpts;
8
+ operation: ConcreteBatch;
9
+ variables: Variables;
10
+ cacheConfig: CacheConfig;
11
+ uploadables?: UploadableMap | null;
12
+ controller: AbortController | null;
13
+ constructor(operation: ConcreteBatch, variables: Variables, cacheConfig: CacheConfig, uploadables?: UploadableMap | null);
14
+ getBody(): string | FormData;
15
+ prepareBody(): string | FormData;
16
+ getID(): string;
17
+ _generateID(): string;
18
+ getQueryString(): string;
19
+ getVariables(): Variables;
20
+ isMutation(): boolean;
21
+ isFormData(): boolean;
22
+ cancel(): boolean;
23
+ clone(): RelayRequest;
24
+ }
25
+ //#endregion
26
+ export { RelayRequest };