@ttoss/appsync-api 0.7.5 → 0.8.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.
- package/README.md +33 -0
- package/dist/esm/{chunk-CXLYPHQJ.js → chunk-Y27A2LBZ.js} +3 -2
- package/dist/esm/cli.js +1 -1
- package/dist/esm/index.js +162 -4
- package/dist/index.d.ts +20 -2
- package/dist/index.js +181 -10
- package/package.json +4 -3
- package/src/composeWithRelay/composeWithRelay.ts +67 -0
- package/src/composeWithRelay/globalId.ts +32 -0
- package/src/composeWithRelay/index.ts +7 -0
- package/src/composeWithRelay/nodeFieldConfig.ts +82 -0
- package/src/composeWithRelay/nodeInterface.ts +31 -0
- package/src/index.ts +2 -0
package/README.md
CHANGED
|
@@ -77,3 +77,36 @@ server.listen(4000, () => {
|
|
|
77
77
|
);
|
|
78
78
|
});
|
|
79
79
|
```
|
|
80
|
+
|
|
81
|
+
## Schema Composer
|
|
82
|
+
|
|
83
|
+
The `schemaComposer` object is a [`graphql-compose`](https://graphql-compose.github.io/docs/intro/quick-start.html) object that defines your API's schema and handle resolvers. You can use `schemaComposer` to create types, queries, mutations, and subscriptions.
|
|
84
|
+
|
|
85
|
+
### Connections
|
|
86
|
+
|
|
87
|
+
This packages provides the method `composeWithConnection` to create a connection type and queries for a given type, based on [graphql-compose-connection](https://graphql-compose.github.io/docs/plugins/plugin-connection.html) plugin and following the [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm).
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { composeWithConnection } from '@ttoss/appsync-api';
|
|
91
|
+
|
|
92
|
+
composeWithConnection(AuthorTC, {
|
|
93
|
+
findManyResolver: AuthorTC.getResolver('findMany'),
|
|
94
|
+
countResolver: AuthorTC.getResolver('count'),
|
|
95
|
+
sort: {
|
|
96
|
+
ASC: {
|
|
97
|
+
value: {
|
|
98
|
+
scanIndexForward: true,
|
|
99
|
+
},
|
|
100
|
+
cursorFields: ['id'],
|
|
101
|
+
beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
|
|
102
|
+
if (!rawQuery.id) rawQuery.id = {};
|
|
103
|
+
rawQuery.id.$lt = cursorData.id;
|
|
104
|
+
},
|
|
105
|
+
afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
|
|
106
|
+
if (!rawQuery.id) rawQuery.id = {};
|
|
107
|
+
rawQuery.id.$gt = cursorData.id;
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
@@ -79,7 +79,7 @@ var getPackageLambdaLayerStackName = (packageName) => {
|
|
|
79
79
|
// package.json
|
|
80
80
|
var package_default = {
|
|
81
81
|
name: "@ttoss/appsync-api",
|
|
82
|
-
version: "0.
|
|
82
|
+
version: "0.8.0",
|
|
83
83
|
description: "A library for building GraphQL APIs for AWS AppSync.",
|
|
84
84
|
license: "UNLICENSED",
|
|
85
85
|
author: "ttoss",
|
|
@@ -114,8 +114,9 @@ var package_default = {
|
|
|
114
114
|
dependencies: {
|
|
115
115
|
"@ttoss/cloudformation": "^0.5.1",
|
|
116
116
|
express: "^4.18.2",
|
|
117
|
+
"graphql-compose-connection": "^8.2.1",
|
|
117
118
|
"graphql-helix": "^1.13.0",
|
|
118
|
-
minimist: "^1.2.
|
|
119
|
+
minimist: "^1.2.8"
|
|
119
120
|
},
|
|
120
121
|
peerDependencies: {
|
|
121
122
|
graphql: "^16.6.0",
|
package/dist/esm/cli.js
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
2
|
import {
|
|
3
3
|
createApiTemplate
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Y27A2LBZ.js";
|
|
5
5
|
|
|
6
6
|
// src/createAppSyncResolverHandler.ts
|
|
7
7
|
var createAppSyncResolverHandler = ({
|
|
8
|
-
schemaComposer
|
|
8
|
+
schemaComposer: schemaComposer2
|
|
9
9
|
}) => {
|
|
10
10
|
return async (event, context) => {
|
|
11
11
|
const { info, arguments: args, source } = event;
|
|
12
12
|
const { parentTypeName, fieldName } = info;
|
|
13
|
-
const resolver =
|
|
13
|
+
const resolver = schemaComposer2.getResolveMethods()[parentTypeName][fieldName];
|
|
14
14
|
return resolver(
|
|
15
15
|
source,
|
|
16
16
|
args,
|
|
@@ -19,7 +19,165 @@ var createAppSyncResolverHandler = ({
|
|
|
19
19
|
);
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
+
|
|
23
|
+
// src/composeWithRelay/composeWithRelay.ts
|
|
24
|
+
import { ObjectTypeComposer } from "graphql-compose";
|
|
25
|
+
|
|
26
|
+
// src/composeWithRelay/nodeFieldConfig.ts
|
|
27
|
+
import {
|
|
28
|
+
getProjectionFromAST
|
|
29
|
+
} from "graphql-compose";
|
|
30
|
+
|
|
31
|
+
// src/composeWithRelay/globalId.ts
|
|
32
|
+
var base64 = (i) => {
|
|
33
|
+
return Buffer.from(i, "ascii").toString("base64");
|
|
34
|
+
};
|
|
35
|
+
var unbase64 = (i) => {
|
|
36
|
+
return Buffer.from(i, "base64").toString("ascii");
|
|
37
|
+
};
|
|
38
|
+
var toGlobalId = (type, id) => {
|
|
39
|
+
return base64([type, id].join(":"));
|
|
40
|
+
};
|
|
41
|
+
var fromGlobalId = (globalId) => {
|
|
42
|
+
const unbasedGlobalId = unbase64(globalId);
|
|
43
|
+
const [type, id] = unbasedGlobalId.split(":");
|
|
44
|
+
return { type, id };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/composeWithRelay/nodeFieldConfig.ts
|
|
48
|
+
var getNodeFieldConfig = (typeMapForRelayNode, nodeInterface) => {
|
|
49
|
+
return {
|
|
50
|
+
description: "Fetches an object that has globally unique ID among all types",
|
|
51
|
+
type: nodeInterface,
|
|
52
|
+
args: {
|
|
53
|
+
id: {
|
|
54
|
+
type: "ID!",
|
|
55
|
+
description: "The globally unique ID among all types"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
resolve: (source, args, context, info) => {
|
|
59
|
+
if (!args.id || !(typeof args.id === "string")) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const { type } = fromGlobalId(args.id);
|
|
63
|
+
if (!typeMapForRelayNode[type]) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const { tc, resolver: findById } = typeMapForRelayNode[type];
|
|
67
|
+
if (findById && findById.resolve && tc) {
|
|
68
|
+
const graphqlType = tc.getType();
|
|
69
|
+
let projection;
|
|
70
|
+
if (info) {
|
|
71
|
+
projection = getProjectionFromAST({
|
|
72
|
+
...info,
|
|
73
|
+
returnType: graphqlType
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
projection = {};
|
|
77
|
+
}
|
|
78
|
+
const idArgName = Object.keys(findById.args)[0];
|
|
79
|
+
return findById.resolve({
|
|
80
|
+
source,
|
|
81
|
+
args: { [idArgName]: args.id },
|
|
82
|
+
// eg. mongoose has _id fieldname, so should map
|
|
83
|
+
context,
|
|
84
|
+
info,
|
|
85
|
+
projection
|
|
86
|
+
}).then((res) => {
|
|
87
|
+
if (!res)
|
|
88
|
+
return res;
|
|
89
|
+
res.__nodeType = graphqlType;
|
|
90
|
+
return res;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/composeWithRelay/nodeInterface.ts
|
|
99
|
+
import { InterfaceTypeComposer } from "graphql-compose";
|
|
100
|
+
var NodeTC = InterfaceTypeComposer.createTemp({
|
|
101
|
+
name: "Node",
|
|
102
|
+
description: "An object, that can be fetched by the globally unique ID among all types.",
|
|
103
|
+
fields: {
|
|
104
|
+
id: {
|
|
105
|
+
type: "ID!",
|
|
106
|
+
description: "The globally unique ID among all types."
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
resolveType: (payload) => {
|
|
110
|
+
return payload.__nodeType.name ? payload.__nodeType.name : null;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
var NodeInterface = NodeTC.getType();
|
|
114
|
+
var getNodeInterface = (sc) => {
|
|
115
|
+
if (sc.hasInstance("Node", InterfaceTypeComposer)) {
|
|
116
|
+
return sc.get("Node");
|
|
117
|
+
}
|
|
118
|
+
sc.set("Node", NodeTC);
|
|
119
|
+
return NodeTC;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/composeWithRelay/composeWithRelay.ts
|
|
123
|
+
var TypeMapForRelayNode = {};
|
|
124
|
+
var composeWithRelay = (tc) => {
|
|
125
|
+
if (!(tc instanceof ObjectTypeComposer)) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"You should provide ObjectTypeComposer instance to composeWithRelay method"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const nodeInterface = getNodeInterface(tc.schemaComposer);
|
|
131
|
+
const nodeFieldConfig = getNodeFieldConfig(
|
|
132
|
+
TypeMapForRelayNode,
|
|
133
|
+
nodeInterface
|
|
134
|
+
);
|
|
135
|
+
if (tc.getTypeName() === "Query" || tc.getTypeName() === "RootQuery") {
|
|
136
|
+
tc.setField("node", nodeFieldConfig);
|
|
137
|
+
return tc;
|
|
138
|
+
}
|
|
139
|
+
if (tc.getTypeName() === "Mutation" || tc.getTypeName() === "RootMutation") {
|
|
140
|
+
return tc;
|
|
141
|
+
}
|
|
142
|
+
if (!tc.hasRecordIdFn()) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`ObjectTypeComposer(${tc.getTypeName()}) should have recordIdFn. This function returns ID from provided object.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const findById = tc.getResolver("findById");
|
|
148
|
+
if (!findById) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`ObjectTypeComposer(${tc.getTypeName()}) provided to composeWithRelay should have findById resolver.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
TypeMapForRelayNode[tc.getTypeName()] = {
|
|
154
|
+
resolver: findById,
|
|
155
|
+
tc
|
|
156
|
+
};
|
|
157
|
+
tc.addFields({
|
|
158
|
+
id: {
|
|
159
|
+
type: "ID!",
|
|
160
|
+
description: "The globally unique ID among all types",
|
|
161
|
+
resolve: (source) => {
|
|
162
|
+
return toGlobalId(tc.getTypeName(), tc.getRecordId(source));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
tc.addInterface(nodeInterface);
|
|
167
|
+
return tc;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/composeWithRelay/index.ts
|
|
171
|
+
import { schemaComposer } from "graphql-compose";
|
|
172
|
+
composeWithRelay(schemaComposer.Query);
|
|
173
|
+
|
|
174
|
+
// src/index.ts
|
|
175
|
+
import { default as default2 } from "graphql-compose-connection";
|
|
22
176
|
export {
|
|
177
|
+
default2 as composeWithConnection,
|
|
178
|
+
composeWithRelay,
|
|
23
179
|
createApiTemplate,
|
|
24
|
-
createAppSyncResolverHandler
|
|
180
|
+
createAppSyncResolverHandler,
|
|
181
|
+
fromGlobalId,
|
|
182
|
+
toGlobalId
|
|
25
183
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { SchemaComposer } from 'graphql-compose';
|
|
1
|
+
import { SchemaComposer, ObjectTypeComposer } from 'graphql-compose';
|
|
2
2
|
import { CloudFormationTemplate } from '@ttoss/cloudformation';
|
|
3
3
|
import { AppSyncResolverHandler as AppSyncResolverHandler$1 } from 'aws-lambda';
|
|
4
4
|
export { AppSyncIdentityCognito } from 'aws-lambda';
|
|
5
|
+
export { default as composeWithConnection } from 'graphql-compose-connection';
|
|
5
6
|
|
|
6
7
|
type StringOrImport = string | {
|
|
7
8
|
'Fn::ImportValue': string;
|
|
@@ -33,4 +34,21 @@ declare const createAppSyncResolverHandler: ({ schemaComposer, }: {
|
|
|
33
34
|
schemaComposer: SchemaComposer<any>;
|
|
34
35
|
}) => AppSyncResolverHandler<any, any, any>;
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
declare const composeWithRelay: <TContext>(tc: ObjectTypeComposer<any, TContext>) => ObjectTypeComposer<any, TContext>;
|
|
38
|
+
|
|
39
|
+
type ResolvedGlobalId = {
|
|
40
|
+
type: string;
|
|
41
|
+
id: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Takes a type name and an ID specific to that type name, and returns a
|
|
45
|
+
* "global ID" that is unique among all types.
|
|
46
|
+
*/
|
|
47
|
+
declare const toGlobalId: (type: string, id: string | number) => string;
|
|
48
|
+
/**
|
|
49
|
+
* Takes the "global ID" created by toGlobalID, and returns the type name and ID
|
|
50
|
+
* used to create it.
|
|
51
|
+
*/
|
|
52
|
+
declare const fromGlobalId: (globalId: string) => ResolvedGlobalId;
|
|
53
|
+
|
|
54
|
+
export { AppSyncResolverHandler, composeWithRelay, createApiTemplate, createAppSyncResolverHandler, fromGlobalId, toGlobalId };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
9
|
var __export = (target, all) => {
|
|
8
10
|
for (var name in all)
|
|
@@ -16,13 +18,25 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
|
|
21
31
|
// src/index.ts
|
|
22
32
|
var src_exports = {};
|
|
23
33
|
__export(src_exports, {
|
|
34
|
+
composeWithConnection: () => import_graphql_compose_connection.default,
|
|
35
|
+
composeWithRelay: () => composeWithRelay,
|
|
24
36
|
createApiTemplate: () => createApiTemplate,
|
|
25
|
-
createAppSyncResolverHandler: () => createAppSyncResolverHandler
|
|
37
|
+
createAppSyncResolverHandler: () => createAppSyncResolverHandler,
|
|
38
|
+
fromGlobalId: () => fromGlobalId,
|
|
39
|
+
toGlobalId: () => toGlobalId
|
|
26
40
|
});
|
|
27
41
|
module.exports = __toCommonJS(src_exports);
|
|
28
42
|
|
|
@@ -105,7 +119,7 @@ var getPackageLambdaLayerStackName = (packageName) => {
|
|
|
105
119
|
// package.json
|
|
106
120
|
var package_default = {
|
|
107
121
|
name: "@ttoss/appsync-api",
|
|
108
|
-
version: "0.
|
|
122
|
+
version: "0.8.0",
|
|
109
123
|
description: "A library for building GraphQL APIs for AWS AppSync.",
|
|
110
124
|
license: "UNLICENSED",
|
|
111
125
|
author: "ttoss",
|
|
@@ -140,8 +154,9 @@ var package_default = {
|
|
|
140
154
|
dependencies: {
|
|
141
155
|
"@ttoss/cloudformation": "^0.5.1",
|
|
142
156
|
express: "^4.18.2",
|
|
157
|
+
"graphql-compose-connection": "^8.2.1",
|
|
143
158
|
"graphql-helix": "^1.13.0",
|
|
144
|
-
minimist: "^1.2.
|
|
159
|
+
minimist: "^1.2.8"
|
|
145
160
|
},
|
|
146
161
|
peerDependencies: {
|
|
147
162
|
graphql: "^16.6.0",
|
|
@@ -184,14 +199,14 @@ var AppSyncGraphQLApiKeyLogicalId = "AppSyncGraphQLApiKey";
|
|
|
184
199
|
var createApiTemplate = ({
|
|
185
200
|
additionalAuthenticationProviders,
|
|
186
201
|
authenticationType = "AMAZON_COGNITO_USER_POOLS",
|
|
187
|
-
schemaComposer,
|
|
202
|
+
schemaComposer: schemaComposer2,
|
|
188
203
|
dataSource,
|
|
189
204
|
lambdaFunction,
|
|
190
205
|
userPoolConfig
|
|
191
206
|
}) => {
|
|
192
|
-
const sdl =
|
|
193
|
-
import_graphql_compose.graphql.validateSchema(
|
|
194
|
-
const resolveMethods =
|
|
207
|
+
const sdl = schemaComposer2.toSDL();
|
|
208
|
+
import_graphql_compose.graphql.validateSchema(schemaComposer2.buildSchema());
|
|
209
|
+
const resolveMethods = schemaComposer2.getResolveMethods();
|
|
195
210
|
const resolveMethodsEntries = Object.entries(resolveMethods).flatMap(
|
|
196
211
|
([typeName, fieldResolvers]) => {
|
|
197
212
|
return Object.entries(fieldResolvers).map(([fieldName]) => {
|
|
@@ -381,12 +396,12 @@ var createApiTemplate = ({
|
|
|
381
396
|
|
|
382
397
|
// src/createAppSyncResolverHandler.ts
|
|
383
398
|
var createAppSyncResolverHandler = ({
|
|
384
|
-
schemaComposer
|
|
399
|
+
schemaComposer: schemaComposer2
|
|
385
400
|
}) => {
|
|
386
401
|
return async (event, context) => {
|
|
387
402
|
const { info, arguments: args, source } = event;
|
|
388
403
|
const { parentTypeName, fieldName } = info;
|
|
389
|
-
const resolver =
|
|
404
|
+
const resolver = schemaComposer2.getResolveMethods()[parentTypeName][fieldName];
|
|
390
405
|
return resolver(
|
|
391
406
|
source,
|
|
392
407
|
args,
|
|
@@ -395,8 +410,164 @@ var createAppSyncResolverHandler = ({
|
|
|
395
410
|
);
|
|
396
411
|
};
|
|
397
412
|
};
|
|
413
|
+
|
|
414
|
+
// src/composeWithRelay/composeWithRelay.ts
|
|
415
|
+
var import_graphql_compose4 = require("graphql-compose");
|
|
416
|
+
|
|
417
|
+
// src/composeWithRelay/nodeFieldConfig.ts
|
|
418
|
+
var import_graphql_compose2 = require("graphql-compose");
|
|
419
|
+
|
|
420
|
+
// src/composeWithRelay/globalId.ts
|
|
421
|
+
var base64 = (i) => {
|
|
422
|
+
return Buffer.from(i, "ascii").toString("base64");
|
|
423
|
+
};
|
|
424
|
+
var unbase64 = (i) => {
|
|
425
|
+
return Buffer.from(i, "base64").toString("ascii");
|
|
426
|
+
};
|
|
427
|
+
var toGlobalId = (type, id) => {
|
|
428
|
+
return base64([type, id].join(":"));
|
|
429
|
+
};
|
|
430
|
+
var fromGlobalId = (globalId) => {
|
|
431
|
+
const unbasedGlobalId = unbase64(globalId);
|
|
432
|
+
const [type, id] = unbasedGlobalId.split(":");
|
|
433
|
+
return { type, id };
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/composeWithRelay/nodeFieldConfig.ts
|
|
437
|
+
var getNodeFieldConfig = (typeMapForRelayNode, nodeInterface) => {
|
|
438
|
+
return {
|
|
439
|
+
description: "Fetches an object that has globally unique ID among all types",
|
|
440
|
+
type: nodeInterface,
|
|
441
|
+
args: {
|
|
442
|
+
id: {
|
|
443
|
+
type: "ID!",
|
|
444
|
+
description: "The globally unique ID among all types"
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
resolve: (source, args, context, info) => {
|
|
448
|
+
if (!args.id || !(typeof args.id === "string")) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const { type } = fromGlobalId(args.id);
|
|
452
|
+
if (!typeMapForRelayNode[type]) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
const { tc, resolver: findById } = typeMapForRelayNode[type];
|
|
456
|
+
if (findById && findById.resolve && tc) {
|
|
457
|
+
const graphqlType = tc.getType();
|
|
458
|
+
let projection;
|
|
459
|
+
if (info) {
|
|
460
|
+
projection = (0, import_graphql_compose2.getProjectionFromAST)({
|
|
461
|
+
...info,
|
|
462
|
+
returnType: graphqlType
|
|
463
|
+
});
|
|
464
|
+
} else {
|
|
465
|
+
projection = {};
|
|
466
|
+
}
|
|
467
|
+
const idArgName = Object.keys(findById.args)[0];
|
|
468
|
+
return findById.resolve({
|
|
469
|
+
source,
|
|
470
|
+
args: { [idArgName]: args.id },
|
|
471
|
+
// eg. mongoose has _id fieldname, so should map
|
|
472
|
+
context,
|
|
473
|
+
info,
|
|
474
|
+
projection
|
|
475
|
+
}).then((res) => {
|
|
476
|
+
if (!res)
|
|
477
|
+
return res;
|
|
478
|
+
res.__nodeType = graphqlType;
|
|
479
|
+
return res;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/composeWithRelay/nodeInterface.ts
|
|
488
|
+
var import_graphql_compose3 = require("graphql-compose");
|
|
489
|
+
var NodeTC = import_graphql_compose3.InterfaceTypeComposer.createTemp({
|
|
490
|
+
name: "Node",
|
|
491
|
+
description: "An object, that can be fetched by the globally unique ID among all types.",
|
|
492
|
+
fields: {
|
|
493
|
+
id: {
|
|
494
|
+
type: "ID!",
|
|
495
|
+
description: "The globally unique ID among all types."
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
resolveType: (payload) => {
|
|
499
|
+
return payload.__nodeType.name ? payload.__nodeType.name : null;
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
var NodeInterface = NodeTC.getType();
|
|
503
|
+
var getNodeInterface = (sc) => {
|
|
504
|
+
if (sc.hasInstance("Node", import_graphql_compose3.InterfaceTypeComposer)) {
|
|
505
|
+
return sc.get("Node");
|
|
506
|
+
}
|
|
507
|
+
sc.set("Node", NodeTC);
|
|
508
|
+
return NodeTC;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// src/composeWithRelay/composeWithRelay.ts
|
|
512
|
+
var TypeMapForRelayNode = {};
|
|
513
|
+
var composeWithRelay = (tc) => {
|
|
514
|
+
if (!(tc instanceof import_graphql_compose4.ObjectTypeComposer)) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
"You should provide ObjectTypeComposer instance to composeWithRelay method"
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
const nodeInterface = getNodeInterface(tc.schemaComposer);
|
|
520
|
+
const nodeFieldConfig = getNodeFieldConfig(
|
|
521
|
+
TypeMapForRelayNode,
|
|
522
|
+
nodeInterface
|
|
523
|
+
);
|
|
524
|
+
if (tc.getTypeName() === "Query" || tc.getTypeName() === "RootQuery") {
|
|
525
|
+
tc.setField("node", nodeFieldConfig);
|
|
526
|
+
return tc;
|
|
527
|
+
}
|
|
528
|
+
if (tc.getTypeName() === "Mutation" || tc.getTypeName() === "RootMutation") {
|
|
529
|
+
return tc;
|
|
530
|
+
}
|
|
531
|
+
if (!tc.hasRecordIdFn()) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
`ObjectTypeComposer(${tc.getTypeName()}) should have recordIdFn. This function returns ID from provided object.`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
const findById = tc.getResolver("findById");
|
|
537
|
+
if (!findById) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`ObjectTypeComposer(${tc.getTypeName()}) provided to composeWithRelay should have findById resolver.`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
TypeMapForRelayNode[tc.getTypeName()] = {
|
|
543
|
+
resolver: findById,
|
|
544
|
+
tc
|
|
545
|
+
};
|
|
546
|
+
tc.addFields({
|
|
547
|
+
id: {
|
|
548
|
+
type: "ID!",
|
|
549
|
+
description: "The globally unique ID among all types",
|
|
550
|
+
resolve: (source) => {
|
|
551
|
+
return toGlobalId(tc.getTypeName(), tc.getRecordId(source));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
tc.addInterface(nodeInterface);
|
|
556
|
+
return tc;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/composeWithRelay/index.ts
|
|
560
|
+
var import_graphql_compose5 = require("graphql-compose");
|
|
561
|
+
composeWithRelay(import_graphql_compose5.schemaComposer.Query);
|
|
562
|
+
|
|
563
|
+
// src/index.ts
|
|
564
|
+
var import_graphql_compose_connection = __toESM(require("graphql-compose-connection"));
|
|
398
565
|
// Annotate the CommonJS export names for ESM import in node:
|
|
399
566
|
0 && (module.exports = {
|
|
567
|
+
composeWithConnection,
|
|
568
|
+
composeWithRelay,
|
|
400
569
|
createApiTemplate,
|
|
401
|
-
createAppSyncResolverHandler
|
|
570
|
+
createAppSyncResolverHandler,
|
|
571
|
+
fromGlobalId,
|
|
572
|
+
toGlobalId
|
|
402
573
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/appsync-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A library for building GraphQL APIs for AWS AppSync.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "ttoss",
|
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@ttoss/cloudformation": "^0.5.1",
|
|
37
37
|
"express": "^4.18.2",
|
|
38
|
+
"graphql-compose-connection": "^8.2.1",
|
|
38
39
|
"graphql-helix": "^1.13.0",
|
|
39
|
-
"minimist": "^1.2.
|
|
40
|
+
"minimist": "^1.2.8"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
43
|
"graphql": "^16.6.0",
|
|
@@ -68,5 +69,5 @@
|
|
|
68
69
|
]
|
|
69
70
|
}
|
|
70
71
|
},
|
|
71
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "84ebce1283e142519776d7045c484269d3dbabe4"
|
|
72
73
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ObjectTypeComposer } from 'graphql-compose';
|
|
2
|
+
import { getNodeFieldConfig } from './nodeFieldConfig';
|
|
3
|
+
import { getNodeInterface } from './nodeInterface';
|
|
4
|
+
import { toGlobalId } from './globalId';
|
|
5
|
+
|
|
6
|
+
// all wrapped typeComposers with Relay, stored in this variable
|
|
7
|
+
// for futher type resolving via NodeInterface.resolveType method
|
|
8
|
+
export const TypeMapForRelayNode: any = {};
|
|
9
|
+
|
|
10
|
+
export const composeWithRelay = <TContext>(
|
|
11
|
+
tc: ObjectTypeComposer<any, TContext>
|
|
12
|
+
): ObjectTypeComposer<any, TContext> => {
|
|
13
|
+
if (!(tc instanceof ObjectTypeComposer)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'You should provide ObjectTypeComposer instance to composeWithRelay method'
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const nodeInterface = getNodeInterface(tc.schemaComposer);
|
|
20
|
+
const nodeFieldConfig = getNodeFieldConfig(
|
|
21
|
+
TypeMapForRelayNode,
|
|
22
|
+
nodeInterface
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (tc.getTypeName() === 'Query' || tc.getTypeName() === 'RootQuery') {
|
|
26
|
+
tc.setField('node', nodeFieldConfig);
|
|
27
|
+
return tc;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (tc.getTypeName() === 'Mutation' || tc.getTypeName() === 'RootMutation') {
|
|
31
|
+
// just skip
|
|
32
|
+
return tc;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!tc.hasRecordIdFn()) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`ObjectTypeComposer(${tc.getTypeName()}) should have recordIdFn. ` +
|
|
38
|
+
'This function returns ID from provided object.'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const findById = tc.getResolver('findById');
|
|
43
|
+
if (!findById) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`ObjectTypeComposer(${tc.getTypeName()}) provided to composeWithRelay ` +
|
|
46
|
+
'should have findById resolver.'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
TypeMapForRelayNode[tc.getTypeName()] = {
|
|
50
|
+
resolver: findById,
|
|
51
|
+
tc,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
tc.addFields({
|
|
55
|
+
id: {
|
|
56
|
+
type: 'ID!',
|
|
57
|
+
description: 'The globally unique ID among all types',
|
|
58
|
+
resolve: (source) => {
|
|
59
|
+
return toGlobalId(tc.getTypeName(), tc.getRecordId(source));
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
tc.addInterface(nodeInterface);
|
|
65
|
+
|
|
66
|
+
return tc;
|
|
67
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type Base64String = string;
|
|
2
|
+
|
|
3
|
+
export type ResolvedGlobalId = {
|
|
4
|
+
type: string;
|
|
5
|
+
id: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const base64 = (i: string): Base64String => {
|
|
9
|
+
return Buffer.from(i, 'ascii').toString('base64');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const unbase64 = (i: Base64String): string => {
|
|
13
|
+
return Buffer.from(i, 'base64').toString('ascii');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Takes a type name and an ID specific to that type name, and returns a
|
|
18
|
+
* "global ID" that is unique among all types.
|
|
19
|
+
*/
|
|
20
|
+
export const toGlobalId = (type: string, id: string | number): string => {
|
|
21
|
+
return base64([type, id].join(':'));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Takes the "global ID" created by toGlobalID, and returns the type name and ID
|
|
26
|
+
* used to create it.
|
|
27
|
+
*/
|
|
28
|
+
export const fromGlobalId = (globalId: string): ResolvedGlobalId => {
|
|
29
|
+
const unbasedGlobalId = unbase64(globalId);
|
|
30
|
+
const [type, id] = unbasedGlobalId.split(':');
|
|
31
|
+
return { type, id };
|
|
32
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type InterfaceTypeComposer,
|
|
3
|
+
type ObjectTypeComposerFieldConfigDefinition,
|
|
4
|
+
getProjectionFromAST,
|
|
5
|
+
} from 'graphql-compose';
|
|
6
|
+
import { fromGlobalId } from './globalId';
|
|
7
|
+
import type { GraphQLResolveInfo } from 'graphql-compose/lib/graphql';
|
|
8
|
+
import type { ObjectTypeComposer, Resolver } from 'graphql-compose';
|
|
9
|
+
|
|
10
|
+
export type TypeMapForRelayNode<TSource, TContext> = {
|
|
11
|
+
[typeName: string]: {
|
|
12
|
+
resolver: Resolver<TSource, TContext>;
|
|
13
|
+
tc: ObjectTypeComposer<TSource, TContext>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// this fieldConfig must be set to RootQuery.node field
|
|
18
|
+
export const getNodeFieldConfig = (
|
|
19
|
+
typeMapForRelayNode: TypeMapForRelayNode<any, any>,
|
|
20
|
+
nodeInterface: InterfaceTypeComposer<any, any>
|
|
21
|
+
): ObjectTypeComposerFieldConfigDefinition<any, any> => {
|
|
22
|
+
return {
|
|
23
|
+
description:
|
|
24
|
+
'Fetches an object that has globally unique ID among all types',
|
|
25
|
+
type: nodeInterface,
|
|
26
|
+
args: {
|
|
27
|
+
id: {
|
|
28
|
+
type: 'ID!',
|
|
29
|
+
description: 'The globally unique ID among all types',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
resolve: (
|
|
33
|
+
source: any,
|
|
34
|
+
args: { [argName: string]: any },
|
|
35
|
+
context: any,
|
|
36
|
+
info: GraphQLResolveInfo
|
|
37
|
+
) => {
|
|
38
|
+
if (!args.id || !(typeof args.id === 'string')) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const { type } = fromGlobalId(args.id);
|
|
42
|
+
|
|
43
|
+
if (!typeMapForRelayNode[type]) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const { tc, resolver: findById } = typeMapForRelayNode[type];
|
|
47
|
+
if (findById && findById.resolve && tc) {
|
|
48
|
+
const graphqlType = tc.getType();
|
|
49
|
+
|
|
50
|
+
// set `returnType` for proper work of `getProjectionFromAST`
|
|
51
|
+
// it will correctly add required fields for `relation` to `projection`
|
|
52
|
+
let projection;
|
|
53
|
+
if (info) {
|
|
54
|
+
projection = getProjectionFromAST({
|
|
55
|
+
...info,
|
|
56
|
+
returnType: graphqlType,
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
projection = {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// suppose that first argument is argument with id field
|
|
63
|
+
const idArgName = Object.keys(findById.args)[0];
|
|
64
|
+
return findById
|
|
65
|
+
.resolve({
|
|
66
|
+
source,
|
|
67
|
+
args: { [idArgName]: args.id }, // eg. mongoose has _id fieldname, so should map
|
|
68
|
+
context,
|
|
69
|
+
info,
|
|
70
|
+
projection,
|
|
71
|
+
})
|
|
72
|
+
.then((res: any) => {
|
|
73
|
+
if (!res) return res;
|
|
74
|
+
res.__nodeType = graphqlType;
|
|
75
|
+
return res;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { InterfaceTypeComposer, type SchemaComposer } from 'graphql-compose';
|
|
2
|
+
|
|
3
|
+
const NodeTC = InterfaceTypeComposer.createTemp({
|
|
4
|
+
name: 'Node',
|
|
5
|
+
description:
|
|
6
|
+
'An object, that can be fetched by the globally unique ID among all types.',
|
|
7
|
+
fields: {
|
|
8
|
+
id: {
|
|
9
|
+
type: 'ID!',
|
|
10
|
+
description: 'The globally unique ID among all types.',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
resolveType: (payload: any) => {
|
|
14
|
+
// `payload.__nodeType` was added to payload via nodeFieldConfig.resolve
|
|
15
|
+
return payload.__nodeType.name ? payload.__nodeType.name : null;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const NodeInterface = NodeTC.getType();
|
|
20
|
+
|
|
21
|
+
export const getNodeInterface = <TContext>(
|
|
22
|
+
sc: SchemaComposer<TContext>
|
|
23
|
+
): InterfaceTypeComposer<any, TContext> => {
|
|
24
|
+
if (sc.hasInstance('Node', InterfaceTypeComposer)) {
|
|
25
|
+
return sc.get('Node') as any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
sc.set('Node', NodeTC);
|
|
29
|
+
|
|
30
|
+
return NodeTC;
|
|
31
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -4,3 +4,5 @@ export {
|
|
|
4
4
|
createAppSyncResolverHandler,
|
|
5
5
|
} from './createAppSyncResolverHandler';
|
|
6
6
|
export type { AppSyncIdentityCognito } from 'aws-lambda';
|
|
7
|
+
export { composeWithRelay, toGlobalId, fromGlobalId } from './composeWithRelay';
|
|
8
|
+
export { default as composeWithConnection } from 'graphql-compose-connection';
|