@aws-amplify/data-schema 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/data-schema",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,8 @@
1
- import type { CustomOperationParamShape } from '../../CustomOperation';
2
- import type { BaseModelField } from '../../ModelField';
1
+ import type {
2
+ CustomOperationParamShape,
3
+ UltimateFunctionHandlerAsyncType,
4
+ } from '../../CustomOperation';
5
+ import type { BaseModelField, ModelFieldType } from '../../ModelField';
3
6
  import type { RefType } from '../../RefType';
4
7
  import type { ResolveFieldRequirements } from '../../MappedTypes/ResolveFieldProperties';
5
8
  import type { AppSyncResolverHandler } from 'aws-lambda';
@@ -36,7 +39,13 @@ export interface ClientCustomOperation<
36
39
  */
37
40
  functionHandler: AppSyncResolverHandler<
38
41
  CustomOpArguments<Op>,
39
- LambdaReturnType<CustomOpReturnType<Op, RefBag>>
42
+ // If the final handler is an async function, the Schema['fieldname']['functionhandler']
43
+ // should have a return type of `void`. This only applies to `functionHandler` and not
44
+ // `returnType` because `returnType` determines the type returned by the mutation / query
45
+ // in the data client.
46
+ Op['handlers'] extends UltimateFunctionHandlerAsyncType
47
+ ? void
48
+ : LambdaReturnType<CustomOpReturnType<Op, RefBag>>
40
49
  >;
41
50
 
42
51
  /**
@@ -100,6 +109,26 @@ type Normalize<
100
109
  ? Exclude<RT, null | undefined>
101
110
  : RT;
102
111
 
112
+ type _EventInvocationResponseBag = {
113
+ EventInvocationResponse: {
114
+ __entityType: 'customType';
115
+ type: '';
116
+ data: {
117
+ fields: {
118
+ success: {
119
+ data: {
120
+ fieldType: ModelFieldType.Boolean;
121
+ required: true;
122
+ array: false;
123
+ arrayRequired: false;
124
+ };
125
+ };
126
+ };
127
+ type: 'customType';
128
+ };
129
+ };
130
+ };
131
+
103
132
  /**
104
133
  * Computes the return type from the `returnType` of a custom operation shape.
105
134
  *
@@ -1,6 +1,6 @@
1
1
  import { SetTypeSubArg } from '@aws-amplify/data-schema-types';
2
2
  import { Brand, brand } from './util';
3
- import { InternalField, type BaseModelField } from './ModelField';
3
+ import { InternalField, ModelField, type BaseModelField } from './ModelField';
4
4
  import {
5
5
  AllowModifierForCustomOperation,
6
6
  Authorization,
@@ -10,10 +10,12 @@ import { RefType, InternalRef } from './RefType';
10
10
  import { EnumType } from './EnumType';
11
11
  import { CustomType } from './CustomType';
12
12
  import type {
13
+ AsyncFunctionHandler,
13
14
  CustomHandler,
14
15
  FunctionHandler,
15
16
  HandlerType as Handler,
16
17
  } from './Handler';
18
+ import { brandSymbol } from './util/Brand';
17
19
  import { AiModel, InferenceConfiguration } from './ai/ModelType';
18
20
 
19
21
  const queryBrand = 'queryCustomOperation';
@@ -28,14 +30,31 @@ type CustomOperationBrand =
28
30
  | typeof generationBrand;
29
31
 
30
32
  type CustomArguments = Record<string, BaseModelField | EnumType>;
31
-
32
33
  type SubscriptionSource = RefType<any, any>;
33
34
  type InternalSubscriptionSource = InternalRef;
34
-
35
35
  type CustomReturnType = RefType<any> | CustomType<any>;
36
36
  type InternalCustomArguments = Record<string, InternalField>;
37
37
  type InternalCustomReturnType = InternalRef;
38
- type HandlerInputType = FunctionHandler[] | CustomHandler[] | Handler;
38
+ type HandlerInputType =
39
+ | FunctionHandler[]
40
+ | CustomHandler[]
41
+ | AsyncFunctionHandler[]
42
+ | HeterogeneousFunctionHandlerWithLastAsync
43
+ | HeterogeneousFunctionHandlerType
44
+ | Handler;
45
+ type HeterogeneousFunctionHandlerType = (
46
+ | FunctionHandler
47
+ | AsyncFunctionHandler
48
+ )[];
49
+ type HeterogeneousFunctionHandlerWithLastAsync = [
50
+ ...HeterogeneousFunctionHandlerType,
51
+ AsyncFunctionHandler,
52
+ ];
53
+
54
+ export type UltimateFunctionHandlerAsyncType =
55
+ | AsyncFunctionHandler
56
+ | AsyncFunctionHandler[]
57
+ | HeterogeneousFunctionHandlerWithLastAsync;
39
58
 
40
59
  export const CustomOperationNames = [
41
60
  'Query',
@@ -104,7 +123,14 @@ export type CustomOperation<
104
123
  >;
105
124
  handler<H extends HandlerInputType>(
106
125
  handlers: H,
107
- ): CustomOperation<T, K | 'handler', B>;
126
+ ): [H] extends [UltimateFunctionHandlerAsyncType]
127
+ ? CustomOperation<
128
+ AsyncFunctionCustomOperation<T>,
129
+ K | 'handler' | 'returns',
130
+ B
131
+ >
132
+ : CustomOperation<T, K | 'handler', B>;
133
+
108
134
  for<Source extends SubscriptionSource>(
109
135
  source: Source | Source[],
110
136
  ): CustomOperation<
@@ -183,6 +209,10 @@ function _custom<
183
209
  ? handlers
184
210
  : ([handlers] as Handler[]);
185
211
 
212
+ if (lastHandlerIsAsyncFunction(handlers)) {
213
+ data.returnType = eventInvocationResponse;
214
+ }
215
+
186
216
  return this;
187
217
  },
188
218
  for(source: SubscriptionSource | SubscriptionSource[]) {
@@ -307,6 +337,39 @@ export function subscription(): CustomOperation<
307
337
  return _custom('Subscription', subscriptionBrand);
308
338
  }
309
339
 
340
+ // #region async Lambda function related types
341
+ type AsyncFunctionCustomOperation<T extends CustomOperationParamShape> =
342
+ SetTypeSubArg<
343
+ SetTypeSubArg<T, 'returnType', EventInvocationResponseCustomType>,
344
+ 'handlers',
345
+ AsyncFunctionHandler
346
+ >;
347
+
348
+ type EventInvocationResponseCustomType = CustomType<{
349
+ fields: {
350
+ success: ModelField<boolean, 'required', undefined>;
351
+ };
352
+ }>;
353
+
354
+ const eventInvocationResponse = {
355
+ data: {
356
+ type: 'ref',
357
+ link: 'EventInvocationResponse',
358
+ valueRequired: false,
359
+ array: false,
360
+ arrayRequired: false,
361
+ mutationOperations: [],
362
+ authorization: [],
363
+ },
364
+ };
365
+
366
+ function lastHandlerIsAsyncFunction(handlers: HandlerInputType): boolean {
367
+ const lastHandlerBrandSymbol = Array.isArray(handlers)
368
+ ? handlers[handlers.length - 1][brandSymbol]
369
+ : handlers[brandSymbol];
370
+ return lastHandlerBrandSymbol === 'asyncFunctionHandler';
371
+ }
372
+ // #endregion async Lambda function related types
310
373
  export interface GenerationInput {
311
374
  aiModel: AiModel;
312
375
  systemPrompt: string;
package/src/Handler.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import type { DefineFunction } from '@aws-amplify/data-schema-types';
2
2
  import { Brand, brand } from './util';
3
+ import type { brandSymbol } from './util/Brand.js';
3
4
  import { RefType } from './RefType';
4
5
 
5
6
  export type HandlerType =
6
7
  | InlineSqlHandler
7
8
  | SqlReferenceHandler
8
9
  | CustomHandler
9
- | FunctionHandler;
10
+ | FunctionHandler
11
+ | AsyncFunctionHandler;
10
12
 
11
13
  const dataSymbol = Symbol('Data');
12
14
 
@@ -18,7 +20,8 @@ type AllHandlers =
18
20
  | InlineSqlHandler
19
21
  | SqlReferenceHandler
20
22
  | CustomHandler
21
- | FunctionHandler;
23
+ | FunctionHandler
24
+ | AsyncFunctionHandler;
22
25
 
23
26
  export function getHandlerData<H extends AllHandlers>(
24
27
  handler: H,
@@ -30,9 +33,10 @@ export function getHandlerData<H extends AllHandlers>(
30
33
 
31
34
  const inlineSqlBrand = 'inlineSql';
32
35
 
33
- export type InlineSqlHandler = { [dataSymbol]: string } & Brand<
34
- typeof inlineSqlBrand
35
- >;
36
+ export type InlineSqlHandler = {
37
+ [dataSymbol]: string;
38
+ [brandSymbol]: typeof inlineSqlBrand;
39
+ };
36
40
 
37
41
  function inlineSql(sql: string): InlineSqlHandler {
38
42
  return { [dataSymbol]: sql, ...buildHandler(inlineSqlBrand) };
@@ -51,7 +55,8 @@ export type SqlReferenceHandlerData = {
51
55
 
52
56
  export type SqlReferenceHandler = {
53
57
  [dataSymbol]: SqlReferenceHandlerData;
54
- } & Brand<typeof sqlReferenceBrand>;
58
+ [brandSymbol]: typeof sqlReferenceBrand;
59
+ };
55
60
 
56
61
  function sqlReference(sqlFilePath: string): SqlReferenceHandler {
57
62
  // used to determine caller directory in order to resolve relative path downstream
@@ -88,12 +93,13 @@ export type CustomHandlerData = CustomHandlerInput & {
88
93
 
89
94
  const customHandlerBrand = 'customHandler';
90
95
 
91
- export type CustomHandler = { [dataSymbol]: CustomHandlerData } & Brand<
92
- typeof customHandlerBrand
93
- >;
96
+ export type CustomHandler = {
97
+ [dataSymbol]: CustomHandlerData;
98
+ [brandSymbol]: typeof customHandlerBrand;
99
+ };
94
100
 
95
101
  /**
96
- * Use a custom JavaScript resolver to handle a query, mutation, or subscription.
102
+ * Use a custom JavaScript resolver to handle a query, mutation, or subscription.
97
103
  * @see {@link https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/#step-2---configure-custom-business-logic-handler-code}
98
104
  * @param customHandler `{ entry: "path-to-javascript-resolver-file.js", dataSource: "Data Source name added via "backend.data.add*DataSoruce(...)"}`
99
105
  * @returns A JavaScript resolver attached to the query, mutation, or subscription.
@@ -107,7 +113,7 @@ export type CustomHandler = { [dataSymbol]: CustomHandlerData } & Brand<
107
113
  * allow.owner(),
108
114
  * allow.authenticated().to(['read'])
109
115
  * ]),
110
- *
116
+ *
111
117
  * likePost: a
112
118
  * .mutation()
113
119
  * .arguments({ postId: a.id() })
@@ -131,17 +137,63 @@ function custom(customHandler: CustomHandlerInput): CustomHandler {
131
137
  //#endregion
132
138
 
133
139
  //#region handler.function
134
-
135
- export type FunctionHandlerData = DefineFunction | string;
140
+ export type FunctionHandlerInput = DefineFunction | string;
141
+ export type FunctionHandlerData = {
142
+ handler: FunctionHandlerInput;
143
+ invocationType: 'RequestResponse' | 'Event';
144
+ };
136
145
 
137
146
  const functionHandlerBrand = 'functionHandler';
138
147
 
139
148
  export type FunctionHandler = {
140
149
  [dataSymbol]: FunctionHandlerData;
141
- } & Brand<typeof functionHandlerBrand>;
150
+ /**
151
+ * Use `async()` to have this function handler invoked asynchronously.
152
+ * If an `async()` function is only handler or the final handler in a pipeline, the return type of this
153
+ * custom query / mutation is `{ success: boolean }`.
154
+ * @example
155
+ * import {
156
+ * type ClientSchema,
157
+ * a,
158
+ * defineData,
159
+ * defineFunction // 1.Import "defineFunction" to create new functions
160
+ * } from '@aws-amplify/backend';
161
+ *
162
+ * // 2. define a function
163
+ * const echoHandler = defineFunction({
164
+ * entry: './echo-handler/handler.ts'
165
+ * })
166
+ *
167
+ * const schema = a.schema({
168
+ * EchoResponse: a.customType({
169
+ * content: a.string(),
170
+ * executionDuration: a.float()
171
+ * }),
172
+ *
173
+ * echo: a
174
+ * .query()
175
+ * .arguments({ content: a.string() })
176
+ * .returns(a.ref('EchoResponse'))
177
+ * .authorization(allow => [allow.publicApiKey()])
178
+ * // 3. set the function has the handler
179
+ * .handler(a.handler.function(echoHandler).async())
180
+ *
181
+ * @see {@link https://docs.amplify.aws/react/build-a-backend/data/INSERT_PATH_WHEN_READY}
182
+ * @returns A function handler for query / mutation that is asynchronously invoked.
183
+ */
184
+ async(): AsyncFunctionHandler;
185
+ [brandSymbol]: typeof functionHandlerBrand;
186
+ };
187
+
188
+ const asyncFunctionHandlerBrand = 'asyncFunctionHandler';
189
+
190
+ export type AsyncFunctionHandler = {
191
+ [dataSymbol]: FunctionHandlerData;
192
+ [brandSymbol]: typeof asyncFunctionHandlerBrand;
193
+ };
142
194
 
143
195
  /**
144
- * Use a function created via `defineFunction` to handle the custom query/mutation/subscription. In your function handler,
196
+ * Use a function created via `defineFunction` to handle the custom query/mutation/subscription. In your function handler,
145
197
  * you can use the `Schema["YOUR_QUERY_OR_MUTATION_NAME"]["functionHandler"]` utility type to type the handler function.
146
198
  * @example
147
199
  * import {
@@ -150,18 +202,18 @@ export type FunctionHandler = {
150
202
  * defineData,
151
203
  * defineFunction // 1.Import "defineFunction" to create new functions
152
204
  * } from '@aws-amplify/backend';
153
- *
205
+ *
154
206
  * // 2. define a function
155
207
  * const echoHandler = defineFunction({
156
208
  * entry: './echo-handler/handler.ts'
157
209
  * })
158
- *
210
+ *
159
211
  * const schema = a.schema({
160
212
  * EchoResponse: a.customType({
161
213
  * content: a.string(),
162
214
  * executionDuration: a.float()
163
215
  * }),
164
- *
216
+ *
165
217
  * echo: a
166
218
  * .query()
167
219
  * .arguments({ content: a.string() })
@@ -171,12 +223,31 @@ export type FunctionHandler = {
171
223
  * .handler(a.handler.function(echoHandler))
172
224
  * });
173
225
  * @see {@link https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/}
174
- * @param fn A function created via `defineFunction`. Alternatively, you can pass in a "string" of the function name and pass
226
+ * @param fn A function created via `defineFunction`. Alternatively, you can pass in a "string" of the function name and pass
175
227
  * in a corresponding value into the `functionMap` property of defineData.
176
228
  * @returns A handler for the query / mutation / subscription
177
229
  */
178
- function fcn(fn: FunctionHandlerData): FunctionHandler {
179
- return { [dataSymbol]: fn, ...buildHandler(functionHandlerBrand) };
230
+ function fcn(fn: FunctionHandlerInput): FunctionHandler {
231
+ return {
232
+ [dataSymbol]: {
233
+ handler: fn,
234
+ invocationType: 'RequestResponse',
235
+ },
236
+ async() {
237
+ return _async(this);
238
+ },
239
+ ...buildHandler(functionHandlerBrand),
240
+ };
241
+ }
242
+
243
+ function _async(fnHandler: FunctionHandler): AsyncFunctionHandler {
244
+ return {
245
+ [dataSymbol]: {
246
+ handler: fnHandler[dataSymbol].handler,
247
+ invocationType: 'Event',
248
+ },
249
+ ...buildHandler(asyncFunctionHandlerBrand),
250
+ };
180
251
  }
181
252
 
182
253
  //#endregion
@@ -42,10 +42,12 @@ import {
42
42
  type HandlerType,
43
43
  type CustomHandler,
44
44
  type SqlReferenceHandler,
45
- type FunctionHandler,
45
+ FunctionHandler,
46
+ AsyncFunctionHandler
46
47
  } from './Handler';
47
48
  import * as os from 'os';
48
49
  import * as path from 'path';
50
+ import { brandSymbol } from './util/Brand';
49
51
  import {
50
52
  brandName as conversationBrandName,
51
53
  type InternalConversationType,
@@ -279,7 +281,7 @@ function enumFieldToGql(enumName: string, secondaryIndexes: string[] = []) {
279
281
  }
280
282
 
281
283
  function transformFunctionHandler(
282
- handlers: FunctionHandler[],
284
+ handlers: (FunctionHandler | AsyncFunctionHandler)[],
283
285
  functionFieldName: string,
284
286
  ): {
285
287
  gqlHandlerContent: string;
@@ -291,21 +293,25 @@ function transformFunctionHandler(
291
293
  handlers.forEach((handler, idx) => {
292
294
  const handlerData = getHandlerData(handler);
293
295
 
294
- if (typeof handlerData === 'string') {
295
- gqlHandlerContent += `@function(name: "${handlerData}") `;
296
- } else if (typeof handlerData.getInstance === 'function') {
296
+ if (typeof handlerData.handler === 'string') {
297
+ gqlHandlerContent += `@function(name: "${handlerData.handler}") `;
298
+ } else if (typeof handlerData.handler.getInstance === 'function') {
297
299
  const fnName = `Fn${capitalize(functionFieldName)}${idx === 0 ? '' : `${idx + 1}`}`;
298
-
299
- lambdaFunctionDefinition[fnName] = handlerData;
300
- gqlHandlerContent += `@function(name: "${fnName}") `;
301
- } else {
300
+ lambdaFunctionDefinition[fnName] = handlerData.handler;
301
+ const invocationTypeArg = handlerData.invocationType === 'Event' ? ', invocationType: Event)' : ')'
302
+ gqlHandlerContent += `@function(name: "${fnName}"${invocationTypeArg} `;
303
+ }
304
+ else {
302
305
  throw new Error(
303
306
  `Invalid value specified for ${functionFieldName} handler.function(). Expected: defineFunction or string.`,
304
307
  );
305
308
  }
306
309
  });
307
310
 
308
- return { gqlHandlerContent, lambdaFunctionDefinition };
311
+ return {
312
+ gqlHandlerContent,
313
+ lambdaFunctionDefinition,
314
+ };
309
315
  }
310
316
 
311
317
  type CustomTypeAuthRules =
@@ -1275,6 +1281,20 @@ const schemaPreprocessor = (
1275
1281
 
1276
1282
  const staticSchema = databaseType === 'sql';
1277
1283
 
1284
+ // If the schema contains a custom operation with an async lambda handler,
1285
+ // we need to add the EventInvocationResponse custom type to the schema.
1286
+ // This is done here so that:
1287
+ // - it only happens once per schema
1288
+ // - downstream validation based on `getRefTypeForSchema` finds the EventInvocationResponse type
1289
+ const containsAsyncLambdaCustomOperation = Object.entries(schema.data.types).find(([_, typeDef]) => {
1290
+ return isCustomOperation(typeDef)
1291
+ && finalHandlerIsAsyncFunctionHandler(typeDef.data.handlers);
1292
+ });
1293
+
1294
+ if (containsAsyncLambdaCustomOperation) {
1295
+ schema.data.types['EventInvocationResponse'] = eventInvocationResponseCustomType;
1296
+ }
1297
+
1278
1298
  const topLevelTypes = sortTopLevelTypes(Object.entries(schema.data.types));
1279
1299
 
1280
1300
  const { schemaAuth, functionSchemaAccess } = extractFunctionSchemaAccess(
@@ -1575,12 +1595,16 @@ function validateCustomOperations(
1575
1595
  }
1576
1596
 
1577
1597
  if (configuredHandlers.size > 1) {
1578
- const configuredHandlersStr = JSON.stringify(
1579
- Array.from(configuredHandlers),
1580
- );
1581
- throw new Error(
1582
- `Field handlers must be of the same type. ${typeName} has been configured with ${configuredHandlersStr}`,
1583
- );
1598
+ configuredHandlers.delete('asyncFunctionHandler');
1599
+ configuredHandlers.delete('functionHandler');
1600
+ if (configuredHandlers.size > 0) {
1601
+ const configuredHandlersStr = JSON.stringify(
1602
+ Array.from(configuredHandlers),
1603
+ );
1604
+ throw new Error(
1605
+ `Field handlers must be of the same type. ${typeName} has been configured with ${configuredHandlersStr}`,
1606
+ );
1607
+ }
1584
1608
  }
1585
1609
  }
1586
1610
 
@@ -1588,11 +1612,15 @@ function validateCustomOperations(
1588
1612
  typeDef.data.returnType === null &&
1589
1613
  (opType === 'Query' || opType === 'Mutation' || opType === 'Generation')
1590
1614
  ) {
1591
- const typeDescription =
1592
- opType === 'Generation' ? 'Generation Route' : `Custom ${opType}`;
1593
- throw new Error(
1594
- `Invalid ${typeDescription} definition. A ${typeDescription} must include a return type. ${typeName} has no return type specified.`,
1595
- );
1615
+ // TODO: There should be a more elegant and readable way to handle this check.
1616
+ // Maybe it's not even necessary anymore since we're the setting returnType in the handler() method.
1617
+ if (!handlers || handlers.length === 0 || handlers[handlers.length - 1][brandSymbol] !== 'asyncFunctionHandler') {
1618
+ const typeDescription =
1619
+ opType === 'Generation' ? 'Generation Route' : `Custom ${opType}`;
1620
+ throw new Error(
1621
+ `Invalid ${typeDescription} definition. A ${typeDescription} must include a return type. ${typeName} has no return type specified.`,
1622
+ );
1623
+ }
1596
1624
  }
1597
1625
 
1598
1626
  if (opType !== 'Subscription' && subscriptionSource.length > 0) {
@@ -1677,10 +1705,16 @@ const isCustomHandler = (
1677
1705
 
1678
1706
  const isFunctionHandler = (
1679
1707
  handler: HandlerType[] | null,
1680
- ): handler is FunctionHandler[] => {
1681
- return Array.isArray(handler) && getBrand(handler[0]) === 'functionHandler';
1708
+ ): handler is (FunctionHandler | AsyncFunctionHandler)[] => {
1709
+ return Array.isArray(handler) && ['functionHandler', 'asyncFunctionHandler'].includes(getBrand(handler[0]));
1682
1710
  };
1683
1711
 
1712
+ const finalHandlerIsAsyncFunctionHandler = (
1713
+ handler: HandlerType[] | null,
1714
+ ): handler is AsyncFunctionHandler[] => {
1715
+ return Array.isArray(handler) && getBrand(handler[handler.length - 1]) === 'asyncFunctionHandler';
1716
+ }
1717
+
1684
1718
  const normalizeDataSourceName = (
1685
1719
  dataSource: undefined | string | RefType<any, any, any>,
1686
1720
  ): string => {
@@ -1762,6 +1796,22 @@ const handleCustom = (
1762
1796
  return jsFn;
1763
1797
  };
1764
1798
 
1799
+ const eventInvocationResponseCustomType = {
1800
+ data: {
1801
+ fields: {
1802
+ success: {
1803
+ data: {
1804
+ fieldType: ModelFieldType.Boolean,
1805
+ required: true,
1806
+ array: false,
1807
+ arrayRequired: false,
1808
+ }
1809
+ }
1810
+ },
1811
+ type: 'customType'
1812
+ }
1813
+ };
1814
+
1765
1815
  function transformCustomOperations(
1766
1816
  typeDef: InternalCustom,
1767
1817
  typeName: string,
@@ -17,6 +17,7 @@ import {
17
17
  generateGraphQLDocument,
18
18
  getCustomHeaders,
19
19
  initializeModel,
20
+ flattenItems,
20
21
  } from '../APIClient';
21
22
 
22
23
  export function subscriptionFactory(
@@ -59,18 +60,25 @@ export function subscriptionFactory(
59
60
  return observable.pipe(
60
61
  map((value) => {
61
62
  const [key] = Object.keys(value.data);
62
- // Will need flattening here if/when custom selection set support is added:
63
63
  const data = (value.data as any)[key];
64
- const [initialized] = initializeModel(
65
- client,
66
- name,
67
- [data],
68
- modelIntrospection,
69
- auth.authMode,
70
- auth.authToken,
71
- );
64
+ const flattenedResult = flattenItems(modelIntrospection, name, data);
72
65
 
73
- return initialized;
66
+ if (flattenedResult === null) {
67
+ return null;
68
+ } else if (args?.selectionSet) {
69
+ return flattenedResult;
70
+ } else {
71
+ const [initialized] = initializeModel(
72
+ client,
73
+ name,
74
+ [flattenedResult],
75
+ modelIntrospection,
76
+ auth.authMode,
77
+ auth.authToken,
78
+ );
79
+
80
+ return initialized;
81
+ }
74
82
  }),
75
83
  );
76
84
  };