@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 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.7.5",
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.7"
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
@@ -1,7 +1,7 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
2
  import {
3
3
  AppSyncGraphQLSchemaLogicalId
4
- } from "./chunk-CXLYPHQJ.js";
4
+ } from "./chunk-RWRQSJ4M.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import * as fs from "fs";
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-CXLYPHQJ.js";
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 = schemaComposer.getResolveMethods()[parentTypeName][fieldName];
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
- export { AppSyncResolverHandler, createApiTemplate, createAppSyncResolverHandler };
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.7.5",
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.7"
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 = schemaComposer.toSDL();
193
- import_graphql_compose.graphql.validateSchema(schemaComposer.buildSchema());
194
- const resolveMethods = schemaComposer.getResolveMethods();
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 = schemaComposer.getResolveMethods()[parentTypeName][fieldName];
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.7.5",
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.7"
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": "d4df96176c1d3fce4ebf7842de5847a618a33d9e"
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,7 @@
1
+ import { composeWithRelay } from './composeWithRelay';
2
+ import { schemaComposer } from 'graphql-compose';
3
+
4
+ composeWithRelay(schemaComposer.Query);
5
+
6
+ export { composeWithRelay };
7
+ export { fromGlobalId, toGlobalId } from './globalId';
@@ -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
+ };
@@ -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
- import type { CloudFormationTemplate } from '@ttoss/cloudformation';
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';