@aws-amplify/api 5.3.4-api-v6.29 → 5.3.4-api-v6-models.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/api",
3
- "version": "5.3.4-api-v6.29+98c172c32",
3
+ "version": "5.3.4-api-v6-models.34+62a13d7bc",
4
4
  "description": "Api category of aws-amplify",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib-esm/index.js",
@@ -28,14 +28,14 @@
28
28
  "build-with-test": "npm test && npm run build",
29
29
  "build:cjs": "rimraf lib && tsc -p ./tsconfig.build.json -m commonjs --outDir lib && webpack && webpack --config ./webpack.config.dev.js",
30
30
  "build:esm": "rimraf lib-esm && tsc -p ./tsconfig.build.json -m esnext --outDir lib-esm",
31
- "build:cjs:watch": "rimraf lib && tsc -m commonjs --outDir lib --watch",
32
- "build:esm:watch": "rimraf lib-esm && tsc -m esnext --outDir lib-esm --watch",
31
+ "build:cjs:watch": "rimraf lib && tsc -p ./tsconfig.build.json -m commonjs --outDir lib --watch",
32
+ "build:esm:watch": "rimraf lib-esm && tsc -p ./tsconfig.build.json -m esnext --outDir lib-esm --watch",
33
33
  "build": "npm run clean && npm run build:esm && npm run build:cjs",
34
34
  "clean": "npm run clean:size && rimraf lib-esm lib dist",
35
35
  "clean:size": "rimraf dual-publish-tmp tmp*",
36
36
  "format": "echo \"Not implemented\"",
37
37
  "lint": "tslint 'src/**/*.ts' && npm run ts-coverage",
38
- "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 88"
38
+ "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 83"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
@@ -49,7 +49,7 @@
49
49
  "homepage": "https://aws-amplify.github.io/",
50
50
  "devDependencies": {
51
51
  "@types/zen-observable": "^0.8.0",
52
- "typescript": "^5.1.6"
52
+ "typescript": "5.1.6"
53
53
  },
54
54
  "files": [
55
55
  "lib",
@@ -59,9 +59,9 @@
59
59
  "internals"
60
60
  ],
61
61
  "dependencies": {
62
- "@aws-amplify/api-graphql": "3.4.4-api-v6.29+98c172c32",
63
- "@aws-amplify/api-rest": "3.3.3-api-v6.29+98c172c32",
64
- "@aws-amplify/types-package-alpha": "0.0.1-api-v6.8114+98c172c32",
62
+ "@aws-amplify/api-graphql": "3.4.4-api-v6-models.34+62a13d7bc",
63
+ "@aws-amplify/api-rest": "3.3.3-api-v6-models.34+62a13d7bc",
64
+ "@aws-amplify/types-package-alpha": "0.0.1-api-v6-models.8123+62a13d7bc",
65
65
  "tslib": "^2.6.1"
66
66
  },
67
67
  "size-limit": [
@@ -69,7 +69,7 @@
69
69
  "name": "API (top-level class)",
70
70
  "path": "./lib-esm/index.js",
71
71
  "import": "{ Amplify, API }",
72
- "limit": "92 kB"
72
+ "limit": "93.82 kB"
73
73
  }
74
74
  ],
75
75
  "jest": {
@@ -116,5 +116,5 @@
116
116
  "lib-esm"
117
117
  ]
118
118
  },
119
- "gitHead": "98c172c32bc793b99b31442ea2d84829baea6e3c"
119
+ "gitHead": "62a13d7bcd5a015eab416b66dff503b0f657f6bc"
120
120
  }
package/src/API.ts CHANGED
@@ -11,8 +11,20 @@ import { graphql as v6graphql } from '@aws-amplify/api-graphql/internals';
11
11
  import { Amplify, ConsoleLogger as Logger } from '@aws-amplify/core';
12
12
  import Observable from 'zen-observable-ts';
13
13
  import { InternalAPIClass } from './internals/InternalAPI';
14
+ import {
15
+ initializeModel,
16
+ generateGraphQLDocument,
17
+ buildGraphQLVariables,
18
+ graphQLOperationsInfo,
19
+ ModelOperation,
20
+ } from './APIClient';
14
21
  import type { ModelTypes } from '@aws-amplify/types-package-alpha';
15
22
 
23
+ /*
24
+ await post.comments()
25
+ => await graphql()
26
+ */
27
+
16
28
  const logger = new Logger('API');
17
29
  /**
18
30
  * @deprecated
@@ -62,6 +74,8 @@ export class APIClass extends InternalAPIClass {
62
74
  models: {},
63
75
  };
64
76
 
77
+ // TODO: refactor this to use separate methods for each CRUDL.
78
+ // Doesn't make sense to gen the methods dynamically given the different args and return values
65
79
  for (const model of Object.values(modelIntrospection.models)) {
66
80
  const { name } = model as any;
67
81
 
@@ -71,29 +85,87 @@ export class APIClass extends InternalAPIClass {
71
85
  ([key, { operationPrefix }]) => {
72
86
  const operation = key as ModelOperation;
73
87
 
74
- // e.g. clients.models.Todo.update
75
- client.models[name][operationPrefix] = async (arg?: any) => {
76
- const query = generateGraphQLDocument(model, operation);
77
- const variables = buildGraphQLVariables(model, operation, arg);
78
-
79
- const res = (await this.graphql({
80
- query,
81
- variables,
82
- })) as any;
83
-
84
- // flatten response
85
- if (res.data !== undefined) {
86
- const [key] = Object.keys(res.data);
87
-
88
- if (res.data[key].items) {
89
- return res.data[key].items;
88
+ if (operation === 'LIST') {
89
+ client.models[name][operationPrefix] = async (args?: any) => {
90
+ const query = generateGraphQLDocument(
91
+ modelIntrospection.models,
92
+ name,
93
+ 'LIST',
94
+ args
95
+ );
96
+ const variables = buildGraphQLVariables(model, 'LIST', args);
97
+
98
+ console.log('API list', query, variables);
99
+
100
+ const res = (await this.graphql({
101
+ query,
102
+ variables,
103
+ })) as any;
104
+
105
+ // flatten response
106
+ if (res.data !== undefined) {
107
+ const [key] = Object.keys(res.data);
108
+
109
+ if (res.data[key].items) {
110
+ const flattenedResult = res.data[key].items;
111
+
112
+ // don't init if custom selection set
113
+ if (args?.selectionSet) {
114
+ return flattenedResult;
115
+ } else {
116
+ const initialized = initializeModel(
117
+ client,
118
+ name,
119
+ flattenedResult,
120
+ modelIntrospection
121
+ );
122
+
123
+ return initialized;
124
+ }
125
+ }
126
+
127
+ return res.data[key];
90
128
  }
91
129
 
92
- return res.data[key];
93
- }
130
+ return res as any;
131
+ };
132
+ } else {
133
+ client.models[name][operationPrefix] = async (
134
+ arg?: any,
135
+ options?: any
136
+ ) => {
137
+ const query = generateGraphQLDocument(
138
+ modelIntrospection.models,
139
+ name,
140
+ operation
141
+ );
142
+ const variables = buildGraphQLVariables(model, operation, arg);
143
+
144
+ console.log(`API ${operationPrefix}`, query, variables);
145
+
146
+ const res = (await this.graphql({
147
+ query,
148
+ variables,
149
+ })) as any;
150
+
151
+ // flatten response
152
+ if (res.data !== undefined) {
153
+ const [key] = Object.keys(res.data);
154
+
155
+ // TODO: refactor to avoid destructuring here
156
+ const [initialized] = initializeModel(
157
+ client,
158
+ name,
159
+ [res.data[key]],
160
+ modelIntrospection
161
+ );
162
+
163
+ return initialized;
164
+ }
94
165
 
95
- return res;
96
- };
166
+ return res;
167
+ };
168
+ }
97
169
  }
98
170
  );
99
171
  }
@@ -102,167 +174,6 @@ export class APIClass extends InternalAPIClass {
102
174
  }
103
175
  }
104
176
 
105
- const graphQLOperationsInfo = {
106
- CREATE: { operationPrefix: 'create' as const, usePlural: false },
107
- READ: { operationPrefix: 'get' as const, usePlural: false },
108
- UPDATE: { operationPrefix: 'update' as const, usePlural: false },
109
- DELETE: { operationPrefix: 'delete' as const, usePlural: false },
110
- LIST: { operationPrefix: 'list' as const, usePlural: true },
111
- };
112
- type ModelOperation = keyof typeof graphQLOperationsInfo;
113
- type OperationPrefix =
114
- (typeof graphQLOperationsInfo)[ModelOperation]['operationPrefix'];
115
-
116
- const graphQLDocumentsCache = new Map<string, Map<ModelOperation, string>>();
117
-
118
- function generateGraphQLDocument(
119
- modelDefinition: any,
120
- modelOperation: ModelOperation
121
- ): string {
122
- const {
123
- name,
124
- pluralName,
125
- fields,
126
- primaryKeyInfo: {
127
- isCustomPrimaryKey,
128
- primaryKeyFieldName,
129
- sortKeyFieldNames,
130
- },
131
- } = modelDefinition;
132
- const { operationPrefix, usePlural } = graphQLOperationsInfo[modelOperation];
133
-
134
- const fromCache = graphQLDocumentsCache.get(name)?.get(modelOperation);
135
-
136
- if (fromCache !== undefined) {
137
- return fromCache;
138
- }
139
-
140
- if (!graphQLDocumentsCache.has(name)) {
141
- graphQLDocumentsCache.set(name, new Map());
142
- }
143
-
144
- const graphQLFieldName = `${operationPrefix}${usePlural ? pluralName : name}`;
145
- let graphQLOperationType: 'mutation' | 'query' | undefined;
146
- let graphQLSelectionSet: string | undefined;
147
- let graphQLArguments: Record<string, any> | undefined;
148
-
149
- const selectionSetFields = Object.values<any>(fields)
150
- .map(({ type, name }) => typeof type === 'string' && name) // Only scalars for now
151
- .filter(Boolean)
152
- .join(' ');
153
-
154
- switch (modelOperation) {
155
- case 'CREATE':
156
- case 'UPDATE':
157
- case 'DELETE':
158
- graphQLArguments ??
159
- (graphQLArguments = {
160
- input: `${
161
- operationPrefix.charAt(0).toLocaleUpperCase() +
162
- operationPrefix.slice(1)
163
- }${name}Input!`,
164
- });
165
- graphQLOperationType ?? (graphQLOperationType = 'mutation');
166
- case 'READ':
167
- graphQLArguments ??
168
- (graphQLArguments = isCustomPrimaryKey
169
- ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce(
170
- (acc, fieldName) => {
171
- acc[fieldName] = fields[fieldName].type;
172
-
173
- return acc;
174
- },
175
- {}
176
- )
177
- : {
178
- [primaryKeyFieldName]: `${fields[primaryKeyFieldName].type}!`,
179
- });
180
- graphQLSelectionSet ?? (graphQLSelectionSet = selectionSetFields);
181
- case 'LIST':
182
- graphQLOperationType ?? (graphQLOperationType = 'query');
183
- graphQLSelectionSet ??
184
- (graphQLSelectionSet = `items { ${selectionSetFields} }`);
185
- }
186
-
187
- const graphQLDocument = `${graphQLOperationType}${
188
- graphQLArguments
189
- ? `(${Object.entries(graphQLArguments).map(
190
- ([fieldName, type]) => `\$${fieldName}: ${type}`
191
- )})`
192
- : ''
193
- } { ${graphQLFieldName}${
194
- graphQLArguments
195
- ? `(${Object.keys(graphQLArguments).map(
196
- fieldName => `${fieldName}: \$${fieldName}`
197
- )})`
198
- : ''
199
- } { ${graphQLSelectionSet} } }`;
200
-
201
- graphQLDocumentsCache.get(name)?.set(modelOperation, graphQLDocument);
202
-
203
- return graphQLDocument;
204
- }
205
-
206
- function buildGraphQLVariables(
207
- modelDefinition: any,
208
- operation: ModelOperation,
209
- arg: any
210
- ): object {
211
- const {
212
- fields,
213
- primaryKeyInfo: {
214
- isCustomPrimaryKey,
215
- primaryKeyFieldName,
216
- sortKeyFieldNames,
217
- },
218
- } = modelDefinition;
219
-
220
- let variables = {};
221
-
222
- switch (operation) {
223
- case 'CREATE':
224
- variables = { input: arg };
225
- break;
226
- case 'UPDATE':
227
- // readonly fields are not updated
228
- variables = {
229
- input: Object.fromEntries(
230
- Object.entries(arg).filter(([fieldName]) => {
231
- const { isReadOnly } = fields[fieldName];
232
-
233
- return !isReadOnly;
234
- })
235
- ),
236
- };
237
- break;
238
- case 'READ':
239
- case 'DELETE':
240
- // only identifiers are sent
241
- variables = isCustomPrimaryKey
242
- ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce(
243
- (acc, fieldName) => {
244
- acc[fieldName] = arg[fieldName];
245
-
246
- return acc;
247
- },
248
- {}
249
- )
250
- : { [primaryKeyFieldName]: arg[primaryKeyFieldName] };
251
-
252
- if (operation === 'DELETE') {
253
- variables = { input: variables };
254
- }
255
- break;
256
- case 'LIST':
257
- break;
258
- default:
259
- const exhaustiveCheck: never = operation;
260
- throw new Error(`Unhandled operation case: ${exhaustiveCheck}`);
261
- }
262
-
263
- return variables;
264
- }
265
-
266
177
  type FilteredKeys<T> = {
267
178
  [P in keyof T]: T[P] extends never ? never : P;
268
179
  }[keyof T];
@@ -0,0 +1,283 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { ModelTypes } from '@aws-amplify/types-package-alpha';
5
+
6
+ type ListArgs = { selectionSet?: string[]; filter?: {} };
7
+
8
+ // TODO: this should accept single result to support CRUD methods; create helper for array/list
9
+ export function initializeModel(
10
+ client: any,
11
+ modelName: string,
12
+ result: any[],
13
+ modelIntrospection: any
14
+ ): any[] {
15
+ const introModel = modelIntrospection.models[modelName];
16
+ const introModelFields = introModel.fields;
17
+
18
+ const modelFields: string[] = Object.entries(introModelFields)
19
+ .filter(([_, field]: [string, any]) => typeof field.type !== 'string')
20
+ .map(([fieldName]) => fieldName);
21
+
22
+ return result.map(record => {
23
+ const initialized = {};
24
+
25
+ for (const field of modelFields) {
26
+ const relatedModel = introModelFields[field].type.model;
27
+ const connectionField =
28
+ introModelFields[field].association.associatedWith;
29
+ // TODO: support sort key
30
+ const parentPk = introModel.primaryKeyInfo.primaryKeyFieldName;
31
+
32
+ initialized[field] = async () => {
33
+ return client.models[relatedModel].list({
34
+ filter: { [connectionField]: { eq: record[parentPk] } },
35
+ });
36
+ };
37
+ }
38
+
39
+ return { ...record, ...initialized };
40
+ });
41
+ }
42
+
43
+ export const graphQLOperationsInfo = {
44
+ CREATE: { operationPrefix: 'create' as const, usePlural: false },
45
+ READ: { operationPrefix: 'get' as const, usePlural: false },
46
+ UPDATE: { operationPrefix: 'update' as const, usePlural: false },
47
+ DELETE: { operationPrefix: 'delete' as const, usePlural: false },
48
+ LIST: { operationPrefix: 'list' as const, usePlural: true },
49
+ };
50
+ export type ModelOperation = keyof typeof graphQLOperationsInfo;
51
+
52
+ type OperationPrefix =
53
+ (typeof graphQLOperationsInfo)[ModelOperation]['operationPrefix'];
54
+
55
+ const graphQLDocumentsCache = new Map<string, Map<ModelOperation, string>>();
56
+ const SELECTION_SET_ALL_NESTED = '*';
57
+
58
+ function defaultSelectionSetForModel(modelDefinition: any): string {
59
+ const { fields } = modelDefinition;
60
+ return Object.values<any>(fields)
61
+ .map(({ type, name }) => typeof type === 'string' && name) // Default selection set omits model fields
62
+ .filter(Boolean)
63
+ .join(' ');
64
+ }
65
+
66
+ function generateSelectionSet(
67
+ modelIntrospection: any,
68
+ modelName: string,
69
+ selectionSet?: string[]
70
+ ) {
71
+ const modelDefinition = modelIntrospection[modelName];
72
+ const { fields } = modelDefinition;
73
+
74
+ if (!selectionSet) {
75
+ return defaultSelectionSetForModel(modelDefinition);
76
+ }
77
+
78
+ const selSet: string[] = [];
79
+
80
+ for (const f of selectionSet) {
81
+ const nested = f.includes('.');
82
+
83
+ if (nested) {
84
+ const [modelFieldName, selectedField] = f.split('.');
85
+
86
+ const relatedModel = fields[modelFieldName]?.type?.model;
87
+
88
+ if (!relatedModel) {
89
+ // TODO: may need to change this to support custom types
90
+ throw Error(`${modelFieldName} is not a model field`);
91
+ }
92
+
93
+ if (selectedField === SELECTION_SET_ALL_NESTED) {
94
+ const relatedModelDefinition = modelIntrospection[relatedModel];
95
+ const defaultSelectionSet = defaultSelectionSetForModel(
96
+ relatedModelDefinition
97
+ );
98
+
99
+ if (fields[modelFieldName]?.isArray) {
100
+ selSet.push(`${modelFieldName} { items { ${defaultSelectionSet} } }`);
101
+ } else {
102
+ selSet.push(`${modelFieldName} { ${defaultSelectionSet} }`);
103
+ }
104
+ }
105
+ } else {
106
+ const exists = Boolean(fields[f]);
107
+
108
+ if (!exists) {
109
+ throw Error(`${f} is not a field of model ${modelName}`);
110
+ }
111
+
112
+ selSet.push(f);
113
+ }
114
+ }
115
+
116
+ return selSet.join(' ');
117
+ }
118
+
119
+ export function generateGraphQLDocument(
120
+ modelIntrospection: any,
121
+ modelName: string,
122
+ modelOperation: ModelOperation,
123
+ listArgs?: ListArgs
124
+ ): string {
125
+ const modelDefinition = modelIntrospection[modelName];
126
+
127
+ const {
128
+ name,
129
+ pluralName,
130
+ fields,
131
+ primaryKeyInfo: {
132
+ isCustomPrimaryKey,
133
+ primaryKeyFieldName,
134
+ sortKeyFieldNames,
135
+ },
136
+ } = modelDefinition;
137
+
138
+ const { operationPrefix, usePlural } = graphQLOperationsInfo[modelOperation];
139
+
140
+ const { selectionSet } = listArgs || {};
141
+
142
+ const fromCache = graphQLDocumentsCache.get(name)?.get(modelOperation);
143
+
144
+ if (fromCache !== undefined) {
145
+ return fromCache;
146
+ }
147
+
148
+ if (!graphQLDocumentsCache.has(name)) {
149
+ graphQLDocumentsCache.set(name, new Map());
150
+ }
151
+
152
+ const graphQLFieldName = `${operationPrefix}${usePlural ? pluralName : name}`;
153
+ let graphQLOperationType: 'mutation' | 'query' | undefined;
154
+ let graphQLSelectionSet: string | undefined;
155
+ let graphQLArguments: Record<string, any> | undefined;
156
+
157
+ const selectionSetFields = generateSelectionSet(
158
+ modelIntrospection,
159
+ modelName,
160
+ selectionSet
161
+ );
162
+
163
+ console.log('generated sel set', selectionSetFields);
164
+
165
+ switch (modelOperation) {
166
+ case 'CREATE':
167
+ case 'UPDATE':
168
+ case 'DELETE':
169
+ graphQLArguments ??
170
+ (graphQLArguments = {
171
+ input: `${
172
+ operationPrefix.charAt(0).toLocaleUpperCase() +
173
+ operationPrefix.slice(1)
174
+ }${name}Input!`,
175
+ });
176
+ graphQLOperationType ?? (graphQLOperationType = 'mutation');
177
+ case 'READ':
178
+ graphQLArguments ??
179
+ (graphQLArguments = isCustomPrimaryKey
180
+ ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce(
181
+ (acc, fieldName) => {
182
+ acc[fieldName] = fields[fieldName].type;
183
+
184
+ return acc;
185
+ },
186
+ {}
187
+ )
188
+ : {
189
+ [primaryKeyFieldName]: `${fields[primaryKeyFieldName].type}!`,
190
+ });
191
+ graphQLSelectionSet ?? (graphQLSelectionSet = selectionSetFields);
192
+ case 'LIST':
193
+ graphQLArguments ??
194
+ (graphQLArguments = {
195
+ filter: `Model${name}FilterInput`,
196
+ });
197
+ graphQLOperationType ?? (graphQLOperationType = 'query');
198
+ graphQLSelectionSet ??
199
+ (graphQLSelectionSet = `items { ${selectionSetFields} }`);
200
+ }
201
+
202
+ const graphQLDocument = `${graphQLOperationType}${
203
+ graphQLArguments
204
+ ? `(${Object.entries(graphQLArguments).map(
205
+ ([fieldName, type]) => `\$${fieldName}: ${type}`
206
+ )})`
207
+ : ''
208
+ } { ${graphQLFieldName}${
209
+ graphQLArguments
210
+ ? `(${Object.keys(graphQLArguments).map(
211
+ fieldName => `${fieldName}: \$${fieldName}`
212
+ )})`
213
+ : ''
214
+ } { ${graphQLSelectionSet} } }`;
215
+
216
+ graphQLDocumentsCache.get(name)?.set(modelOperation, graphQLDocument);
217
+
218
+ return graphQLDocument;
219
+ }
220
+
221
+ export function buildGraphQLVariables(
222
+ modelDefinition: any,
223
+ operation: ModelOperation,
224
+ arg: any
225
+ ): object {
226
+ const {
227
+ fields,
228
+ primaryKeyInfo: {
229
+ isCustomPrimaryKey,
230
+ primaryKeyFieldName,
231
+ sortKeyFieldNames,
232
+ },
233
+ } = modelDefinition;
234
+
235
+ let variables = {};
236
+
237
+ // TODO: process input
238
+ switch (operation) {
239
+ case 'CREATE':
240
+ variables = { input: arg };
241
+ break;
242
+ case 'UPDATE':
243
+ // readonly fields are not updated
244
+ variables = {
245
+ input: Object.fromEntries(
246
+ Object.entries(arg).filter(([fieldName]) => {
247
+ const { isReadOnly } = fields[fieldName];
248
+
249
+ return !isReadOnly;
250
+ })
251
+ ),
252
+ };
253
+ break;
254
+ case 'READ':
255
+ case 'DELETE':
256
+ // only identifiers are sent
257
+ variables = isCustomPrimaryKey
258
+ ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce(
259
+ (acc, fieldName) => {
260
+ acc[fieldName] = arg[fieldName];
261
+
262
+ return acc;
263
+ },
264
+ {}
265
+ )
266
+ : { [primaryKeyFieldName]: arg[primaryKeyFieldName] };
267
+
268
+ if (operation === 'DELETE') {
269
+ variables = { input: variables };
270
+ }
271
+ break;
272
+ case 'LIST':
273
+ if (arg?.filter) {
274
+ variables = { filter: arg.filter };
275
+ }
276
+ break;
277
+ default:
278
+ const exhaustiveCheck: never = operation;
279
+ throw new Error(`Unhandled operation case: ${exhaustiveCheck}`);
280
+ }
281
+
282
+ return variables;
283
+ }