@superbia/untrue 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 iconshot
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.
package/README.md ADDED
@@ -0,0 +1,524 @@
1
+ # @superbia/untrue
2
+
3
+ Integrate Superbia and Untrue.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ npm i @superbia/untrue
9
+ ```
10
+
11
+ ## Get started
12
+
13
+ We will use the Untrue's Context API to integrate Superbia and Untrue. Two contexts are needed:
14
+
15
+ - `DocumentContext`: It will store all the documents returned by the Superbia requests and subscriptions, e.g., users, posts, comments, etc.
16
+ - `RequestContext` It will store all the requests we do via a Superbia client.
17
+
18
+ #### What is a Document?
19
+
20
+ A document is an object that has an ID and a typename.
21
+
22
+ ```json
23
+ {
24
+ "user": {
25
+ "_typename": "User",
26
+ "_id": "123",
27
+ "name": "Jhon Doe"
28
+ }
29
+ }
30
+ ```
31
+
32
+ In this case the ID is found at `_id`. This may change from app to app according to how your data has been structured. Common keys for ID are: `id`, `ID`, `_id`.
33
+
34
+ `_typename`, on the other hand, is automatically added by Superbia in the server side.
35
+
36
+ ## DocumentContext
37
+
38
+ `DocumentContext` will intercept any data handled by the `client` and it will group the documents based on their ID and typename.
39
+
40
+ If we receive some client data like:
41
+
42
+ ```json
43
+ {
44
+ "user": {
45
+ "_typename": "User",
46
+ "_id": "123",
47
+ "name": "Jhon Doe",
48
+ "username": "jhondoe",
49
+ "lastPost": {
50
+ "_typename": "Post",
51
+ "_id": "456",
52
+ "text": "Hello world"
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ... the `DocumentContext` data will be:
59
+
60
+ ```js
61
+ this.documents = {
62
+ User: {
63
+ 123: {
64
+ _typename: "User",
65
+ _id: "123",
66
+ name: "Jhon Doe",
67
+ username: "jhondoe",
68
+ lastPost: "456",
69
+ },
70
+ },
71
+ Post: {
72
+ 456: {
73
+ _typename: "Post",
74
+ _id: "456",
75
+ text: "Hello world",
76
+ },
77
+ },
78
+ };
79
+ ```
80
+
81
+ Notice how for `lastPost` we only store the `id`. We do this to have a single source of truth for every document.
82
+
83
+ ### Creation
84
+
85
+ ```js
86
+ import { DocumentContext } from "@superbia/untrue";
87
+
88
+ import { client } from "./client";
89
+
90
+ class AppDocumentContext extends DocumentContext {
91
+ onFollow = (userId) => {
92
+ const user = this.documents.User[userId]; // get the user
93
+
94
+ // update user
95
+
96
+ user.following = true;
97
+ user.followersCount++;
98
+
99
+ this.update(); // notify the Wrapper of changes
100
+ };
101
+
102
+ onUnfollow = (userId) => {
103
+ const user = this.documents.User[userId]; // get the user
104
+
105
+ // update user
106
+
107
+ user.following = false;
108
+ user.followersCount--;
109
+
110
+ this.update(); // notify the Wrapper of changes
111
+ };
112
+ }
113
+
114
+ export default new AppDocumentContext(client, {
115
+ id: "_id",
116
+ typename: "_typename",
117
+ }); // the client and the keys of documents
118
+ ```
119
+
120
+ ### Usage
121
+
122
+ ```js
123
+ import { Node, Wrapper } from "untrue";
124
+
125
+ import AppDocumentContext from "./AppDocumentContext";
126
+
127
+ function User({ userId, name, username, following, onFollow, onUnfollow }) {
128
+ const onFollowUser = () => onFollow(userId);
129
+ const onUnfollowUser = () => onUnfollow(userId);
130
+
131
+ return [
132
+ new Node("span", name),
133
+ new Node("span", `@${username}`),
134
+ new Node(
135
+ "button",
136
+ {
137
+ onclick: following ? onUnfollowUser : onFollowUser,
138
+ },
139
+ following ? "unfollow" : "follow"
140
+ ),
141
+ ];
142
+ }
143
+
144
+ export default Wrapper.wrapContext(User, AppDocumentContext, (props) => {
145
+ const { userId } = props;
146
+
147
+ const documents = AppDocumentContext.getDocuments(); // all the documents
148
+
149
+ const { name, username, following } = documents.User[userId]; // the desired user document
150
+
151
+ const { onFollow, onUnfollow } = AppDocumentContext; // context handlers
152
+
153
+ return { name, username, following, onFollow, onUnfollow }; // data the component needs
154
+ });
155
+ ```
156
+
157
+ ## RequestContext
158
+
159
+ `RequestContext` will keep the state of the requests.
160
+
161
+ Every `request` has 4 properties:
162
+
163
+ - `loading`: `Boolean`. `true` if the request is loading.
164
+ - `done`: `Boolean`. `true` if the request has been completed and it has no error.
165
+ - `data`: `Object`. Result of the request. `null` if the request hasn't been completed yet or an error was found.
166
+ - `error`: `Error` object or `null`. It's an `Error` object if the request has been completed but there's an error in the request itself or in any endpoint.
167
+
168
+ The next example assumes we have an endpoint `userPosts` that returns an array of `Post` documents.
169
+
170
+ ```json
171
+ {
172
+ "userPosts": [
173
+ { "_typename": "Post", "_id": "123", "text": "Hello world" },
174
+ { "_typename": "Post", "_id": "456", "text": "Lorem ipsum" }
175
+ ]
176
+ }
177
+ ```
178
+
179
+ Just as in `DocumentContext`, we won't store the entire documents but their IDs only.
180
+
181
+ ```js
182
+ this.requests = {
183
+ someRequestKey: {
184
+ loading: false,
185
+ done: true,
186
+ error: null,
187
+ data: { userPosts: ["123", "456"] },
188
+ },
189
+ };
190
+ ```
191
+
192
+ ### Creation
193
+
194
+ ```js
195
+ import { RequestContext } from "@superbia/untrue";
196
+
197
+ import { client } from "./client";
198
+
199
+ class AppRequestContext extends RequestContext {
200
+ onSomeHandler = () => {
201
+ // we can update this.requests data directly from here
202
+ };
203
+ }
204
+
205
+ export default new AppRequestContext(client, {
206
+ id: "_id",
207
+ typename: "_typename",
208
+ }); // the client and the keys of documents
209
+ ```
210
+
211
+ ### Usage
212
+
213
+ ```js
214
+ import { Component, Node, Wrapper } from "untrue";
215
+
216
+ import { RequestWrapper } from "@superbia/untrue";
217
+
218
+ import Post from "./Post";
219
+
220
+ class PostList extends Component {
221
+ constructor(props) {
222
+ super(props);
223
+
224
+ this.on("mount", this.handleMountRequest); // request on mount
225
+ }
226
+
227
+ handleMountRequest = () => {
228
+ const { requestKey, userId, onRequest } = this.props;
229
+
230
+ // it will be requested as:
231
+ // client.request({ userPosts: { userId } })
232
+
233
+ onRequest(requestKey, { userPosts: { userId } });
234
+ };
235
+
236
+ render() {
237
+ const { loading, done, error, postIds } = this.props;
238
+
239
+ if (loading) {
240
+ return new Node("span", "Loading...");
241
+ }
242
+
243
+ if (error !== null) {
244
+ return new Node("span", error.message);
245
+ }
246
+
247
+ if (done) {
248
+ return postIds.map((postId) => new Node(Post, { postId }));
249
+ }
250
+
251
+ return null;
252
+ }
253
+ }
254
+
255
+ // RequestWrapper will add the `requestKey` prop
256
+
257
+ export default RequestWrapper.wrapRequester(
258
+ Wrapper.wrapContext(PostList, AppRequestContext, (props) => {
259
+ const { requestKey, userId } = props;
260
+
261
+ const requests = AppRequestContext.getRequests(); // all the requests
262
+
263
+ // the desired request, it's undefined until onRequest is fired
264
+
265
+ const {
266
+ loading = false,
267
+ done = false,
268
+ error = null,
269
+ data = null,
270
+ } = requests?.[requestKey] ?? {};
271
+
272
+ const postIds = data !== null ? data.userPosts : [];
273
+
274
+ const { onRequest } = AppRequestContext; // context handler
275
+
276
+ return { loading, done, error, postIds, onRequest }; // data the component needs
277
+ })
278
+ );
279
+ ```
280
+
281
+ ### Intercepting requests
282
+
283
+ As we know, documents are stored in `DocumentContext` and requests are stored in `RequestContext`.
284
+
285
+ Let's say you want to update a document when a specific endpoint is requested.
286
+
287
+ You can `intercept` a request to do so. You need to override the `intercept` method to return an object of interceptors.
288
+
289
+ `AppDocumentContext.js`:
290
+
291
+ ```js
292
+ class AppDocumentContext extends DocumentContext {
293
+ onLike = (postId) => {
294
+ const post = this.documents.Post[postId];
295
+
296
+ post.liked = true;
297
+ post.likesCount++;
298
+
299
+ this.update();
300
+ };
301
+ }
302
+ ```
303
+
304
+ `AppRequestContext.js`:
305
+
306
+ ```js
307
+ import AppDocumentContext from "./AppDocumentContext";
308
+
309
+ class AppRequestContext extends RequestContext {
310
+ intercept() {
311
+ return {
312
+ likePost: {
313
+ load: (requestKey, endpoints) => {
314
+ // every time `likePost` is requested, this closure will be executed
315
+
316
+ const { postId } = endpoints.likePost;
317
+
318
+ AppDocumentContext.onLike(postId);
319
+ },
320
+ },
321
+ };
322
+ }
323
+ }
324
+ ```
325
+
326
+ Then in the Component, we call:
327
+
328
+ ```js
329
+ const { postId } = this.props;
330
+
331
+ onRequest(null, { likePost: { postId } });
332
+ ```
333
+
334
+ ### Paginated requests
335
+
336
+ To implement paginated requests the server must return a Pagination object.
337
+
338
+ ```json
339
+ {
340
+ "userPosts": {
341
+ "_typename": "PostPagination",
342
+ "nodes": [
343
+ { "_typename": "Post", "_id": "123", "text": "Hello world" },
344
+ { "_typename": "Post", "_id": "456", "text": "Lorem ipsum" }
345
+ ],
346
+ "pageInfo": {
347
+ "_typename": "PaginationPageInfo",
348
+ "hasNextPage": true,
349
+ "nextPageCursor": "789"
350
+ }
351
+ }
352
+ }
353
+ ```
354
+
355
+ `RequestContext` will manage to store the data like:
356
+
357
+ ```js
358
+ this.requests = {
359
+ someRequestKey: {
360
+ loading: false,
361
+ done: false,
362
+ error: null,
363
+ data: {
364
+ userPosts: {
365
+ loading: false,
366
+ error: null,
367
+ data: {
368
+ nodes: ["123", "456"],
369
+ pageInfo: { hasNextPage: true, nextPageCursor: "789" },
370
+ },
371
+ },
372
+ },
373
+ },
374
+ };
375
+ ```
376
+
377
+ Notice how we have two set of properties for `loading`, `error` and `data`. The first set will belong to the `onRequest` call while the second one will belong to the `onLoad` calls.
378
+
379
+ The rules of documents will be applied here, so every `node` will be stored as an ID only.
380
+
381
+ #### Usage
382
+
383
+ We will need two different components: one for the initial `onRequest` call and another one for the subsequent `onLoad` calls.
384
+
385
+ `PostList.js`
386
+
387
+ ```js
388
+ import { Component, Node, Wrapper } from "untrue";
389
+
390
+ import { RequestWrapper } from "@superbia/untrue";
391
+
392
+ import AppRequestContext from "./AppRequestContext";
393
+
394
+ import Content from "./Content";
395
+
396
+ class PostList extends Component {
397
+ constructor(props) {
398
+ super(props);
399
+
400
+ this.on("mount", this.handleMountRequest); // request on mount
401
+ }
402
+
403
+ handleMountRequest = () => {
404
+ const { requestKey, userId, onRequest } = this.props;
405
+
406
+ onRequest(requestKey, { userPosts: { userId, limit: 20 } });
407
+ };
408
+
409
+ render() {
410
+ const { requestKey, userId, loading, done, error } = this.props;
411
+
412
+ if (loading) {
413
+ return new Node("span", "Loading...");
414
+ }
415
+
416
+ if (error !== null) {
417
+ return new Node("span", error.message);
418
+ }
419
+
420
+ if (done) {
421
+ return new Node(Content, { requestKey, userId }); // pass requestKey and userId
422
+ }
423
+
424
+ return null;
425
+ }
426
+ }
427
+
428
+ export default RequestWrapper.wrapRequester(
429
+ Wrapper.wrapContext(PostList, AppRequestContext, (props) => {
430
+ const { requestKey } = props;
431
+
432
+ const requests = AppRequestContext.getRequests(); // all the requests
433
+
434
+ const {
435
+ loading = false,
436
+ done = false,
437
+ error = null,
438
+ } = requests?.[requestKey] ?? {}; // the desire request
439
+
440
+ const { onRequest } = AppRequestContext; // context handler
441
+
442
+ return { loading, done, error, onRequest }; // data the component needs
443
+ })
444
+ );
445
+ ```
446
+
447
+ `Content.js`
448
+
449
+ ```js
450
+ import { Node, Wrapper } from "untrue";
451
+
452
+ import AppRequestContext from "./AppRequestContext";
453
+
454
+ import Post from "./Post";
455
+
456
+ function Content({
457
+ requestKey,
458
+ userId,
459
+ loading,
460
+ error,
461
+ postIds,
462
+ hasNextPage,
463
+ nextPageCursor,
464
+ onLoad,
465
+ }) {
466
+ const onLoadNext = () => {
467
+ onLoad(requestKey, {
468
+ userPosts: { userId, limit: 20, cursor: nextPageCursor },
469
+ });
470
+ };
471
+
472
+ return [
473
+ postIds.map((postId) => new Node(Post, { postId })),
474
+ hasNextPage
475
+ ? new Node("button", { onclick: onLoadNext }, "load next page")
476
+ : null,
477
+ loading ? new Node("span", "Loading next page...") : null,
478
+ error !== null ? new Node("span", error.message) : null,
479
+ ];
480
+ }
481
+
482
+ // here we don't need RequestWrapper because we receive the "requestKey" as a prop already
483
+
484
+ export default Wrapper.wrapContext(Content, AppRequestContext, (props) => {
485
+ const { requestKey } = props;
486
+
487
+ const requests = AppRequestContext.getRequests(); // all the requests
488
+
489
+ const {
490
+ loading,
491
+ error,
492
+ data: {
493
+ nodes: postIds, // renaming for convenience
494
+ pageInfo: { hasNextPage, nextPageCursor },
495
+ },
496
+ } = requests[requestKey].data.userPosts; // the desired request's data
497
+
498
+ const { onLoad } = AppRequestContext; // context handler
499
+
500
+ return { loading, error, postIds, hasNextPage, nextPageCursor, onLoad }; // data the component needs
501
+ });
502
+ ```
503
+
504
+ ## RequestWrapper
505
+
506
+ `RequestWrapper` exposes a method `wrapRequester`. This method will add a `requestKey` prop to the component.
507
+
508
+ ```js
509
+ import { RequestWrapper } from "@superbia/untrue";
510
+
511
+ function Child({ requestKey }) {
512
+ // ...
513
+ }
514
+
515
+ const UsernameRequester = RequestWrapper.wrapRequester(Child, (props) => {
516
+ const { username } = props;
517
+
518
+ return username;
519
+ }); // requestKey will be the username prop
520
+
521
+ const ValueRequester = RequestWrapper.wrapRequester(Child, "profile"); // requestKey will be "profile"
522
+
523
+ const UniqueRequester = RequestWrapper.wrapRequester(Child); // requestKey will be a unique id
524
+ ```
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./src/DocumentContext";
2
+ export * from "./src/RequestContext";
3
+ export * from "./src/RequestWrapper";
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@superbia/untrue",
3
+ "version": "1.1.4",
4
+ "description": "Integrate Superbia and Untrue.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/iconshot/superbia-untrue.git"
8
+ },
9
+ "main": "index.js",
10
+ "keywords": [
11
+ "superbia",
12
+ "untrue"
13
+ ],
14
+ "license": "MIT",
15
+ "peerDependencies": {
16
+ "untrue": "^3.11.8"
17
+ },
18
+ "dependencies": {
19
+ "uuid": "^9.0.0"
20
+ }
21
+ }
@@ -0,0 +1,63 @@
1
+ import SuperbiaContext from "./SuperbiaContext";
2
+
3
+ export class DocumentContext extends SuperbiaContext {
4
+ constructor(client, documentKeys) {
5
+ super(documentKeys);
6
+
7
+ this.documents = {};
8
+
9
+ client
10
+ .on("request", (endpoints, emitter) => {
11
+ emitter.on("data", (data) => {
12
+ this.onData(data);
13
+ });
14
+ })
15
+ .on("subscribe", (endpoint, emitter) => {
16
+ emitter.on("data", (data) => {
17
+ this.onData(data);
18
+ });
19
+ });
20
+ }
21
+
22
+ // default persistence
23
+
24
+ hydrate(documents) {
25
+ this.documents = documents;
26
+ }
27
+
28
+ persist() {
29
+ return this.documents;
30
+ }
31
+
32
+ getDocuments() {
33
+ return this.documents;
34
+ }
35
+
36
+ onData(data) {
37
+ const newData = {};
38
+
39
+ Object.values(data).forEach((result) => {
40
+ this.parseResult(result, newData);
41
+ });
42
+
43
+ const types = Object.keys(newData);
44
+
45
+ if (types.length === 0) {
46
+ return;
47
+ }
48
+
49
+ for (const type of types) {
50
+ if (!(type in this.documents)) {
51
+ this.documents[type] = {};
52
+ }
53
+
54
+ const newDocuments = newData[type];
55
+
56
+ for (const id in newDocuments) {
57
+ this.documents[type][id] = newDocuments[id];
58
+ }
59
+ }
60
+
61
+ this.update();
62
+ }
63
+ }
@@ -0,0 +1,155 @@
1
+ import SuperbiaContext from "./SuperbiaContext";
2
+
3
+ export class RequestContext extends SuperbiaContext {
4
+ constructor(client, documentKeys) {
5
+ super(documentKeys);
6
+
7
+ this.requests = {};
8
+
9
+ this.client = client;
10
+ }
11
+
12
+ // default persistence
13
+
14
+ hydrate(requests) {
15
+ this.requests = requests;
16
+ }
17
+
18
+ persist() {
19
+ return this.requests;
20
+ }
21
+
22
+ getRequests() {
23
+ return this.requests;
24
+ }
25
+
26
+ intercept() {
27
+ return {};
28
+ }
29
+
30
+ parseRequestResult(result) {
31
+ if (
32
+ result !== null &&
33
+ typeof result === "object" &&
34
+ this.documentKeys.typename in result &&
35
+ result[this.documentKeys.typename].endsWith("Pagination")
36
+ ) {
37
+ return { loading: false, error: null, data: this.parseResult(result) };
38
+ } else {
39
+ return this.parseResult(result);
40
+ }
41
+ }
42
+
43
+ onRequest = async (key, endpoints, payload = null) => {
44
+ key = key !== null && key !== undefined ? key : Date.now().toString();
45
+
46
+ this.requests[key] = {
47
+ loading: true,
48
+ done: false,
49
+ error: null,
50
+ data: null,
51
+ };
52
+
53
+ const interceptors = this.intercept();
54
+
55
+ const endpointKeys = Object.keys(endpoints);
56
+
57
+ for (const endpointKey of endpointKeys) {
58
+ if (endpointKey in interceptors && "load" in interceptors[endpointKey]) {
59
+ interceptors[endpointKey].load(key, endpoints, payload);
60
+ }
61
+ }
62
+
63
+ this.update();
64
+
65
+ try {
66
+ const response = await this.client.request(endpoints);
67
+
68
+ const data = response.data();
69
+
70
+ const newData = {};
71
+
72
+ for (const key in data) {
73
+ newData[key] = this.parseRequestResult(data[key]);
74
+ }
75
+
76
+ this.requests[key] = {
77
+ loading: false,
78
+ done: true,
79
+ error: null,
80
+ data: newData,
81
+ };
82
+
83
+ for (const endpointKey of endpointKeys) {
84
+ if (
85
+ endpointKey in interceptors &&
86
+ "data" in interceptors[endpointKey]
87
+ ) {
88
+ interceptors[endpointKey].data(key, endpoints, payload, data);
89
+ }
90
+ }
91
+
92
+ this.update();
93
+ } catch (error) {
94
+ this.requests[key] = { loading: false, done: false, error, data: null };
95
+
96
+ for (const endpointKey of endpointKeys) {
97
+ if (
98
+ endpointKey in interceptors &&
99
+ "error" in interceptors[endpointKey]
100
+ ) {
101
+ interceptors[endpointKey].error(key, endpoints, payload, error);
102
+ }
103
+ }
104
+
105
+ this.update();
106
+ }
107
+ };
108
+
109
+ onLoad = async (key, endpoints, payload = null) => {
110
+ const interceptors = this.intercept();
111
+
112
+ const endpointKey = Object.keys(endpoints)[0];
113
+
114
+ const result = this.requests[key].data[endpointKey];
115
+
116
+ result.loading = true;
117
+ result.error = null;
118
+
119
+ if (endpointKey in interceptors && "load" in interceptors[endpointKey]) {
120
+ interceptors[endpointKey].load(key, endpoints, payload);
121
+ }
122
+
123
+ this.update();
124
+
125
+ try {
126
+ const response = await this.client.request(endpoints);
127
+
128
+ const data = response.data();
129
+
130
+ const endpointResult = this.parseResult(data[endpointKey]);
131
+
132
+ result.loading = false;
133
+
134
+ result.data = {
135
+ ...endpointResult,
136
+ nodes: [...result.data.nodes, ...endpointResult.nodes],
137
+ };
138
+
139
+ if (endpointKey in interceptors && "data" in interceptors[endpointKey]) {
140
+ interceptors[endpointKey].data(key, endpoints, payload, data);
141
+ }
142
+
143
+ this.update();
144
+ } catch (error) {
145
+ result.loading = false;
146
+ result.error = error;
147
+
148
+ if (endpointKey in interceptors && "error" in interceptors[endpointKey]) {
149
+ interceptors[endpointKey].error(key, endpoints, payload, error);
150
+ }
151
+
152
+ this.update();
153
+ }
154
+ };
155
+ }
@@ -0,0 +1,39 @@
1
+ import { Component, Node } from "untrue";
2
+
3
+ import { v4 as uuid } from "uuid";
4
+
5
+ export class RequestWrapper {
6
+ static wrapRequester(Child, requestKeyExtractor = null) {
7
+ return class RequestComponent extends Component {
8
+ constructor(props) {
9
+ super(props);
10
+
11
+ let key = null;
12
+
13
+ if (requestKeyExtractor !== null) {
14
+ if (typeof requestKeyExtractor === "function") {
15
+ key = requestKeyExtractor(props);
16
+ } else {
17
+ key = requestKeyExtractor;
18
+ }
19
+ }
20
+
21
+ if (key === null) {
22
+ key = uuid();
23
+ }
24
+
25
+ this.requestKey = key;
26
+ }
27
+
28
+ render() {
29
+ const { children, ...props } = this.props;
30
+
31
+ return new Node(
32
+ Child,
33
+ { ...props, requestKey: this.requestKey },
34
+ children
35
+ );
36
+ }
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,49 @@
1
+ import { Context } from "untrue";
2
+
3
+ class SuperbiaContext extends Context {
4
+ constructor(documentKeys) {
5
+ super();
6
+
7
+ this.documentKeys = documentKeys;
8
+ }
9
+
10
+ parseResult(result, data = {}) {
11
+ if (result === null) {
12
+ return null;
13
+ }
14
+
15
+ if (Array.isArray(result)) {
16
+ return result.map((item) => this.parseResult(item, data));
17
+ }
18
+
19
+ if (typeof result === "object") {
20
+ const newResult = {};
21
+
22
+ for (const key in result) {
23
+ newResult[key] = this.parseResult(result[key], data);
24
+ }
25
+
26
+ const isDocument =
27
+ this.documentKeys.id in result && this.documentKeys.typename in result;
28
+
29
+ if (!isDocument) {
30
+ return newResult;
31
+ }
32
+
33
+ const id = result[this.documentKeys.id];
34
+ const typename = result[this.documentKeys.typename];
35
+
36
+ if (!(typename in data)) {
37
+ data[typename] = {};
38
+ }
39
+
40
+ data[typename][id] = newResult;
41
+
42
+ return result[this.documentKeys.id];
43
+ }
44
+
45
+ return result;
46
+ }
47
+ }
48
+
49
+ export default SuperbiaContext;