@aws-amplify/api-graphql 4.0.2-unstable.ff85fa5.0 → 4.0.2

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.
Files changed (122) hide show
  1. package/dist/cjs/GraphQLAPI.js +1 -1
  2. package/dist/cjs/GraphQLAPI.js.map +1 -1
  3. package/dist/cjs/Providers/AWSAppSyncRealTimeProvider/index.js +1 -1
  4. package/dist/cjs/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
  5. package/dist/cjs/internals/APIClient.js +528 -0
  6. package/dist/cjs/internals/APIClient.js.map +1 -0
  7. package/dist/cjs/internals/InternalGraphQLAPI.js +14 -23
  8. package/dist/cjs/internals/InternalGraphQLAPI.js.map +1 -1
  9. package/dist/cjs/internals/generateClient.js +33 -0
  10. package/dist/cjs/internals/generateClient.js.map +1 -0
  11. package/dist/cjs/internals/generateModelsProperty.js +43 -0
  12. package/dist/cjs/internals/generateModelsProperty.js.map +1 -0
  13. package/dist/cjs/internals/index.js +3 -1
  14. package/dist/cjs/internals/index.js.map +1 -1
  15. package/dist/cjs/internals/operations/get.js +61 -0
  16. package/dist/cjs/internals/operations/get.js.map +1 -0
  17. package/dist/cjs/internals/operations/list.js +73 -0
  18. package/dist/cjs/internals/operations/list.js.map +1 -0
  19. package/dist/cjs/internals/operations/observeQuery.js +121 -0
  20. package/dist/cjs/internals/operations/observeQuery.js.map +1 -0
  21. package/dist/cjs/internals/operations/subscription.js +30 -0
  22. package/dist/cjs/internals/operations/subscription.js.map +1 -0
  23. package/dist/cjs/internals/server/generateClient.js +32 -0
  24. package/dist/cjs/internals/server/generateClient.js.map +1 -0
  25. package/dist/cjs/internals/server/generateModelsProperty.js +47 -0
  26. package/dist/cjs/internals/server/generateModelsProperty.js.map +1 -0
  27. package/dist/cjs/internals/server/index.js +9 -0
  28. package/dist/cjs/internals/server/index.js.map +1 -0
  29. package/dist/cjs/internals/types.js +4 -0
  30. package/dist/cjs/internals/types.js.map +1 -0
  31. package/dist/cjs/types/index.js +3 -1
  32. package/dist/cjs/types/index.js.map +1 -1
  33. package/dist/cjs/utils/findIndexByFields.js +25 -0
  34. package/dist/cjs/utils/findIndexByFields.js.map +1 -0
  35. package/dist/cjs/utils/index.js +5 -1
  36. package/dist/cjs/utils/index.js.map +1 -1
  37. package/dist/cjs/utils/resolveOwnerFields.js +42 -0
  38. package/dist/cjs/utils/resolveOwnerFields.js.map +1 -0
  39. package/dist/cjs/utils/resolvePKFields.js +17 -0
  40. package/dist/cjs/utils/resolvePKFields.js.map +1 -0
  41. package/dist/esm/GraphQLAPI.mjs +1 -1
  42. package/dist/esm/GraphQLAPI.mjs.map +1 -1
  43. package/dist/esm/Providers/AWSAppSyncRealTimeProvider/index.mjs +1 -1
  44. package/dist/esm/Providers/AWSAppSyncRealTimeProvider/index.mjs.map +1 -1
  45. package/dist/esm/index.mjs +1 -1
  46. package/dist/esm/internals/APIClient.d.ts +112 -0
  47. package/dist/esm/internals/APIClient.mjs +518 -0
  48. package/dist/esm/internals/APIClient.mjs.map +1 -0
  49. package/dist/esm/internals/InternalGraphQLAPI.d.ts +0 -11
  50. package/dist/esm/internals/InternalGraphQLAPI.mjs +14 -22
  51. package/dist/esm/internals/InternalGraphQLAPI.mjs.map +1 -1
  52. package/dist/esm/internals/generateClient.d.ts +12 -0
  53. package/dist/esm/internals/generateClient.mjs +31 -0
  54. package/dist/esm/internals/generateClient.mjs.map +1 -0
  55. package/dist/esm/internals/generateModelsProperty.d.ts +4 -0
  56. package/dist/esm/internals/generateModelsProperty.mjs +41 -0
  57. package/dist/esm/internals/generateModelsProperty.mjs.map +1 -0
  58. package/dist/esm/internals/index.d.ts +1 -0
  59. package/dist/esm/internals/index.mjs +1 -0
  60. package/dist/esm/internals/index.mjs.map +1 -1
  61. package/dist/esm/internals/operations/get.d.ts +5 -0
  62. package/dist/esm/internals/operations/get.mjs +59 -0
  63. package/dist/esm/internals/operations/get.mjs.map +1 -0
  64. package/dist/esm/internals/operations/list.d.ts +4 -0
  65. package/dist/esm/internals/operations/list.mjs +71 -0
  66. package/dist/esm/internals/operations/list.mjs.map +1 -0
  67. package/dist/esm/internals/operations/observeQuery.d.ts +3 -0
  68. package/dist/esm/internals/operations/observeQuery.mjs +122 -0
  69. package/dist/esm/internals/operations/observeQuery.mjs.map +1 -0
  70. package/dist/esm/internals/operations/subscription.d.ts +3 -0
  71. package/dist/esm/internals/operations/subscription.mjs +28 -0
  72. package/dist/esm/internals/operations/subscription.mjs.map +1 -0
  73. package/dist/esm/internals/server/generateClient.d.ts +11 -0
  74. package/dist/esm/internals/server/generateClient.mjs +34 -0
  75. package/dist/esm/internals/server/generateClient.mjs.map +1 -0
  76. package/dist/esm/internals/server/generateModelsProperty.d.ts +3 -0
  77. package/dist/esm/internals/server/generateModelsProperty.mjs +45 -0
  78. package/dist/esm/internals/server/generateModelsProperty.mjs.map +1 -0
  79. package/dist/esm/internals/server/index.d.ts +1 -0
  80. package/dist/esm/internals/server/index.mjs +2 -0
  81. package/dist/esm/internals/server/index.mjs.map +1 -0
  82. package/dist/esm/internals/types.d.ts +17 -0
  83. package/dist/esm/internals/types.mjs +2 -0
  84. package/dist/esm/internals/types.mjs.map +1 -0
  85. package/dist/esm/types/index.d.ts +32 -2
  86. package/dist/esm/types/index.mjs +3 -1
  87. package/dist/esm/types/index.mjs.map +1 -1
  88. package/dist/esm/utils/findIndexByFields.d.ts +9 -0
  89. package/dist/esm/utils/findIndexByFields.mjs +22 -0
  90. package/dist/esm/utils/findIndexByFields.mjs.map +1 -0
  91. package/dist/esm/utils/index.d.ts +2 -0
  92. package/dist/esm/utils/index.mjs +2 -0
  93. package/dist/esm/utils/index.mjs.map +1 -1
  94. package/dist/esm/utils/resolveOwnerFields.d.ts +12 -0
  95. package/dist/esm/utils/resolveOwnerFields.mjs +39 -0
  96. package/dist/esm/utils/resolveOwnerFields.mjs.map +1 -0
  97. package/dist/esm/utils/resolvePKFields.d.ts +13 -0
  98. package/dist/esm/utils/resolvePKFields.mjs +14 -0
  99. package/dist/esm/utils/resolvePKFields.mjs.map +1 -0
  100. package/internals/server/package.json +8 -0
  101. package/package.json +148 -146
  102. package/src/GraphQLAPI.ts +2 -2
  103. package/src/Providers/AWSAppSyncRealTimeProvider/index.ts +9 -4
  104. package/src/internals/APIClient.ts +780 -0
  105. package/src/internals/InternalGraphQLAPI.ts +21 -31
  106. package/src/internals/generateClient.ts +33 -0
  107. package/src/internals/generateModelsProperty.ts +73 -0
  108. package/src/internals/index.ts +1 -0
  109. package/src/internals/operations/get.ts +131 -0
  110. package/src/internals/operations/list.ts +132 -0
  111. package/src/internals/operations/observeQuery.ts +144 -0
  112. package/src/internals/operations/subscription.ts +65 -0
  113. package/src/internals/server/generateClient.ts +45 -0
  114. package/src/internals/server/generateModelsProperty.ts +80 -0
  115. package/src/internals/server/index.ts +4 -0
  116. package/src/internals/types.ts +21 -0
  117. package/src/types/index.ts +42 -2
  118. package/src/utils/ConnectionStateMonitor.ts +3 -1
  119. package/src/utils/findIndexByFields.ts +31 -0
  120. package/src/utils/index.ts +2 -0
  121. package/src/utils/resolveOwnerFields.ts +65 -0
  122. package/src/utils/resolvePKFields.ts +22 -0
@@ -0,0 +1,780 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { resolveOwnerFields } from '../utils/resolveOwnerFields';
4
+ import {
5
+ GraphQLAuthMode,
6
+ ModelIntrospectionSchema,
7
+ ModelFieldType,
8
+ SchemaModel,
9
+ SchemaModels,
10
+ AssociationHasOne,
11
+ AssociationBelongsTo,
12
+ } from '@aws-amplify/core/internals/utils';
13
+ import {
14
+ AuthModeParams,
15
+ ClientWithModels,
16
+ ListArgs,
17
+ QueryArgs,
18
+ V6Client,
19
+ V6ClientSSRRequest,
20
+ __authMode,
21
+ __authToken,
22
+ } from '../types';
23
+ import { AmplifyServer } from '@aws-amplify/core/internals/adapter-core';
24
+
25
+ type LazyLoadOptions = {
26
+ authMode?: GraphQLAuthMode;
27
+ authToken?: string | undefined;
28
+ limit?: number | undefined;
29
+ nextToken?: string | undefined | null;
30
+ };
31
+
32
+ const connectionType = {
33
+ HAS_ONE: 'HAS_ONE',
34
+ HAS_MANY: 'HAS_MANY',
35
+ BELONGS_TO: 'BELONGS_TO',
36
+ };
37
+
38
+ /**
39
+ *
40
+ * @param GraphQL response object
41
+ * @returns response object with `items` properties flattened
42
+ */
43
+ export const flattenItems = (obj: Record<string, any>): Record<string, any> => {
44
+ const res: Record<string, any> = {};
45
+
46
+ Object.entries(obj).forEach(([prop, value]) => {
47
+ if (typeof value === 'object' && value !== null) {
48
+ if (value.items !== undefined) {
49
+ res[prop] = value.items.map((item: Record<string, any>) =>
50
+ flattenItems(item)
51
+ );
52
+ return;
53
+ }
54
+ res[prop] = flattenItems(value);
55
+ return;
56
+ }
57
+
58
+ res[prop] = value;
59
+ });
60
+
61
+ return res;
62
+ };
63
+
64
+ // TODO: this should accept single result to support CRUD methods; create helper for array/list
65
+ export function initializeModel(
66
+ client: ClientWithModels,
67
+ modelName: string,
68
+ result: any[],
69
+ modelIntrospection: ModelIntrospectionSchema,
70
+ authMode: GraphQLAuthMode | undefined,
71
+ authToken: string | undefined,
72
+ context = false
73
+ ): any[] {
74
+ const introModel = modelIntrospection.models[modelName];
75
+ const introModelFields = introModel.fields;
76
+
77
+ const modelFields: string[] = Object.entries(introModelFields)
78
+ .filter(([_, field]: [string, any]) => field?.type?.model !== undefined)
79
+ .map(([fieldName]) => fieldName);
80
+
81
+ return result.map(record => {
82
+ const initializedRelationalFields: Record<string, any> = {};
83
+
84
+ for (const fieldName of modelFields) {
85
+ const modelField = introModelFields[fieldName];
86
+ const modelFieldType = modelField?.type as ModelFieldType;
87
+
88
+ const relatedModelName = modelFieldType.model;
89
+ const relatedModel = modelIntrospection.models[relatedModelName!];
90
+
91
+ const relatedModelPKFieldName =
92
+ relatedModel.primaryKeyInfo.primaryKeyFieldName;
93
+
94
+ const relatedModelSKFieldNames =
95
+ relatedModel.primaryKeyInfo.sortKeyFieldNames;
96
+
97
+ const relationType = modelField.association?.connectionType;
98
+
99
+ let connectionFields: string[] = [];
100
+ if (
101
+ modelField.association &&
102
+ 'associatedWith' in modelField.association
103
+ ) {
104
+ connectionFields = modelField.association.associatedWith;
105
+ }
106
+
107
+ let targetNames: string[] = [];
108
+ if (modelField.association && 'targetNames' in modelField.association) {
109
+ targetNames = modelField.association.targetNames;
110
+ }
111
+
112
+ switch (relationType) {
113
+ case connectionType.HAS_ONE:
114
+ case connectionType.BELONGS_TO:
115
+ const sortKeyValues = relatedModelSKFieldNames.reduce(
116
+ (acc: Record<string, any>, curVal) => {
117
+ if (record[curVal]) {
118
+ return (acc[curVal] = record[curVal]);
119
+ }
120
+ },
121
+ {}
122
+ );
123
+
124
+ if (context) {
125
+ initializedRelationalFields[fieldName] = (
126
+ contextSpec: AmplifyServer.ContextSpec,
127
+ options?: LazyLoadOptions
128
+ ) => {
129
+ if (record[targetNames[0]]) {
130
+ return (
131
+ client as V6ClientSSRRequest<Record<string, any>>
132
+ ).models[relatedModelName].get(
133
+ contextSpec,
134
+ {
135
+ [relatedModelPKFieldName]: record[targetNames[0]],
136
+ ...sortKeyValues,
137
+ },
138
+ {
139
+ authMode: options?.authMode || authMode,
140
+ authToken: options?.authToken || authToken,
141
+ }
142
+ );
143
+ }
144
+ return undefined;
145
+ };
146
+ } else {
147
+ initializedRelationalFields[fieldName] = (
148
+ options?: LazyLoadOptions
149
+ ) => {
150
+ if (record[targetNames[0]]) {
151
+ return (client as V6Client<Record<string, any>>).models[
152
+ relatedModelName
153
+ ].get(
154
+ {
155
+ [relatedModelPKFieldName]: record[targetNames[0]],
156
+ ...sortKeyValues,
157
+ },
158
+ {
159
+ authMode: options?.authMode || authMode,
160
+ authToken: options?.authToken || authToken,
161
+ }
162
+ );
163
+ }
164
+ return undefined;
165
+ };
166
+ }
167
+
168
+ break;
169
+ case connectionType.HAS_MANY:
170
+ const parentPk = introModel.primaryKeyInfo.primaryKeyFieldName;
171
+ const parentSK = introModel.primaryKeyInfo.sortKeyFieldNames;
172
+
173
+ // M:N check - TODO: refactor
174
+ const relatedModelField = relatedModel.fields[connectionFields[0]];
175
+ const relatedModelFieldType =
176
+ relatedModelField.type as ModelFieldType;
177
+ if (relatedModelFieldType.model) {
178
+ let relatedTargetNames: string[] = [];
179
+ if (
180
+ relatedModelField.association &&
181
+ 'targetNames' in relatedModelField.association
182
+ ) {
183
+ relatedTargetNames = relatedModelField.association?.targetNames;
184
+ }
185
+
186
+ const hasManyFilter: Record<string, any> = relatedTargetNames.map(
187
+ (field, idx) => {
188
+ if (idx === 0) {
189
+ return { [field]: { eq: record[parentPk] } };
190
+ }
191
+
192
+ return { [field]: { eq: record[parentSK[idx - 1]] } };
193
+ }
194
+ );
195
+
196
+ if (context) {
197
+ initializedRelationalFields[fieldName] = (
198
+ contextSpec: AmplifyServer.ContextSpec,
199
+ options?: LazyLoadOptions
200
+ ) => {
201
+ if (record[parentPk]) {
202
+ return (
203
+ client as V6ClientSSRRequest<Record<string, any>>
204
+ ).models[relatedModelName].list(contextSpec, {
205
+ filter: { and: hasManyFilter },
206
+ limit: options?.limit,
207
+ nextToken: options?.nextToken,
208
+ authMode: options?.authMode || authMode,
209
+ authToken: options?.authToken || authToken,
210
+ });
211
+ }
212
+ return [];
213
+ };
214
+ } else {
215
+ initializedRelationalFields[fieldName] = (
216
+ options?: LazyLoadOptions
217
+ ) => {
218
+ if (record[parentPk]) {
219
+ return (client as V6Client<Record<string, any>>).models[
220
+ relatedModelName
221
+ ].list({
222
+ filter: { and: hasManyFilter },
223
+ limit: options?.limit,
224
+ nextToken: options?.nextToken,
225
+ authMode: options?.authMode || authMode,
226
+ authToken: options?.authToken || authToken,
227
+ });
228
+ }
229
+ return [];
230
+ };
231
+ }
232
+
233
+ break;
234
+ }
235
+
236
+ const hasManyFilter: Record<string, any> = connectionFields.map(
237
+ (field, idx) => {
238
+ if (idx === 0) {
239
+ return { [field]: { eq: record[parentPk] } };
240
+ }
241
+
242
+ return { [field]: { eq: record[parentSK[idx - 1]] } };
243
+ }
244
+ );
245
+
246
+ if (context) {
247
+ initializedRelationalFields[fieldName] = (
248
+ contextSpec: AmplifyServer.ContextSpec,
249
+ options?: LazyLoadOptions
250
+ ) => {
251
+ if (record[parentPk]) {
252
+ return (
253
+ client as V6ClientSSRRequest<Record<string, any>>
254
+ ).models[relatedModelName].list(contextSpec, {
255
+ filter: { and: hasManyFilter },
256
+ limit: options?.limit,
257
+ nextToken: options?.nextToken,
258
+ authMode: options?.authMode || authMode,
259
+ authToken: options?.authToken || authToken,
260
+ });
261
+ }
262
+ return [];
263
+ };
264
+ } else {
265
+ initializedRelationalFields[fieldName] = (
266
+ options?: LazyLoadOptions
267
+ ) => {
268
+ if (record[parentPk]) {
269
+ return (client as V6Client<Record<string, any>>).models[
270
+ relatedModelName
271
+ ].list({
272
+ filter: { and: hasManyFilter },
273
+ limit: options?.limit,
274
+ nextToken: options?.nextToken,
275
+ authMode: options?.authMode || authMode,
276
+ authToken: options?.authToken || authToken,
277
+ });
278
+ }
279
+ return [];
280
+ };
281
+ }
282
+
283
+ break;
284
+ default:
285
+ break;
286
+ }
287
+ }
288
+
289
+ return { ...record, ...initializedRelationalFields };
290
+ });
291
+ }
292
+
293
+ export const graphQLOperationsInfo = {
294
+ CREATE: { operationPrefix: 'create' as const, usePlural: false },
295
+ READ: { operationPrefix: 'get' as const, usePlural: false },
296
+ UPDATE: { operationPrefix: 'update' as const, usePlural: false },
297
+ DELETE: { operationPrefix: 'delete' as const, usePlural: false },
298
+ LIST: { operationPrefix: 'list' as const, usePlural: true },
299
+ ONCREATE: { operationPrefix: 'onCreate' as const, usePlural: false },
300
+ ONUPDATE: { operationPrefix: 'onUpdate' as const, usePlural: false },
301
+ ONDELETE: { operationPrefix: 'onDelete' as const, usePlural: false },
302
+ OBSERVE_QUERY: { operationPrefix: 'observeQuery' as const, usePlural: false },
303
+ };
304
+ export type ModelOperation = keyof typeof graphQLOperationsInfo;
305
+
306
+ type OperationPrefix =
307
+ (typeof graphQLOperationsInfo)[ModelOperation]['operationPrefix'];
308
+
309
+ const graphQLDocumentsCache = new Map<string, Map<ModelOperation, string>>();
310
+ const SELECTION_SET_WILDCARD = '*';
311
+
312
+ function defaultSelectionSetForModel(modelDefinition: SchemaModel): string[] {
313
+ // fields that are explicitly part of the graphql schema; not
314
+ // inferred from owner auth rules.
315
+ const { fields } = modelDefinition;
316
+ const explicitFields = Object.values<any>(fields)
317
+ .map(({ type, name }) => typeof type === 'string' && name) // Default selection set omits model fields
318
+ .filter(Boolean);
319
+
320
+ // fields used for owner auth rules that may or may not also
321
+ // be explicit on the model.
322
+ const ownerFields = resolveOwnerFields(modelDefinition);
323
+
324
+ return Array.from(new Set(explicitFields.concat(ownerFields)));
325
+ }
326
+
327
+ const FIELD_IR = '';
328
+
329
+ /**
330
+ * Generates nested Custom Selection Set IR from path
331
+ *
332
+ * @param modelDefinitions
333
+ * @param modelName
334
+ * @param selectionSet - array of object paths
335
+ * @example
336
+ * ### Given
337
+ * `selectionSet = ['id', 'comments.post.id']`
338
+ * ### Returns
339
+ * ```ts
340
+ * {
341
+ * id: '',
342
+ * comments: {
343
+ * items: { post: { id: '' } }
344
+ * }
345
+ * }
346
+ * ```
347
+ */
348
+ export function customSelectionSetToIR(
349
+ modelDefinitions: SchemaModels,
350
+ modelName: string,
351
+ selectionSet: string[]
352
+ ): Record<string, string | object> {
353
+ const modelDefinition = modelDefinitions[modelName];
354
+ const { fields: modelFields } = modelDefinition;
355
+
356
+ return selectionSet.reduce((resultObj: Record<string, any>, path) => {
357
+ const [fieldName, nested, ...rest] = path.split('.');
358
+
359
+ if (nested) {
360
+ const fieldType = modelFields[fieldName]?.type as ModelFieldType;
361
+ const relatedModel = fieldType.model;
362
+
363
+ if (!relatedModel) {
364
+ // TODO: may need to change this to support custom types
365
+ throw Error(`${fieldName} is not a model field`);
366
+ }
367
+
368
+ const relatedModelDefinition = modelDefinitions[relatedModel];
369
+
370
+ const selectionSet =
371
+ nested === SELECTION_SET_WILDCARD
372
+ ? defaultSelectionSetIR(relatedModelDefinition)
373
+ : // if we have a path like 'field.anotherField' recursively build up selection set IR
374
+ customSelectionSetToIR(modelDefinitions, relatedModel, [
375
+ [nested, ...rest].join('.'),
376
+ ]);
377
+
378
+ if (modelFields[fieldName]?.isArray) {
379
+ const existing = resultObj[fieldName] || {
380
+ items: {},
381
+ };
382
+ const merged = { ...existing.items, ...selectionSet };
383
+
384
+ resultObj[fieldName] = { items: merged };
385
+ return resultObj;
386
+ }
387
+
388
+ const existingItems = resultObj[fieldName] || {};
389
+ const merged = { ...existingItems, ...selectionSet };
390
+
391
+ resultObj[fieldName] = merged;
392
+ return resultObj;
393
+ }
394
+
395
+ const exists = Boolean(modelFields[fieldName]);
396
+
397
+ if (!exists) {
398
+ throw Error(`${fieldName} is not a field of model ${modelName}`);
399
+ }
400
+
401
+ resultObj[fieldName] = FIELD_IR;
402
+ return resultObj;
403
+ }, {});
404
+ }
405
+
406
+ const defaultSelectionSetIR = (relatedModelDefinition: SchemaModel) => {
407
+ const defaultSelectionSet = defaultSelectionSetForModel(
408
+ relatedModelDefinition
409
+ );
410
+
411
+ const reduced = defaultSelectionSet.reduce(
412
+ (acc: Record<string, any>, curVal) => {
413
+ acc[curVal] = FIELD_IR;
414
+ return acc;
415
+ },
416
+ {}
417
+ );
418
+
419
+ return reduced;
420
+ };
421
+
422
+ /**
423
+ * Stringifies selection set IR
424
+ * * @example
425
+ * ### Given
426
+ * ```ts
427
+ * {
428
+ * id: '',
429
+ * comments: {
430
+ * items: { post: { id: '' } }
431
+ * }
432
+ * }
433
+ * ```
434
+ * ### Returns
435
+ * `'id comments { items { post { id } } }'`
436
+ */
437
+ export function selectionSetIRToString(
438
+ obj: Record<string, string | any>
439
+ ): string {
440
+ const res: string[] = [];
441
+
442
+ Object.entries(obj).forEach(([fieldName, value]) => {
443
+ if (value === FIELD_IR) {
444
+ res.push(fieldName);
445
+ } else if (typeof value === 'object' && value !== null) {
446
+ if (value?.items) {
447
+ res.push(
448
+ fieldName,
449
+ '{',
450
+ 'items',
451
+ '{',
452
+ selectionSetIRToString(value.items),
453
+ '}',
454
+ '}'
455
+ );
456
+ } else {
457
+ res.push(fieldName, '{', selectionSetIRToString(value), '}');
458
+ }
459
+ }
460
+ });
461
+
462
+ return res.join(' ');
463
+ }
464
+
465
+ export function generateSelectionSet(
466
+ modelDefinitions: SchemaModels,
467
+ modelName: string,
468
+ selectionSet?: string[]
469
+ ) {
470
+ const modelDefinition = modelDefinitions[modelName];
471
+
472
+ if (!selectionSet) {
473
+ return defaultSelectionSetForModel(modelDefinition).join(' ');
474
+ }
475
+
476
+ const selSetIr = customSelectionSetToIR(
477
+ modelDefinitions,
478
+ modelName,
479
+ selectionSet
480
+ );
481
+ const selSetString = selectionSetIRToString(selSetIr);
482
+
483
+ return selSetString;
484
+ }
485
+
486
+ export function generateGraphQLDocument(
487
+ modelDefinitions: SchemaModels,
488
+ modelName: string,
489
+ modelOperation: ModelOperation,
490
+ listArgs?: ListArgs
491
+ ): string {
492
+ const modelDefinition = modelDefinitions[modelName];
493
+
494
+ const {
495
+ name,
496
+ pluralName,
497
+ fields,
498
+ primaryKeyInfo: {
499
+ isCustomPrimaryKey,
500
+ primaryKeyFieldName,
501
+ sortKeyFieldNames,
502
+ },
503
+ } = modelDefinition;
504
+
505
+ const { operationPrefix, usePlural } = graphQLOperationsInfo[modelOperation];
506
+
507
+ const { selectionSet } = listArgs || {};
508
+
509
+ const fromCache = graphQLDocumentsCache.get(name)?.get(modelOperation);
510
+
511
+ if (fromCache !== undefined) {
512
+ return fromCache;
513
+ }
514
+
515
+ if (!graphQLDocumentsCache.has(name)) {
516
+ graphQLDocumentsCache.set(name, new Map());
517
+ }
518
+
519
+ const graphQLFieldName = `${operationPrefix}${usePlural ? pluralName : name}`;
520
+ let graphQLOperationType: 'mutation' | 'query' | 'subscription' | undefined;
521
+ let graphQLSelectionSet: string | undefined;
522
+ let graphQLArguments: Record<string, any> | undefined;
523
+
524
+ const selectionSetFields = generateSelectionSet(
525
+ modelDefinitions,
526
+ modelName,
527
+ selectionSet
528
+ );
529
+
530
+ switch (modelOperation) {
531
+ case 'CREATE':
532
+ case 'UPDATE':
533
+ case 'DELETE':
534
+ graphQLArguments ??
535
+ (graphQLArguments = {
536
+ input: `${
537
+ operationPrefix.charAt(0).toLocaleUpperCase() +
538
+ operationPrefix.slice(1)
539
+ }${name}Input!`,
540
+ });
541
+ graphQLOperationType ?? (graphQLOperationType = 'mutation');
542
+ case 'READ':
543
+ graphQLArguments ??
544
+ (graphQLArguments = isCustomPrimaryKey
545
+ ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce(
546
+ (acc: Record<string, any>, fieldName) => {
547
+ acc[fieldName] = fields[fieldName].type;
548
+
549
+ return acc;
550
+ },
551
+ {}
552
+ )
553
+ : {
554
+ [primaryKeyFieldName]: `${fields[primaryKeyFieldName].type}!`,
555
+ });
556
+ graphQLSelectionSet ?? (graphQLSelectionSet = selectionSetFields);
557
+ case 'LIST':
558
+ graphQLArguments ??
559
+ (graphQLArguments = {
560
+ filter: `Model${name}FilterInput`,
561
+ limit: 'Int',
562
+ nextToken: 'String',
563
+ });
564
+ graphQLOperationType ?? (graphQLOperationType = 'query');
565
+ graphQLSelectionSet ??
566
+ (graphQLSelectionSet = `items { ${selectionSetFields} } nextToken __typename`);
567
+ case 'ONCREATE':
568
+ case 'ONUPDATE':
569
+ case 'ONDELETE':
570
+ graphQLArguments ??
571
+ (graphQLArguments = {
572
+ filter: `ModelSubscription${name}FilterInput`,
573
+ });
574
+ graphQLOperationType ?? (graphQLOperationType = 'subscription');
575
+ graphQLSelectionSet ?? (graphQLSelectionSet = selectionSetFields);
576
+ break;
577
+ case 'OBSERVE_QUERY':
578
+ default:
579
+ throw new Error(
580
+ 'Internal error: Attempted to generate graphql document for observeQuery. Please report this error.'
581
+ );
582
+ }
583
+
584
+ const graphQLDocument = `${graphQLOperationType}${
585
+ graphQLArguments
586
+ ? `(${Object.entries(graphQLArguments).map(
587
+ ([fieldName, type]) => `\$${fieldName}: ${type}`
588
+ )})`
589
+ : ''
590
+ } { ${graphQLFieldName}${
591
+ graphQLArguments
592
+ ? `(${Object.keys(graphQLArguments).map(
593
+ fieldName => `${fieldName}: \$${fieldName}`
594
+ )})`
595
+ : ''
596
+ } { ${graphQLSelectionSet} } }`;
597
+
598
+ graphQLDocumentsCache.get(name)?.set(modelOperation, graphQLDocument);
599
+
600
+ return graphQLDocument;
601
+ }
602
+
603
+ export function buildGraphQLVariables(
604
+ modelDefinition: SchemaModel,
605
+ operation: ModelOperation,
606
+ arg: QueryArgs | undefined,
607
+ modelIntrospection: ModelIntrospectionSchema
608
+ ): object {
609
+ const {
610
+ fields,
611
+ primaryKeyInfo: {
612
+ isCustomPrimaryKey,
613
+ primaryKeyFieldName,
614
+ sortKeyFieldNames,
615
+ },
616
+ } = modelDefinition;
617
+
618
+ let variables: Record<string, any> = {};
619
+
620
+ // TODO: process input
621
+ switch (operation) {
622
+ case 'CREATE':
623
+ variables = {
624
+ input: arg
625
+ ? normalizeMutationInput(arg, modelDefinition, modelIntrospection)
626
+ : {},
627
+ };
628
+ break;
629
+ case 'UPDATE':
630
+ // readonly fields are not updated
631
+ variables = {
632
+ input: arg
633
+ ? Object.fromEntries(
634
+ Object.entries(
635
+ normalizeMutationInput(arg, modelDefinition, modelIntrospection)
636
+ ).filter(([fieldName]) => {
637
+ const { isReadOnly } = fields[fieldName];
638
+
639
+ return !isReadOnly;
640
+ })
641
+ )
642
+ : {},
643
+ };
644
+ break;
645
+ case 'READ':
646
+ case 'DELETE':
647
+ // only identifiers are sent
648
+ if (arg) {
649
+ variables = isCustomPrimaryKey
650
+ ? [primaryKeyFieldName, ...sortKeyFieldNames].reduce(
651
+ (acc: Record<string, any>, fieldName) => {
652
+ acc[fieldName] = arg[fieldName];
653
+
654
+ return acc;
655
+ },
656
+ {}
657
+ )
658
+ : { [primaryKeyFieldName]: arg[primaryKeyFieldName] };
659
+ }
660
+
661
+ if (operation === 'DELETE') {
662
+ variables = { input: variables };
663
+ }
664
+ break;
665
+ case 'LIST':
666
+ if (arg?.filter) {
667
+ variables.filter = arg.filter;
668
+ }
669
+ if (arg?.nextToken) {
670
+ variables.nextToken = arg.nextToken;
671
+ }
672
+ if (arg?.limit) {
673
+ variables.limit = arg.limit;
674
+ }
675
+ break;
676
+ case 'ONCREATE':
677
+ case 'ONUPDATE':
678
+ case 'ONDELETE':
679
+ if (arg?.filter) {
680
+ variables = { filter: arg.filter };
681
+ }
682
+ break;
683
+ case 'OBSERVE_QUERY':
684
+ throw new Error(
685
+ 'Internal error: Attempted to build variables for observeQuery. Please report this error.'
686
+ );
687
+ break;
688
+ default:
689
+ const exhaustiveCheck: never = operation;
690
+ throw new Error(`Unhandled operation case: ${exhaustiveCheck}`);
691
+ }
692
+
693
+ return variables;
694
+ }
695
+
696
+ /**
697
+ * Iterates over mutation input values and resolves any model inputs to their corresponding join fields/values
698
+ *
699
+ * @example
700
+ * ### Usage
701
+ * ```ts
702
+ * const result = normalizeMutationInput({ post: post }, model, modelDefinition);
703
+ * ```
704
+ * ### Result
705
+ * ```ts
706
+ * { postId: "abc123" }
707
+ * ```
708
+ *
709
+ */
710
+ export function normalizeMutationInput(
711
+ mutationInput: QueryArgs,
712
+ model: SchemaModel,
713
+ modelIntrospection: ModelIntrospectionSchema
714
+ ): QueryArgs {
715
+ const { fields } = model;
716
+
717
+ const normalized: Record<string, unknown> = {};
718
+
719
+ Object.entries(mutationInput).forEach(([inputFieldName, inputValue]) => {
720
+ const fieldType = fields[inputFieldName]?.type as ModelFieldType;
721
+ const relatedModelName = fieldType?.model;
722
+
723
+ if (relatedModelName) {
724
+ const association = fields[inputFieldName]?.association;
725
+ const relatedModelDef = modelIntrospection.models[relatedModelName];
726
+ const relatedModelPkInfo = relatedModelDef.primaryKeyInfo;
727
+
728
+ if (association?.connectionType === connectionType.HAS_ONE) {
729
+ const associationHasOne = association as AssociationHasOne;
730
+ associationHasOne.targetNames.forEach((targetName, idx) => {
731
+ const associatedFieldName = associationHasOne.associatedWith[idx];
732
+ normalized[targetName] = (inputValue as Record<string, unknown>)[
733
+ associatedFieldName
734
+ ];
735
+ });
736
+ }
737
+
738
+ if (association?.connectionType === connectionType.BELONGS_TO) {
739
+ const associationBelongsTo = association as AssociationBelongsTo;
740
+ associationBelongsTo.targetNames.forEach((targetName, idx) => {
741
+ if (idx === 0) {
742
+ const associatedFieldName = relatedModelPkInfo.primaryKeyFieldName;
743
+ normalized[targetName] = (inputValue as Record<string, unknown>)[
744
+ associatedFieldName
745
+ ];
746
+ } else {
747
+ const associatedFieldName =
748
+ relatedModelPkInfo.sortKeyFieldNames[idx - 1];
749
+ normalized[targetName] = (inputValue as Record<string, unknown>)[
750
+ associatedFieldName
751
+ ];
752
+ }
753
+ });
754
+ }
755
+ } else {
756
+ normalized[inputFieldName] = inputValue;
757
+ }
758
+ });
759
+
760
+ return normalized;
761
+ }
762
+
763
+ /**
764
+ * Produces a parameter object that can contains auth mode/token overrides
765
+ * only if present in either `options` (first) or configured on the `client`
766
+ * as a fallback.
767
+ *
768
+ * @param client Configured client from `generateClient`
769
+ * @param options Args/Options obect from call site.
770
+ * @returns
771
+ */
772
+ export function authModeParams(
773
+ client: ClientWithModels,
774
+ options: AuthModeParams = {}
775
+ ): AuthModeParams {
776
+ return {
777
+ authMode: options.authMode || client[__authMode],
778
+ authToken: options.authToken || client[__authToken],
779
+ };
780
+ }