@ttoss/appsync-api 0.7.5 → 0.8.1
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-RWRQSJ4M.js} +8 -3
- package/dist/esm/cli.js +1 -1
- package/dist/esm/index.js +162 -4
- package/dist/index.d.ts +92 -3
- package/dist/index.js +185 -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/createApiTemplate.ts +12 -2
- 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.1",
|
|
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",
|
|
@@ -163,7 +164,11 @@ var createApiTemplate = ({
|
|
|
163
164
|
lambdaFunction,
|
|
164
165
|
userPoolConfig
|
|
165
166
|
}) => {
|
|
166
|
-
const sdl = schemaComposer.toSDL(
|
|
167
|
+
const sdl = schemaComposer.toSDL({
|
|
168
|
+
commentDescriptions: false,
|
|
169
|
+
omitDescriptions: true,
|
|
170
|
+
omitScalars: true
|
|
171
|
+
});
|
|
167
172
|
graphql.validateSchema(schemaComposer.buildSchema());
|
|
168
173
|
const resolveMethods = schemaComposer.getResolveMethods();
|
|
169
174
|
const resolveMethodsEntries = Object.entries(resolveMethods).flatMap(
|
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-RWRQSJ4M.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,79 @@
|
|
|
1
|
-
import { SchemaComposer } from 'graphql-compose';
|
|
2
|
-
import { CloudFormationTemplate } from '@ttoss/cloudformation';
|
|
1
|
+
import { SchemaComposer, ObjectTypeComposer } from 'graphql-compose';
|
|
3
2
|
import { AppSyncResolverHandler as AppSyncResolverHandler$1 } from 'aws-lambda';
|
|
4
3
|
export { AppSyncIdentityCognito } from 'aws-lambda';
|
|
4
|
+
export { default as composeWithConnection } from 'graphql-compose-connection';
|
|
5
|
+
|
|
6
|
+
interface Parameter {
|
|
7
|
+
AllowedValues?: string[];
|
|
8
|
+
Default?: string | number;
|
|
9
|
+
Description?: string;
|
|
10
|
+
Type: string;
|
|
11
|
+
NoEcho?: boolean;
|
|
12
|
+
}
|
|
13
|
+
type Parameters = {
|
|
14
|
+
[key: string]: Parameter;
|
|
15
|
+
};
|
|
16
|
+
interface Resource {
|
|
17
|
+
Type: string;
|
|
18
|
+
DeletionPolicy?: 'Delete' | 'Retain';
|
|
19
|
+
Description?: string;
|
|
20
|
+
DependsOn?: string[] | string;
|
|
21
|
+
Condition?: string;
|
|
22
|
+
Properties: any;
|
|
23
|
+
}
|
|
24
|
+
interface IAMRoleResource extends Resource {
|
|
25
|
+
Type: 'AWS::IAM::Role';
|
|
26
|
+
Properties: {
|
|
27
|
+
AssumeRolePolicyDocument: {
|
|
28
|
+
Version: '2012-10-17';
|
|
29
|
+
Statement: {
|
|
30
|
+
Effect: 'Allow' | 'Deny';
|
|
31
|
+
Action: string;
|
|
32
|
+
Principal: any;
|
|
33
|
+
}[];
|
|
34
|
+
};
|
|
35
|
+
ManagedPolicyArns?: string[];
|
|
36
|
+
Path?: string;
|
|
37
|
+
Policies?: {
|
|
38
|
+
PolicyName: string;
|
|
39
|
+
PolicyDocument: {
|
|
40
|
+
Version: '2012-10-17';
|
|
41
|
+
Statement: {
|
|
42
|
+
Effect: 'Allow' | 'Deny';
|
|
43
|
+
Action: string | string[];
|
|
44
|
+
Resource: string | string[] | {
|
|
45
|
+
[key: string]: string;
|
|
46
|
+
} | {
|
|
47
|
+
[key: string]: string;
|
|
48
|
+
}[];
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
51
|
+
}[];
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
type Resources = {
|
|
55
|
+
[key: string]: IAMRoleResource | Resource;
|
|
56
|
+
};
|
|
57
|
+
type Output = {
|
|
58
|
+
Description?: string;
|
|
59
|
+
Value: string | any;
|
|
60
|
+
Export?: {
|
|
61
|
+
Name: string | any;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
type Outputs = {
|
|
65
|
+
[key: string]: Output;
|
|
66
|
+
};
|
|
67
|
+
type CloudFormationTemplate = {
|
|
68
|
+
AWSTemplateFormatVersion: '2010-09-09';
|
|
69
|
+
Description?: string;
|
|
70
|
+
Transform?: 'AWS::Serverless-2016-10-31';
|
|
71
|
+
Mappings?: any;
|
|
72
|
+
Conditions?: any;
|
|
73
|
+
Parameters?: Parameters;
|
|
74
|
+
Resources: Resources;
|
|
75
|
+
Outputs?: Outputs;
|
|
76
|
+
};
|
|
5
77
|
|
|
6
78
|
type StringOrImport = string | {
|
|
7
79
|
'Fn::ImportValue': string;
|
|
@@ -33,4 +105,21 @@ declare const createAppSyncResolverHandler: ({ schemaComposer, }: {
|
|
|
33
105
|
schemaComposer: SchemaComposer<any>;
|
|
34
106
|
}) => AppSyncResolverHandler<any, any, any>;
|
|
35
107
|
|
|
36
|
-
|
|
108
|
+
declare const composeWithRelay: <TContext>(tc: ObjectTypeComposer<any, TContext>) => ObjectTypeComposer<any, TContext>;
|
|
109
|
+
|
|
110
|
+
type ResolvedGlobalId = {
|
|
111
|
+
type: string;
|
|
112
|
+
id: string;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Takes a type name and an ID specific to that type name, and returns a
|
|
116
|
+
* "global ID" that is unique among all types.
|
|
117
|
+
*/
|
|
118
|
+
declare const toGlobalId: (type: string, id: string | number) => string;
|
|
119
|
+
/**
|
|
120
|
+
* Takes the "global ID" created by toGlobalID, and returns the type name and ID
|
|
121
|
+
* used to create it.
|
|
122
|
+
*/
|
|
123
|
+
declare const fromGlobalId: (globalId: string) => ResolvedGlobalId;
|
|
124
|
+
|
|
125
|
+
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.1",
|
|
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,18 @@ 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
|
-
|
|
194
|
-
|
|
207
|
+
const sdl = schemaComposer2.toSDL({
|
|
208
|
+
commentDescriptions: false,
|
|
209
|
+
omitDescriptions: true,
|
|
210
|
+
omitScalars: true
|
|
211
|
+
});
|
|
212
|
+
import_graphql_compose.graphql.validateSchema(schemaComposer2.buildSchema());
|
|
213
|
+
const resolveMethods = schemaComposer2.getResolveMethods();
|
|
195
214
|
const resolveMethodsEntries = Object.entries(resolveMethods).flatMap(
|
|
196
215
|
([typeName, fieldResolvers]) => {
|
|
197
216
|
return Object.entries(fieldResolvers).map(([fieldName]) => {
|
|
@@ -381,12 +400,12 @@ var createApiTemplate = ({
|
|
|
381
400
|
|
|
382
401
|
// src/createAppSyncResolverHandler.ts
|
|
383
402
|
var createAppSyncResolverHandler = ({
|
|
384
|
-
schemaComposer
|
|
403
|
+
schemaComposer: schemaComposer2
|
|
385
404
|
}) => {
|
|
386
405
|
return async (event, context) => {
|
|
387
406
|
const { info, arguments: args, source } = event;
|
|
388
407
|
const { parentTypeName, fieldName } = info;
|
|
389
|
-
const resolver =
|
|
408
|
+
const resolver = schemaComposer2.getResolveMethods()[parentTypeName][fieldName];
|
|
390
409
|
return resolver(
|
|
391
410
|
source,
|
|
392
411
|
args,
|
|
@@ -395,8 +414,164 @@ var createAppSyncResolverHandler = ({
|
|
|
395
414
|
);
|
|
396
415
|
};
|
|
397
416
|
};
|
|
417
|
+
|
|
418
|
+
// src/composeWithRelay/composeWithRelay.ts
|
|
419
|
+
var import_graphql_compose4 = require("graphql-compose");
|
|
420
|
+
|
|
421
|
+
// src/composeWithRelay/nodeFieldConfig.ts
|
|
422
|
+
var import_graphql_compose2 = require("graphql-compose");
|
|
423
|
+
|
|
424
|
+
// src/composeWithRelay/globalId.ts
|
|
425
|
+
var base64 = (i) => {
|
|
426
|
+
return Buffer.from(i, "ascii").toString("base64");
|
|
427
|
+
};
|
|
428
|
+
var unbase64 = (i) => {
|
|
429
|
+
return Buffer.from(i, "base64").toString("ascii");
|
|
430
|
+
};
|
|
431
|
+
var toGlobalId = (type, id) => {
|
|
432
|
+
return base64([type, id].join(":"));
|
|
433
|
+
};
|
|
434
|
+
var fromGlobalId = (globalId) => {
|
|
435
|
+
const unbasedGlobalId = unbase64(globalId);
|
|
436
|
+
const [type, id] = unbasedGlobalId.split(":");
|
|
437
|
+
return { type, id };
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/composeWithRelay/nodeFieldConfig.ts
|
|
441
|
+
var getNodeFieldConfig = (typeMapForRelayNode, nodeInterface) => {
|
|
442
|
+
return {
|
|
443
|
+
description: "Fetches an object that has globally unique ID among all types",
|
|
444
|
+
type: nodeInterface,
|
|
445
|
+
args: {
|
|
446
|
+
id: {
|
|
447
|
+
type: "ID!",
|
|
448
|
+
description: "The globally unique ID among all types"
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
resolve: (source, args, context, info) => {
|
|
452
|
+
if (!args.id || !(typeof args.id === "string")) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
const { type } = fromGlobalId(args.id);
|
|
456
|
+
if (!typeMapForRelayNode[type]) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
const { tc, resolver: findById } = typeMapForRelayNode[type];
|
|
460
|
+
if (findById && findById.resolve && tc) {
|
|
461
|
+
const graphqlType = tc.getType();
|
|
462
|
+
let projection;
|
|
463
|
+
if (info) {
|
|
464
|
+
projection = (0, import_graphql_compose2.getProjectionFromAST)({
|
|
465
|
+
...info,
|
|
466
|
+
returnType: graphqlType
|
|
467
|
+
});
|
|
468
|
+
} else {
|
|
469
|
+
projection = {};
|
|
470
|
+
}
|
|
471
|
+
const idArgName = Object.keys(findById.args)[0];
|
|
472
|
+
return findById.resolve({
|
|
473
|
+
source,
|
|
474
|
+
args: { [idArgName]: args.id },
|
|
475
|
+
// eg. mongoose has _id fieldname, so should map
|
|
476
|
+
context,
|
|
477
|
+
info,
|
|
478
|
+
projection
|
|
479
|
+
}).then((res) => {
|
|
480
|
+
if (!res)
|
|
481
|
+
return res;
|
|
482
|
+
res.__nodeType = graphqlType;
|
|
483
|
+
return res;
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/composeWithRelay/nodeInterface.ts
|
|
492
|
+
var import_graphql_compose3 = require("graphql-compose");
|
|
493
|
+
var NodeTC = import_graphql_compose3.InterfaceTypeComposer.createTemp({
|
|
494
|
+
name: "Node",
|
|
495
|
+
description: "An object, that can be fetched by the globally unique ID among all types.",
|
|
496
|
+
fields: {
|
|
497
|
+
id: {
|
|
498
|
+
type: "ID!",
|
|
499
|
+
description: "The globally unique ID among all types."
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
resolveType: (payload) => {
|
|
503
|
+
return payload.__nodeType.name ? payload.__nodeType.name : null;
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
var NodeInterface = NodeTC.getType();
|
|
507
|
+
var getNodeInterface = (sc) => {
|
|
508
|
+
if (sc.hasInstance("Node", import_graphql_compose3.InterfaceTypeComposer)) {
|
|
509
|
+
return sc.get("Node");
|
|
510
|
+
}
|
|
511
|
+
sc.set("Node", NodeTC);
|
|
512
|
+
return NodeTC;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/composeWithRelay/composeWithRelay.ts
|
|
516
|
+
var TypeMapForRelayNode = {};
|
|
517
|
+
var composeWithRelay = (tc) => {
|
|
518
|
+
if (!(tc instanceof import_graphql_compose4.ObjectTypeComposer)) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
"You should provide ObjectTypeComposer instance to composeWithRelay method"
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const nodeInterface = getNodeInterface(tc.schemaComposer);
|
|
524
|
+
const nodeFieldConfig = getNodeFieldConfig(
|
|
525
|
+
TypeMapForRelayNode,
|
|
526
|
+
nodeInterface
|
|
527
|
+
);
|
|
528
|
+
if (tc.getTypeName() === "Query" || tc.getTypeName() === "RootQuery") {
|
|
529
|
+
tc.setField("node", nodeFieldConfig);
|
|
530
|
+
return tc;
|
|
531
|
+
}
|
|
532
|
+
if (tc.getTypeName() === "Mutation" || tc.getTypeName() === "RootMutation") {
|
|
533
|
+
return tc;
|
|
534
|
+
}
|
|
535
|
+
if (!tc.hasRecordIdFn()) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
`ObjectTypeComposer(${tc.getTypeName()}) should have recordIdFn. This function returns ID from provided object.`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
const findById = tc.getResolver("findById");
|
|
541
|
+
if (!findById) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`ObjectTypeComposer(${tc.getTypeName()}) provided to composeWithRelay should have findById resolver.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
TypeMapForRelayNode[tc.getTypeName()] = {
|
|
547
|
+
resolver: findById,
|
|
548
|
+
tc
|
|
549
|
+
};
|
|
550
|
+
tc.addFields({
|
|
551
|
+
id: {
|
|
552
|
+
type: "ID!",
|
|
553
|
+
description: "The globally unique ID among all types",
|
|
554
|
+
resolve: (source) => {
|
|
555
|
+
return toGlobalId(tc.getTypeName(), tc.getRecordId(source));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
tc.addInterface(nodeInterface);
|
|
560
|
+
return tc;
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/composeWithRelay/index.ts
|
|
564
|
+
var import_graphql_compose5 = require("graphql-compose");
|
|
565
|
+
composeWithRelay(import_graphql_compose5.schemaComposer.Query);
|
|
566
|
+
|
|
567
|
+
// src/index.ts
|
|
568
|
+
var import_graphql_compose_connection = __toESM(require("graphql-compose-connection"));
|
|
398
569
|
// Annotate the CommonJS export names for ESM import in node:
|
|
399
570
|
0 && (module.exports = {
|
|
571
|
+
composeWithConnection,
|
|
572
|
+
composeWithRelay,
|
|
400
573
|
createApiTemplate,
|
|
401
|
-
createAppSyncResolverHandler
|
|
574
|
+
createAppSyncResolverHandler,
|
|
575
|
+
fromGlobalId,
|
|
576
|
+
toGlobalId
|
|
402
577
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/appsync-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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": "128418716cbc6dd7d4a41a6925b455b7e4784f9f"
|
|
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/createApiTemplate.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type SchemaComposer, graphql } from 'graphql-compose';
|
|
2
2
|
import { getPackageLambdaLayerStackName } from 'carlin/src/deploy/lambdaLayer/getPackageLambdaLayerStackName';
|
|
3
3
|
import packageJson from '../package.json';
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Absolute path to avoid:
|
|
6
|
+
* The inferred type of 'template' cannot be named without a reference to
|
|
7
|
+
* '@ttoss/appsync-api/node_modules/@ttoss/cloudformation'. This is likely not
|
|
8
|
+
* portable. A type annotation is necessary.ts(2742)
|
|
9
|
+
*/
|
|
10
|
+
import type { CloudFormationTemplate } from '../../cloudformation/src';
|
|
5
11
|
|
|
6
12
|
export const AppSyncGraphQLApiLogicalId = 'AppSyncGraphQLApi';
|
|
7
13
|
|
|
@@ -53,7 +59,11 @@ export const createApiTemplate = ({
|
|
|
53
59
|
* It should be on top of the file, otherwise it will have empty Mutation
|
|
54
60
|
* or Subscription if there are no resolvers for them.
|
|
55
61
|
*/
|
|
56
|
-
const sdl = schemaComposer.toSDL(
|
|
62
|
+
const sdl = schemaComposer.toSDL({
|
|
63
|
+
commentDescriptions: false,
|
|
64
|
+
omitDescriptions: true,
|
|
65
|
+
omitScalars: true,
|
|
66
|
+
});
|
|
57
67
|
|
|
58
68
|
graphql.validateSchema(schemaComposer.buildSchema());
|
|
59
69
|
|
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';
|