@aws-amplify/data-schema 0.0.0-20240412222732

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 (92) hide show
  1. package/LICENSE +175 -0
  2. package/NOTICE +1 -0
  3. package/internals/package.json +6 -0
  4. package/lib-esm/index.d.ts +5 -0
  5. package/lib-esm/index.d.ts.map +1 -0
  6. package/lib-esm/index.js +28 -0
  7. package/lib-esm/src/Authorization.d.ts +287 -0
  8. package/lib-esm/src/Authorization.d.ts.map +1 -0
  9. package/lib-esm/src/Authorization.js +332 -0
  10. package/lib-esm/src/ClientSchema.d.ts +54 -0
  11. package/lib-esm/src/ClientSchema.d.ts.map +1 -0
  12. package/lib-esm/src/ClientSchema.js +2 -0
  13. package/lib-esm/src/CombineSchema.d.ts +19 -0
  14. package/lib-esm/src/CombineSchema.d.ts.map +1 -0
  15. package/lib-esm/src/CombineSchema.js +40 -0
  16. package/lib-esm/src/CustomOperation.d.ts +98 -0
  17. package/lib-esm/src/CustomOperation.d.ts.map +1 -0
  18. package/lib-esm/src/CustomOperation.js +67 -0
  19. package/lib-esm/src/CustomType.d.ts +36 -0
  20. package/lib-esm/src/CustomType.d.ts.map +1 -0
  21. package/lib-esm/src/CustomType.js +14 -0
  22. package/lib-esm/src/EnumType.d.ts +16 -0
  23. package/lib-esm/src/EnumType.d.ts.map +1 -0
  24. package/lib-esm/src/EnumType.js +17 -0
  25. package/lib-esm/src/Handler.d.ts +58 -0
  26. package/lib-esm/src/Handler.d.ts.map +1 -0
  27. package/lib-esm/src/Handler.js +48 -0
  28. package/lib-esm/src/MappedTypes/CustomOperations.d.ts +127 -0
  29. package/lib-esm/src/MappedTypes/CustomOperations.d.ts.map +1 -0
  30. package/lib-esm/src/MappedTypes/CustomOperations.js +2 -0
  31. package/lib-esm/src/MappedTypes/ExtractNonModelTypes.d.ts +74 -0
  32. package/lib-esm/src/MappedTypes/ExtractNonModelTypes.d.ts.map +1 -0
  33. package/lib-esm/src/MappedTypes/ExtractNonModelTypes.js +2 -0
  34. package/lib-esm/src/MappedTypes/ForeignKeys.d.ts +84 -0
  35. package/lib-esm/src/MappedTypes/ForeignKeys.d.ts.map +1 -0
  36. package/lib-esm/src/MappedTypes/ForeignKeys.js +2 -0
  37. package/lib-esm/src/MappedTypes/ImplicitFieldInjector.d.ts +38 -0
  38. package/lib-esm/src/MappedTypes/ImplicitFieldInjector.d.ts.map +1 -0
  39. package/lib-esm/src/MappedTypes/ImplicitFieldInjector.js +2 -0
  40. package/lib-esm/src/MappedTypes/MapSecondaryIndexes.d.ts +61 -0
  41. package/lib-esm/src/MappedTypes/MapSecondaryIndexes.d.ts.map +1 -0
  42. package/lib-esm/src/MappedTypes/MapSecondaryIndexes.js +2 -0
  43. package/lib-esm/src/MappedTypes/ModelMetadata.d.ts +26 -0
  44. package/lib-esm/src/MappedTypes/ModelMetadata.d.ts.map +1 -0
  45. package/lib-esm/src/MappedTypes/ModelMetadata.js +2 -0
  46. package/lib-esm/src/MappedTypes/ResolveFieldProperties.d.ts +70 -0
  47. package/lib-esm/src/MappedTypes/ResolveFieldProperties.d.ts.map +1 -0
  48. package/lib-esm/src/MappedTypes/ResolveFieldProperties.js +2 -0
  49. package/lib-esm/src/MappedTypes/ResolveSchema.d.ts +65 -0
  50. package/lib-esm/src/MappedTypes/ResolveSchema.d.ts.map +1 -0
  51. package/lib-esm/src/MappedTypes/ResolveSchema.js +2 -0
  52. package/lib-esm/src/ModelField.d.ts +162 -0
  53. package/lib-esm/src/ModelField.d.ts.map +1 -0
  54. package/lib-esm/src/ModelField.js +209 -0
  55. package/lib-esm/src/ModelIndex.d.ts +19 -0
  56. package/lib-esm/src/ModelIndex.d.ts.map +1 -0
  57. package/lib-esm/src/ModelIndex.js +33 -0
  58. package/lib-esm/src/ModelRelationalField.d.ts +119 -0
  59. package/lib-esm/src/ModelRelationalField.d.ts.map +1 -0
  60. package/lib-esm/src/ModelRelationalField.js +120 -0
  61. package/lib-esm/src/ModelSchema.d.ts +107 -0
  62. package/lib-esm/src/ModelSchema.d.ts.map +1 -0
  63. package/lib-esm/src/ModelSchema.js +162 -0
  64. package/lib-esm/src/ModelType.d.ts +85 -0
  65. package/lib-esm/src/ModelType.d.ts.map +1 -0
  66. package/lib-esm/src/ModelType.js +64 -0
  67. package/lib-esm/src/RefType.d.ts +59 -0
  68. package/lib-esm/src/RefType.d.ts.map +1 -0
  69. package/lib-esm/src/RefType.js +47 -0
  70. package/lib-esm/src/SchemaProcessor.d.ts +11 -0
  71. package/lib-esm/src/SchemaProcessor.d.ts.map +1 -0
  72. package/lib-esm/src/SchemaProcessor.js +1106 -0
  73. package/lib-esm/src/index.d.ts +13 -0
  74. package/lib-esm/src/index.d.ts.map +1 -0
  75. package/lib-esm/src/index.js +43 -0
  76. package/lib-esm/src/internals/index.d.ts +2 -0
  77. package/lib-esm/src/internals/index.d.ts.map +1 -0
  78. package/lib-esm/src/internals/index.js +5 -0
  79. package/lib-esm/src/util/Brand.d.ts +36 -0
  80. package/lib-esm/src/util/Brand.d.ts.map +1 -0
  81. package/lib-esm/src/util/Brand.js +32 -0
  82. package/lib-esm/src/util/IndexLimit.d.ts +8 -0
  83. package/lib-esm/src/util/IndexLimit.d.ts.map +1 -0
  84. package/lib-esm/src/util/IndexLimit.js +2 -0
  85. package/lib-esm/src/util/SpreadTuple.d.ts +7 -0
  86. package/lib-esm/src/util/SpreadTuple.d.ts.map +1 -0
  87. package/lib-esm/src/util/SpreadTuple.js +2 -0
  88. package/lib-esm/src/util/index.d.ts +4 -0
  89. package/lib-esm/src/util/index.d.ts.map +1 -0
  90. package/lib-esm/src/util/index.js +6 -0
  91. package/lib-esm/tsconfig.tsbuildinfo +1 -0
  92. package/package.json +62 -0
@@ -0,0 +1,1106 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.processSchema = void 0;
27
+ const ModelField_1 = require("./ModelField");
28
+ const ModelRelationalField_1 = require("./ModelRelationalField");
29
+ const Authorization_1 = require("./Authorization");
30
+ const CustomOperation_1 = require("./CustomOperation");
31
+ const util_1 = require("./util");
32
+ const Handler_1 = require("./Handler");
33
+ const os = __importStar(require("os"));
34
+ const path = __importStar(require("path"));
35
+ function isInternalModel(model) {
36
+ if (model.data &&
37
+ !isCustomType(model) &&
38
+ !isCustomOperation(model)) {
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ function isEnumType(data) {
44
+ if (data?.type === 'enum') {
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ function isCustomType(data) {
50
+ if (data?.data?.type === 'customType') {
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ function isCustomOperation(type) {
56
+ if (CustomOperation_1.CustomOperationNames.includes(type?.data?.typeName)) {
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ function isModelFieldDef(data) {
62
+ return data?.fieldType === 'model';
63
+ }
64
+ function isScalarFieldDef(data) {
65
+ return data?.fieldType !== 'model';
66
+ }
67
+ function isRefFieldDef(data) {
68
+ return data?.type === 'ref';
69
+ }
70
+ function isModelField(field) {
71
+ return isModelFieldDef(field?.data);
72
+ }
73
+ function dataSourceIsRef(dataSource) {
74
+ return (typeof dataSource !== 'string' &&
75
+ dataSource?.data &&
76
+ dataSource.data.type === 'ref');
77
+ }
78
+ function isScalarField(field) {
79
+ return isScalarFieldDef(field?.data);
80
+ }
81
+ function isRefField(field) {
82
+ return isRefFieldDef(field?.data);
83
+ }
84
+ function scalarFieldToGql(fieldDef, identifier, secondaryIndexes = []) {
85
+ const { fieldType, required, array, arrayRequired, default: _default, } = fieldDef;
86
+ let field = fieldType;
87
+ if (identifier !== undefined) {
88
+ field += '!';
89
+ if (identifier.length > 1) {
90
+ const [_pk, ...sk] = identifier;
91
+ field += ` @primaryKey(sortKeyFields: [${sk
92
+ .map((sk) => `"${sk}"`)
93
+ .join(', ')}])`;
94
+ }
95
+ else {
96
+ field += ' @primaryKey';
97
+ }
98
+ for (const index of secondaryIndexes) {
99
+ field += ` ${index}`;
100
+ }
101
+ return field;
102
+ }
103
+ if (required === true) {
104
+ field += '!';
105
+ }
106
+ if (array) {
107
+ field = `[${field}]`;
108
+ if (arrayRequired === true) {
109
+ field += '!';
110
+ }
111
+ }
112
+ if (_default !== undefined) {
113
+ field += ` @default(value: "${_default?.toString()}")`;
114
+ }
115
+ for (const index of secondaryIndexes) {
116
+ field += ` ${index}`;
117
+ }
118
+ return field;
119
+ }
120
+ function modelFieldToGql(fieldDef) {
121
+ const { type, relatedModel, array, relationName, valueRequired, arrayRequired, references, } = fieldDef;
122
+ let field = relatedModel;
123
+ if (valueRequired === true) {
124
+ field += '!';
125
+ }
126
+ if (array) {
127
+ field = `[${field}]`;
128
+ }
129
+ if (arrayRequired === true) {
130
+ field += '!';
131
+ }
132
+ if (references && Array.isArray(references) && references.length > 0) {
133
+ field += ` @${type}(references: [${references.map((s) => `"${String(s)}"`)}])`;
134
+ }
135
+ else {
136
+ field += ` @${type}`;
137
+ }
138
+ // TODO: accept other relationship options e.g. `fields`
139
+ if (type === 'manyToMany') {
140
+ field += `(relationName: "${relationName}")`;
141
+ }
142
+ return field;
143
+ }
144
+ function refFieldToGql(fieldDef) {
145
+ const { link, valueRequired, array, arrayRequired } = fieldDef;
146
+ let field = link;
147
+ if (valueRequired === true) {
148
+ field += '!';
149
+ }
150
+ if (array === true) {
151
+ field = `[${field}]`;
152
+ }
153
+ if (arrayRequired === true) {
154
+ field += '!';
155
+ }
156
+ return field;
157
+ }
158
+ function transformFunctionHandler(handlers, functionFieldName) {
159
+ let gqlHandlerContent = '';
160
+ const lambdaFunctionDefinition = {};
161
+ handlers.forEach((handler, idx) => {
162
+ const handlerData = (0, Handler_1.getHandlerData)(handler);
163
+ if (typeof handlerData === 'string') {
164
+ gqlHandlerContent += `@function(name: "${handlerData}") `;
165
+ }
166
+ else if (typeof handlerData.getInstance === 'function') {
167
+ const fnName = `Fn${capitalize(functionFieldName)}${idx === 0 ? '' : `${idx + 1}`}`;
168
+ lambdaFunctionDefinition[fnName] = handlerData;
169
+ gqlHandlerContent += `@function(name: "${fnName}") `;
170
+ }
171
+ else {
172
+ throw new Error(`Invalid value specified for ${functionFieldName} handler.function(). Expected: defineFunction or string.`);
173
+ }
174
+ });
175
+ return { gqlHandlerContent, lambdaFunctionDefinition };
176
+ }
177
+ function customOperationToGql(typeName, typeDef, authorization, isCustom = false, databaseType, getRefType) {
178
+ const { arguments: fieldArgs, typeName: opType, returnType, functionRef, handlers, subscriptionSource, } = typeDef.data;
179
+ let callSignature = typeName;
180
+ const implicitModels = [];
181
+ const { authString } = isCustom
182
+ ? calculateCustomAuth(authorization)
183
+ : calculateAuth(authorization);
184
+ /**
185
+ *
186
+ * @param returnType The return type from the `data` field of a customer operation.
187
+ * @param refererTypeName The type the refers {@link returnType} by `a.ref()`.
188
+ * @param shouldAddCustomTypeToImplicitModels A flag indicates wether it should push
189
+ * the return type resolved CustomType to the `implicitModels` list.
190
+ * @returns
191
+ */
192
+ const resolveReturnTypeNameFromReturnType = (returnType, { refererTypeName, shouldAddCustomTypeToImplicitModels = true, }) => {
193
+ if (isRefField(returnType)) {
194
+ return refFieldToGql(returnType?.data);
195
+ }
196
+ else if (isCustomType(returnType)) {
197
+ const returnTypeName = `${capitalize(refererTypeName)}ReturnType`;
198
+ if (shouldAddCustomTypeToImplicitModels) {
199
+ implicitModels.push([returnTypeName, returnType]);
200
+ }
201
+ return returnTypeName;
202
+ }
203
+ else if (isScalarField(returnType)) {
204
+ return scalarFieldToGql(returnType?.data);
205
+ }
206
+ else {
207
+ throw new Error(`Unrecognized return type on ${typeName}`);
208
+ }
209
+ };
210
+ let returnTypeName;
211
+ if (opType === 'Subscription' && returnType === null) {
212
+ // up to this point, we've validated that each subscription resource resolves
213
+ // the same return type, so it's safe to use subscriptionSource[0] here.
214
+ const { type, def } = getRefType(subscriptionSource[0].data.link, typeName);
215
+ if (type === 'CustomOperation') {
216
+ returnTypeName = resolveReturnTypeNameFromReturnType(def.data.returnType, {
217
+ refererTypeName: subscriptionSource[0].data.link,
218
+ shouldAddCustomTypeToImplicitModels: false,
219
+ });
220
+ }
221
+ else {
222
+ returnTypeName = refFieldToGql(subscriptionSource[0].data);
223
+ }
224
+ }
225
+ else {
226
+ returnTypeName = resolveReturnTypeNameFromReturnType(returnType, {
227
+ refererTypeName: typeName,
228
+ });
229
+ }
230
+ if (Object.keys(fieldArgs).length > 0) {
231
+ const { gqlFields, models } = processFields(typeName, fieldArgs, {}, {});
232
+ callSignature += `(${gqlFields.join(', ')})`;
233
+ implicitModels.push(...models);
234
+ }
235
+ const handler = handlers && handlers[0];
236
+ const brand = handler && (0, util_1.getBrand)(handler);
237
+ let gqlHandlerContent = '';
238
+ let lambdaFunctionDefinition = {};
239
+ let customSqlDataSourceStrategy;
240
+ if (isFunctionHandler(handlers)) {
241
+ ({ gqlHandlerContent, lambdaFunctionDefinition } = transformFunctionHandler(handlers, typeName));
242
+ }
243
+ else if (functionRef) {
244
+ gqlHandlerContent = `@function(name: "${functionRef}") `;
245
+ }
246
+ else if (databaseType === 'sql' && handler && brand === 'inlineSql') {
247
+ gqlHandlerContent = `@sql(statement: ${escapeGraphQlString(String((0, Handler_1.getHandlerData)(handler)))}) `;
248
+ customSqlDataSourceStrategy = {
249
+ typeName: opType,
250
+ fieldName: typeName,
251
+ };
252
+ }
253
+ else if (isSqlReferenceHandler(handlers)) {
254
+ const handlerData = (0, Handler_1.getHandlerData)(handlers[0]);
255
+ const entry = resolveEntryPath(handlerData, 'Could not determine import path to construct absolute code path for sql reference handler. Consider using an absolute path instead.');
256
+ const reference = typeof entry === 'string' ? entry : entry.relativePath;
257
+ customSqlDataSourceStrategy = {
258
+ typeName: opType,
259
+ fieldName: typeName,
260
+ entry,
261
+ };
262
+ gqlHandlerContent = `@sql(reference: "${reference}") `;
263
+ }
264
+ if (opType === 'Subscription') {
265
+ const subscriptionSources = subscriptionSource
266
+ .flatMap((source) => {
267
+ const refTarget = source.data.link;
268
+ const { type } = getRefType(refTarget, typeName);
269
+ if (type === 'CustomOperation') {
270
+ return refTarget;
271
+ }
272
+ if (type === 'Model') {
273
+ return source.data.mutationOperations.map(
274
+ // capitalize explicitly in case customer used lowercase model name
275
+ (op) => `${op}${capitalize(refTarget)}`);
276
+ }
277
+ })
278
+ .join('", "');
279
+ gqlHandlerContent += `@aws_subscribe(mutations: ["${subscriptionSources}"]) `;
280
+ }
281
+ const gqlField = `${callSignature}: ${returnTypeName} ${gqlHandlerContent}${authString}`;
282
+ return {
283
+ gqlField,
284
+ models: implicitModels,
285
+ lambdaFunctionDefinition,
286
+ customSqlDataSourceStrategy,
287
+ };
288
+ }
289
+ /**
290
+ * Escape a string that will be used inside of a graphql string.
291
+ * @param str The input string to be escaped
292
+ * @returns The string with special charactars escaped
293
+ */
294
+ function escapeGraphQlString(str) {
295
+ return JSON.stringify(str);
296
+ }
297
+ /**
298
+ * Tests whether two ModelField definitions are in conflict.
299
+ *
300
+ * This is a shallow check intended to catch conflicts between defined fields
301
+ * and fields implied by authorization rules. Hence, it only compares type
302
+ * and plurality.
303
+ *
304
+ * @param left
305
+ * @param right
306
+ * @returns
307
+ */
308
+ function areConflicting(left, right) {
309
+ // These are the only props we care about for this comparison, because the others
310
+ // (required, arrayRequired, etc) are not specified on auth or FK directives.
311
+ const relevantProps = ['array', 'fieldType'];
312
+ for (const prop of relevantProps) {
313
+ if (left.data[prop] !== right.data[prop]) {
314
+ return true;
315
+ }
316
+ }
317
+ return false;
318
+ }
319
+ /**
320
+ * Merges one field defition object onto an existing one, performing
321
+ * validation (conflict detection) along the way.
322
+ *
323
+ * @param existing An existing field map
324
+ * @param additions A field map to merge in
325
+ */
326
+ function addFields(existing, additions) {
327
+ for (const [k, addition] of Object.entries(additions)) {
328
+ if (!existing[k]) {
329
+ existing[k] = addition;
330
+ }
331
+ else if (areConflicting(existing[k], addition)) {
332
+ throw new Error(`Field ${k} defined twice with conflicting definitions.`);
333
+ }
334
+ else {
335
+ // fields are defined on both sides, but match.
336
+ }
337
+ }
338
+ }
339
+ /**
340
+ * Validate that no implicit fields are used by the model definition
341
+ *
342
+ * @param existing An existing field map
343
+ * @param implicitFields A field map inferred from other schema usage
344
+ *
345
+ * @throws An error when an undefined field is used or when a field is used in a way that conflicts with its generated definition
346
+ */
347
+ function validateStaticFields(existing, implicitFields) {
348
+ if (implicitFields === undefined) {
349
+ return;
350
+ }
351
+ for (const [k, field] of Object.entries(implicitFields)) {
352
+ if (!existing[k]) {
353
+ throw new Error(`Field ${k} isn't defined.`);
354
+ }
355
+ else if (areConflicting(existing[k], field)) {
356
+ throw new Error(`Field ${k} defined twice with conflicting definitions.`);
357
+ }
358
+ }
359
+ }
360
+ /**
361
+ * Validate that no implicit fields conflict with explicitly defined fields.
362
+ *
363
+ * @param existing An existing field map
364
+ * @param implicitFields A field map inferred from other schema usage
365
+ *
366
+ * @throws An error when an undefined field is used or when a field is used in a way that conflicts with its generated definition
367
+ */
368
+ function validateImpliedFields(existing, implicitFields) {
369
+ if (implicitFields === undefined) {
370
+ return;
371
+ }
372
+ for (const [k, field] of Object.entries(implicitFields)) {
373
+ if (existing[k] && areConflicting(existing[k], field)) {
374
+ throw new Error(`Implicit field ${k} conflicts with the explicit field definition.`);
375
+ }
376
+ }
377
+ }
378
+ /**
379
+ * Produces a new field definition object from every field definition object
380
+ * given as an argument. Performs validation (conflict detection) as objects
381
+ * are merged together.
382
+ *
383
+ * @param fieldsObjects A list of field definition objects to merge.
384
+ * @returns
385
+ */
386
+ function mergeFieldObjects(...fieldsObjects) {
387
+ const result = {};
388
+ for (const fields of fieldsObjects) {
389
+ if (fields)
390
+ addFields(result, fields);
391
+ }
392
+ return result;
393
+ }
394
+ /**
395
+ * Throws if resource/lambda auth is configured at the model or field level
396
+ *
397
+ * @param authorization A list of authorization rules.
398
+ */
399
+ function validateAuth(authorization = []) {
400
+ for (const entry of authorization) {
401
+ if (ruleIsResourceAuth(entry)) {
402
+ throw new Error('Lambda resource authorization is only confiugrable at the schema level');
403
+ }
404
+ }
405
+ }
406
+ /**
407
+ * Given a list of authorization rules, produces a set of the implied owner and/or
408
+ * group fields, along with the associated graphql `@auth` string directive.
409
+ *
410
+ * This is intended to be called for each model and field to collect the implied
411
+ * fields and directives from that individual "item's" auth rules.
412
+ *
413
+ * The computed directives are intended to be appended to the graphql field definition.
414
+ *
415
+ * The computed fields will be used to confirm no conflicts between explicit field definitions
416
+ * and implicit auth fields.
417
+ *
418
+ * @param authorization A list of authorization rules.
419
+ * @returns
420
+ */
421
+ function calculateAuth(authorization) {
422
+ const authFields = {};
423
+ const rules = [];
424
+ for (const entry of authorization) {
425
+ const rule = (0, Authorization_1.accessData)(entry);
426
+ const ruleParts = [];
427
+ if (rule.strategy) {
428
+ ruleParts.push([`allow: ${rule.strategy}`]);
429
+ }
430
+ else {
431
+ return {
432
+ authFields,
433
+ authString: '',
434
+ };
435
+ }
436
+ if (rule.provider) {
437
+ ruleParts.push(`provider: ${rule.provider}`);
438
+ }
439
+ if (rule.operations) {
440
+ ruleParts.push(`operations: [${rule.operations.join(', ')}]`);
441
+ }
442
+ if (rule.groupOrOwnerField) {
443
+ // directive attribute, depending whether it's owner or group auth
444
+ if (rule.strategy === 'groups') {
445
+ // does this need to be escaped?
446
+ ruleParts.push(`groupsField: "${rule.groupOrOwnerField}"`);
447
+ }
448
+ else {
449
+ // does this need to be escaped?
450
+ ruleParts.push(`ownerField: "${rule.groupOrOwnerField}"`);
451
+ }
452
+ // model field dep, type of which depends on whether multiple owner/group
453
+ // is required.
454
+ if (rule.multiOwner) {
455
+ addFields(authFields, { [rule.groupOrOwnerField]: (0, ModelField_1.string)().array() });
456
+ }
457
+ else {
458
+ addFields(authFields, { [rule.groupOrOwnerField]: (0, ModelField_1.string)() });
459
+ }
460
+ }
461
+ if (rule.groups) {
462
+ // does `group` need to be escaped?
463
+ ruleParts.push(`groups: [${rule.groups.map((group) => `"${group}"`).join(', ')}]`);
464
+ }
465
+ // identityClaim
466
+ if (rule.identityClaim) {
467
+ // does this need to be escaped?
468
+ ruleParts.push(`identityClaim: "${rule.identityClaim}"`);
469
+ }
470
+ // groupClaim
471
+ if (rule.groupClaim) {
472
+ // does this need to be escaped?
473
+ ruleParts.push(`groupClaim: "${rule.groupClaim}"`);
474
+ }
475
+ rules.push(`{${ruleParts.join(', ')}}`);
476
+ }
477
+ const authString = rules.length > 0 ? `@auth(rules: [${rules.join(',\n ')}])` : '';
478
+ return { authString, authFields };
479
+ }
480
+ function validateCustomAuthRule(rule) {
481
+ if (rule.operations) {
482
+ throw new Error('.to() modifier is not supported for custom queries/mutations');
483
+ }
484
+ if (rule.groupOrOwnerField) {
485
+ throw new Error('Dynamic auth (owner or dynamic groups) is not supported for custom queries/mutations');
486
+ }
487
+ // identityClaim
488
+ if (rule.identityClaim) {
489
+ throw new Error('identityClaim attr is not supported with a.handler.custom');
490
+ }
491
+ // groupClaim
492
+ if (rule.groupClaim) {
493
+ throw new Error('groupClaim attr is not supported with a.handler.custom');
494
+ }
495
+ if (rule.groups && rule.provider === 'oidc') {
496
+ throw new Error('OIDC group auth is not supported with a.handler.custom');
497
+ }
498
+ }
499
+ function getCustomAuthProvider(rule) {
500
+ const strategyDict = {
501
+ public: {
502
+ default: '@aws_api_key',
503
+ apiKey: '@aws_api_key',
504
+ iam: '@aws_iam',
505
+ },
506
+ private: {
507
+ default: '@aws_cognito_user_pools',
508
+ userPools: '@aws_cognito_user_pools',
509
+ oidc: '@aws_oidc',
510
+ iam: '@aws_iam',
511
+ },
512
+ groups: {
513
+ default: '@aws_cognito_user_pools',
514
+ userPools: '@aws_cognito_user_pools',
515
+ },
516
+ custom: {
517
+ default: '@aws_lambda',
518
+ function: '@aws_lambda',
519
+ },
520
+ };
521
+ const stratProviders = strategyDict[rule.strategy];
522
+ if (stratProviders === undefined) {
523
+ throw new Error(`Unsupported auth strategy for custom handlers: ${rule.strategy}`);
524
+ }
525
+ const provider = rule.provider || 'default';
526
+ const stratProvider = stratProviders[provider];
527
+ if (stratProvider === undefined) {
528
+ throw new Error(`Unsupported provider for custom handlers: ${rule.provider}`);
529
+ }
530
+ return stratProvider;
531
+ }
532
+ function calculateCustomAuth(authorization) {
533
+ const rules = [];
534
+ for (const entry of authorization) {
535
+ const rule = (0, Authorization_1.accessData)(entry);
536
+ validateCustomAuthRule(rule);
537
+ const provider = getCustomAuthProvider(rule);
538
+ if (rule.groups) {
539
+ // example: (cognito_groups: ["Bloggers", "Readers"])
540
+ rules.push(`${provider}(cognito_groups: [${rule.groups
541
+ .map((group) => `"${group}"`)
542
+ .join(', ')}])`);
543
+ }
544
+ else {
545
+ rules.push(provider);
546
+ }
547
+ }
548
+ const authString = rules.join(' ');
549
+ return { authString };
550
+ }
551
+ function capitalize(s) {
552
+ return `${s[0].toUpperCase()}${s.slice(1)}`;
553
+ }
554
+ function uncapitalize(s) {
555
+ return `${s[0].toLowerCase()}${s.slice(1)}`;
556
+ }
557
+ function fkName(model, field, identifier) {
558
+ return `${uncapitalize(model)}${capitalize(field)}${capitalize(identifier)}`;
559
+ }
560
+ /**
561
+ * Returns all explicitly defined and implied fields from a model.
562
+ *
563
+ * @param schema The schema the model is part of. Necessary to derive implied FK's.
564
+ * @param model The model to extract fields from and derive fields for.
565
+ * @returns
566
+ */
567
+ const allImpliedFKs = (schema) => {
568
+ const fks = {};
569
+ function addFk({ onModel, asField, fieldDef, }) {
570
+ fks[onModel] = fks[onModel] || {};
571
+ fks[onModel][asField] = fieldDef;
572
+ }
573
+ // implied FK's
574
+ for (const [modelName, typeDef] of Object.entries(schema.data.types)) {
575
+ if (!isInternalModel(typeDef))
576
+ continue;
577
+ for (const [fieldName, fieldDef] of Object.entries(typeDef.data.fields)) {
578
+ if (!isModelField(fieldDef))
579
+ continue;
580
+ const relatedModel = schema.data.types[fieldDef.data.relatedModel];
581
+ switch (fieldDef.data.type) {
582
+ case ModelRelationalField_1.ModelRelationshipTypes.hasOne:
583
+ for (const idField of relatedModel.data.identifier) {
584
+ addFk({
585
+ onModel: modelName,
586
+ asField: fkName(modelName, fieldName, idField),
587
+ fieldDef: {
588
+ data: {
589
+ ...fieldDef.data,
590
+ fieldType: relatedModel.data.fields[idField]?.data.fieldType ||
591
+ ModelField_1.ModelFieldType.Id,
592
+ },
593
+ },
594
+ });
595
+ }
596
+ break;
597
+ case ModelRelationalField_1.ModelRelationshipTypes.hasMany:
598
+ {
599
+ let authorization = [];
600
+ let required = false;
601
+ const [_belongsToName, belongsToDef] = Object.entries(relatedModel.data.fields).find(([_name, def]) => {
602
+ return (isModelField(def) &&
603
+ def.data.type === ModelRelationalField_1.ModelRelationshipTypes.belongsTo &&
604
+ def.data.relatedModel === fieldName);
605
+ }) || [];
606
+ if (belongsToDef && isModelField(belongsToDef)) {
607
+ authorization = belongsToDef.data.authorization;
608
+ required = belongsToDef.data.valueRequired;
609
+ }
610
+ for (const idField of typeDef.data.identifier) {
611
+ addFk({
612
+ onModel: fieldDef.data.relatedModel,
613
+ asField: fkName(modelName, fieldName, idField),
614
+ fieldDef: {
615
+ data: {
616
+ ...(typeDef.data.fields[idField]?.data ||
617
+ (0, ModelField_1.id)().data),
618
+ authorization,
619
+ required,
620
+ },
621
+ },
622
+ });
623
+ }
624
+ }
625
+ break;
626
+ case ModelRelationalField_1.ModelRelationshipTypes.belongsTo:
627
+ {
628
+ // only create if corresponds to hasOne
629
+ const [_hasOneName, hasOneDef] = Object.entries(relatedModel.data.fields).find(([_name, def]) => {
630
+ return (isModelField(def) &&
631
+ def.data.type === ModelRelationalField_1.ModelRelationshipTypes.hasOne &&
632
+ def.data.relatedModel === modelName);
633
+ }) || [];
634
+ if (hasOneDef && isModelField(hasOneDef)) {
635
+ for (const idField of relatedModel.data.identifier) {
636
+ addFk({
637
+ onModel: modelName,
638
+ asField: fkName(modelName, fieldName, idField),
639
+ fieldDef: {
640
+ data: {
641
+ ...typeDef.data,
642
+ fieldType: relatedModel.data.fields[idField]?.data.fieldType ||
643
+ ModelField_1.ModelFieldType.Id,
644
+ },
645
+ },
646
+ });
647
+ }
648
+ }
649
+ }
650
+ break;
651
+ case ModelRelationalField_1.ModelRelationshipTypes.manyToMany:
652
+ // pretty sure there's nothing to do here.
653
+ // the implicit join table already has everything, AFAIK.
654
+ break;
655
+ default:
656
+ // nothing to do.
657
+ }
658
+ }
659
+ }
660
+ return fks;
661
+ };
662
+ function processFieldLevelAuthRules(fields, authFields) {
663
+ const fieldLevelAuthRules = {};
664
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
665
+ const fieldAuth = fieldDef?.data?.authorization || [];
666
+ validateAuth(fieldAuth);
667
+ const { authString, authFields: fieldAuthField } = calculateAuth(fieldAuth);
668
+ if (authString)
669
+ fieldLevelAuthRules[fieldName] = authString;
670
+ if (fieldAuthField) {
671
+ addFields(authFields, fieldAuthField);
672
+ }
673
+ }
674
+ return fieldLevelAuthRules;
675
+ }
676
+ function processFields(typeName, fields, impliedFields, fieldLevelAuthRules, identifier, partitionKey, secondaryIndexes = {}) {
677
+ const gqlFields = [];
678
+ const models = [];
679
+ validateImpliedFields(fields, impliedFields);
680
+ for (const [fieldName, fieldDef] of Object.entries(fields)) {
681
+ const fieldAuth = fieldLevelAuthRules[fieldName]
682
+ ? ` ${fieldLevelAuthRules[fieldName]}`
683
+ : '';
684
+ if (isModelField(fieldDef)) {
685
+ gqlFields.push(`${fieldName}: ${modelFieldToGql(fieldDef.data)}${fieldAuth}`);
686
+ }
687
+ else if (isScalarField(fieldDef)) {
688
+ if (fieldName === partitionKey) {
689
+ gqlFields.push(`${fieldName}: ${scalarFieldToGql(fieldDef.data, identifier, secondaryIndexes[fieldName])}${fieldAuth}`);
690
+ }
691
+ else if (isRefField(fieldDef)) {
692
+ gqlFields.push(`${fieldName}: ${refFieldToGql(fieldDef.data)}${fieldAuth}`);
693
+ }
694
+ else if (isEnumType(fieldDef)) {
695
+ // The inline enum type name should be `<TypeName><FieldName>` to avoid
696
+ // enum type name conflicts
697
+ const enumName = `${capitalize(typeName)}${capitalize(fieldName)}`;
698
+ models.push([enumName, fieldDef]);
699
+ gqlFields.push(`${fieldName}: ${enumName}`);
700
+ }
701
+ else if (isCustomType(fieldDef)) {
702
+ // The inline CustomType name should be `<TypeName><FieldName>` to avoid
703
+ // CustomType name conflicts
704
+ const customTypeName = `${capitalize(typeName)}${capitalize(fieldName)}`;
705
+ models.push([customTypeName, fieldDef]);
706
+ gqlFields.push(`${fieldName}: ${customTypeName}`);
707
+ }
708
+ else {
709
+ gqlFields.push(`${fieldName}: ${scalarFieldToGql(fieldDef.data, undefined, secondaryIndexes[fieldName])}${fieldAuth}`);
710
+ }
711
+ }
712
+ else {
713
+ throw new Error(`Unexpected field definition: ${fieldDef}`);
714
+ }
715
+ }
716
+ return { gqlFields, models };
717
+ }
718
+ /**
719
+ *
720
+ * @param pk - partition key field name
721
+ * @param sk - (optional) array of sort key field names
722
+ * @returns default query field name
723
+ */
724
+ const secondaryIndexDefaultQueryField = (pk, sk) => {
725
+ const skName = sk?.length ? 'And' + sk?.map(capitalize).join('And') : '';
726
+ const queryField = `listBy${capitalize(pk)}${skName}`;
727
+ return queryField;
728
+ };
729
+ /**
730
+ * Given InternalModelIndexType[] returns a map where the key is the model field to be annotated with an @index directive
731
+ * and the value is an array of transformed Amplify @index directives with all supplied attributes
732
+ */
733
+ const transformedSecondaryIndexesForModel = (secondaryIndexes) => {
734
+ const indexDirectiveWithAttributes = (partitionKey, sortKeys, indexName, queryField) => {
735
+ if (!sortKeys.length && !indexName && !queryField) {
736
+ return `@index(queryField: "${secondaryIndexDefaultQueryField(partitionKey)}")`;
737
+ }
738
+ const attributes = [];
739
+ if (indexName) {
740
+ attributes.push(`name: "${indexName}"`);
741
+ }
742
+ if (sortKeys.length) {
743
+ attributes.push(`sortKeyFields: [${sortKeys.map((sk) => `"${sk}"`).join(', ')}]`);
744
+ }
745
+ if (queryField) {
746
+ attributes.push(`queryField: "${queryField}"`);
747
+ }
748
+ else {
749
+ attributes.push(`queryField: "${secondaryIndexDefaultQueryField(partitionKey, sortKeys)}"`);
750
+ }
751
+ return `@index(${attributes.join(', ')})`;
752
+ };
753
+ return secondaryIndexes.reduce((acc, { data: { partitionKey, sortKeys, indexName, queryField } }) => {
754
+ acc[partitionKey] = acc[partitionKey] || [];
755
+ acc[partitionKey].push(indexDirectiveWithAttributes(partitionKey, sortKeys, indexName, queryField));
756
+ return acc;
757
+ }, {});
758
+ };
759
+ const ruleIsResourceAuth = (authRule) => {
760
+ const data = (0, Authorization_1.accessSchemaData)(authRule);
761
+ return data.strategy === 'resource';
762
+ };
763
+ /**
764
+ * Separates out lambda resource auth rules from remaining schema rules.
765
+ *
766
+ * @param authRules schema auth rules
767
+ */
768
+ const extractFunctionSchemaAccess = (authRules) => {
769
+ const schemaAuth = [];
770
+ const functionSchemaAccess = [];
771
+ const defaultActions = [
772
+ 'query',
773
+ 'mutate',
774
+ 'listen',
775
+ ];
776
+ for (const rule of authRules) {
777
+ if (ruleIsResourceAuth(rule)) {
778
+ const ruleData = (0, Authorization_1.accessSchemaData)(rule);
779
+ const fnAccess = {
780
+ resourceProvider: ruleData.resource,
781
+ actions: ruleData.operations || defaultActions,
782
+ };
783
+ functionSchemaAccess.push(fnAccess);
784
+ }
785
+ else {
786
+ schemaAuth.push(rule);
787
+ }
788
+ }
789
+ return { schemaAuth, functionSchemaAccess };
790
+ };
791
+ /**
792
+ * Returns a closure for retrieving reference type and definition from schema
793
+ */
794
+ const getRefTypeForSchema = (schema) => {
795
+ const getRefType = (source, target) => {
796
+ const typeDef = schema.data.types[source];
797
+ if (typeDef === undefined) {
798
+ throw new Error(`Invalid ref. ${target} is referencing ${source} which is not defined in the schema`);
799
+ }
800
+ if (isInternalModel(typeDef)) {
801
+ return { type: 'Model', def: typeDef };
802
+ }
803
+ if (isCustomOperation(typeDef)) {
804
+ return { type: 'CustomOperation', def: typeDef };
805
+ }
806
+ if (isCustomType(typeDef)) {
807
+ return { type: 'CustomType', def: typeDef };
808
+ }
809
+ if (isEnumType(typeDef)) {
810
+ return { type: 'Enum', def: typeDef };
811
+ }
812
+ throw new Error(`Invalid ref. ${target} is referencing ${source} which is neither a Model, Custom Operation, Custom Type, or Enum`);
813
+ };
814
+ return getRefType;
815
+ };
816
+ const schemaPreprocessor = (schema) => {
817
+ const gqlModels = [];
818
+ const customQueries = [];
819
+ const customMutations = [];
820
+ const customSubscriptions = [];
821
+ const jsFunctions = [];
822
+ const lambdaFunctions = {};
823
+ const customSqlDataSourceStrategies = [];
824
+ const databaseType = schema.data.configuration.database.engine === 'dynamodb'
825
+ ? 'dynamodb'
826
+ : 'sql';
827
+ const staticSchema = schema.data.configuration.database.engine === 'dynamodb' ? false : true;
828
+ const fkFields = staticSchema ? {} : allImpliedFKs(schema);
829
+ const topLevelTypes = Object.entries(schema.data.types);
830
+ const { schemaAuth, functionSchemaAccess } = extractFunctionSchemaAccess(schema.data.authorization);
831
+ const getRefType = getRefTypeForSchema(schema);
832
+ for (const [typeName, typeDef] of topLevelTypes) {
833
+ validateAuth(typeDef.data?.authorization);
834
+ const mostRelevantAuthRules = typeDef.data?.authorization?.length > 0
835
+ ? typeDef.data.authorization
836
+ : schemaAuth;
837
+ if (!isInternalModel(typeDef)) {
838
+ if (isEnumType(typeDef)) {
839
+ if (typeDef.values.some((value) => /\s/.test(value))) {
840
+ throw new Error(`Values of the enum type ${typeName} should not contain any whitespace.`);
841
+ }
842
+ const enumType = `enum ${typeName} {\n ${typeDef.values.join('\n ')}\n}`;
843
+ gqlModels.push(enumType);
844
+ }
845
+ else if (isCustomType(typeDef)) {
846
+ const fields = typeDef.data.fields;
847
+ const fieldAuthApplicableFields = Object.fromEntries(Object.entries(fields).filter((pair) => isModelField(pair[1])));
848
+ const authString = '';
849
+ const authFields = {};
850
+ const fieldLevelAuthRules = processFieldLevelAuthRules(fieldAuthApplicableFields, authFields);
851
+ const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules);
852
+ topLevelTypes.push(...models);
853
+ const joined = gqlFields.join('\n ');
854
+ const model = `type ${typeName} ${authString}\n{\n ${joined}\n}`;
855
+ gqlModels.push(model);
856
+ }
857
+ else if (isCustomOperation(typeDef)) {
858
+ const { typeName: opType } = typeDef.data;
859
+ const { gqlField, models, jsFunctionForField, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = transformCustomOperations(typeDef, typeName, mostRelevantAuthRules, databaseType, getRefType);
860
+ Object.assign(lambdaFunctions, lambdaFunctionDefinition);
861
+ topLevelTypes.push(...models);
862
+ if (jsFunctionForField) {
863
+ jsFunctions.push(jsFunctionForField);
864
+ }
865
+ if (customSqlDataSourceStrategy) {
866
+ customSqlDataSourceStrategies.push(customSqlDataSourceStrategy);
867
+ }
868
+ switch (opType) {
869
+ case 'Query':
870
+ customQueries.push(gqlField);
871
+ break;
872
+ case 'Mutation':
873
+ customMutations.push(gqlField);
874
+ break;
875
+ case 'Subscription':
876
+ customSubscriptions.push(gqlField);
877
+ break;
878
+ default:
879
+ break;
880
+ }
881
+ }
882
+ }
883
+ else if (staticSchema) {
884
+ const fields = { ...typeDef.data.fields };
885
+ const identifier = typeDef.data.identifier;
886
+ const [partitionKey] = identifier;
887
+ validateStaticFields(fields, fkFields[typeName]);
888
+ const { authString, authFields } = calculateAuth(mostRelevantAuthRules);
889
+ if (authString == '') {
890
+ throw new Error(`Model \`${typeName}\` is missing authorization rules. Add global rules to the schema or ensure every model has its own rules.`);
891
+ }
892
+ const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
893
+ validateStaticFields(fields, authFields);
894
+ const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey);
895
+ topLevelTypes.push(...models);
896
+ const joined = gqlFields.join('\n ');
897
+ const model = `type ${typeName} @model ${authString}\n{\n ${joined}\n}`;
898
+ gqlModels.push(model);
899
+ }
900
+ else {
901
+ const fields = mergeFieldObjects(typeDef.data.fields, fkFields[typeName]);
902
+ const identifier = typeDef.data.identifier;
903
+ const [partitionKey] = identifier;
904
+ const transformedSecondaryIndexes = transformedSecondaryIndexesForModel(typeDef.data.secondaryIndexes);
905
+ const { authString, authFields } = calculateAuth(mostRelevantAuthRules);
906
+ if (authString == '') {
907
+ throw new Error(`Model \`${typeName}\` is missing authorization rules. Add global rules to the schema or ensure every model has its own rules.`);
908
+ }
909
+ const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
910
+ const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey, transformedSecondaryIndexes);
911
+ topLevelTypes.push(...models);
912
+ const joined = gqlFields.join('\n ');
913
+ const model = `type ${typeName} @model ${authString}\n{\n ${joined}\n}`;
914
+ gqlModels.push(model);
915
+ }
916
+ }
917
+ const customOperations = {
918
+ queries: customQueries,
919
+ mutations: customMutations,
920
+ subscriptions: customSubscriptions,
921
+ };
922
+ gqlModels.push(...generateCustomOperationTypes(customOperations));
923
+ const processedSchema = gqlModels.join('\n\n');
924
+ return {
925
+ schema: processedSchema,
926
+ jsFunctions,
927
+ functionSchemaAccess,
928
+ lambdaFunctions,
929
+ customSqlDataSourceStrategies,
930
+ };
931
+ };
932
+ function validateCustomOperations(typeDef, typeName, authRules, getRefType) {
933
+ const { functionRef, handlers, typeName: opType, subscriptionSource, } = typeDef.data;
934
+ // TODO: remove `functionRef` after deprecating
935
+ const handlerConfigured = functionRef !== null || handlers?.length;
936
+ const authConfigured = authRules.length > 0;
937
+ if ((authConfigured && !handlerConfigured) ||
938
+ (handlerConfigured && !authConfigured)) {
939
+ // Deploying a custom operation with auth and no handler reference OR
940
+ // with a handler reference but no auth
941
+ // causes the CFN stack to reach an unrecoverable state. Ideally, this should be fixed
942
+ // in the CDK construct, but we're catching it early here as a stopgap
943
+ throw new Error(`Custom operation ${typeName} requires both an authorization rule and a handler reference`);
944
+ }
945
+ // Handlers must all be of the same type
946
+ if (handlers?.length) {
947
+ const configuredHandlers = new Set();
948
+ for (const handler of handlers) {
949
+ configuredHandlers.add((0, util_1.getBrand)(handler));
950
+ }
951
+ if (configuredHandlers.size > 1) {
952
+ const configuredHandlersStr = JSON.stringify(Array.from(configuredHandlers));
953
+ throw new Error(`Field handlers must be of the same type. ${typeName} has been configured with ${configuredHandlersStr}`);
954
+ }
955
+ }
956
+ if (opType === 'Subscription') {
957
+ if (subscriptionSource.length < 1) {
958
+ throw new Error(`${typeName} is missing a mutation source. Custom subscriptions must reference a mutation source via subscription().for(a.ref('ModelOrOperationName')) `);
959
+ }
960
+ let expectedReturnType;
961
+ for (const source of subscriptionSource) {
962
+ const sourceName = source.data.link;
963
+ const { type, def } = getRefType(sourceName, typeName);
964
+ if (type !== 'Model' && source.data.mutationOperations.length > 0) {
965
+ throw new Error(`Invalid subscription definition. .mutations() modifier can only be used with a Model ref. ${typeName} is referencing ${type}`);
966
+ }
967
+ let resolvedReturnType;
968
+ if (type === 'Model') {
969
+ if (source.data.mutationOperations.length === 0) {
970
+ throw new Error(`Invalid subscription definition. .mutations() modifier must be used with a Model ref subscription source. ${typeName} is referencing ${sourceName} without specifying a mutation`);
971
+ }
972
+ else {
973
+ resolvedReturnType = def;
974
+ }
975
+ }
976
+ if (type === 'CustomOperation') {
977
+ if (def.data.typeName !== 'Mutation') {
978
+ throw new Error(`Invalid subscription definition. .for() can only reference a mutation. ${typeName} is referencing ${sourceName} which is a ${def.data.typeName}`);
979
+ }
980
+ else {
981
+ const returnType = def.data.returnType;
982
+ if (isRefField(returnType)) {
983
+ ({ def: resolvedReturnType } = getRefType(returnType.data.link, typeName));
984
+ }
985
+ else {
986
+ resolvedReturnType = returnType;
987
+ }
988
+ }
989
+ }
990
+ expectedReturnType = expectedReturnType ?? resolvedReturnType;
991
+ // As the return types are resolved from the root `schema` object and they should
992
+ // not be mutated, we compare by references here.
993
+ if (expectedReturnType !== resolvedReturnType) {
994
+ throw new Error(`Invalid subscription definition. .for() can only reference resources that have the same return type. ${typeName} is referencing resources that have different return types.`);
995
+ }
996
+ }
997
+ }
998
+ }
999
+ const isSqlReferenceHandler = (handler) => {
1000
+ return Array.isArray(handler) && (0, util_1.getBrand)(handler[0]) === 'sqlReference';
1001
+ };
1002
+ const isCustomHandler = (handler) => {
1003
+ return Array.isArray(handler) && (0, util_1.getBrand)(handler[0]) === 'customHandler';
1004
+ };
1005
+ const isFunctionHandler = (handler) => {
1006
+ return Array.isArray(handler) && (0, util_1.getBrand)(handler[0]) === 'functionHandler';
1007
+ };
1008
+ const normalizeDataSourceName = (dataSource) => {
1009
+ // default data source
1010
+ const noneDataSourceName = 'NONE_DS';
1011
+ if (dataSource === undefined) {
1012
+ return noneDataSourceName;
1013
+ }
1014
+ if (dataSourceIsRef(dataSource)) {
1015
+ return `${dataSource.data.link}Table`;
1016
+ }
1017
+ return dataSource;
1018
+ };
1019
+ const sanitizeStackTrace = (stackTrace) => {
1020
+ // normalize EOL to \n so that parsing is consistent across platforms
1021
+ const normalizedStackTrace = stackTrace.replaceAll(os.EOL, '\n');
1022
+ return (normalizedStackTrace
1023
+ .split('\n')
1024
+ .map((line) => line.trim())
1025
+ // filters out noise not relevant to the stack trace. All stack trace lines begin with 'at'
1026
+ .filter((line) => line.startsWith('at')) || []);
1027
+ };
1028
+ // copied from the defineFunction path resolution impl:
1029
+ // https://github.com/aws-amplify/amplify-backend/blob/main/packages/backend-function/src/get_caller_directory.ts
1030
+ const resolveEntryPath = (data, errorMessage) => {
1031
+ if (path.isAbsolute(data.entry)) {
1032
+ return data.entry;
1033
+ }
1034
+ if (!data.stack) {
1035
+ throw new Error(errorMessage);
1036
+ }
1037
+ const stackTraceLines = sanitizeStackTrace(data.stack);
1038
+ if (stackTraceLines.length < 2) {
1039
+ throw new Error(errorMessage);
1040
+ }
1041
+ const stackTraceImportLine = stackTraceLines[1]; // the first entry is the file where the error was initialized (our code). The second entry is where the customer called our code which is what we are interested in
1042
+ // if entry is relative, compute with respect to the caller directory
1043
+ return { relativePath: data.entry, importLine: stackTraceImportLine };
1044
+ };
1045
+ const handleCustom = (handlers, opType, typeName) => {
1046
+ const transformedHandlers = handlers.map((handler) => {
1047
+ const handlerData = (0, Handler_1.getHandlerData)(handler);
1048
+ return {
1049
+ dataSource: normalizeDataSourceName(handlerData.dataSource),
1050
+ entry: resolveEntryPath(handlerData, 'Could not determine import path to construct absolute code path for custom handler. Consider using an absolute path instead.'),
1051
+ };
1052
+ });
1053
+ const jsFn = {
1054
+ typeName: opType,
1055
+ fieldName: typeName,
1056
+ handlers: transformedHandlers,
1057
+ };
1058
+ return jsFn;
1059
+ };
1060
+ function transformCustomOperations(typeDef, typeName, authRules, databaseType, getRefType) {
1061
+ const { typeName: opType, handlers } = typeDef.data;
1062
+ let jsFunctionForField = undefined;
1063
+ validateCustomOperations(typeDef, typeName, authRules, getRefType);
1064
+ if (isCustomHandler(handlers)) {
1065
+ jsFunctionForField = handleCustom(handlers, opType, typeName);
1066
+ }
1067
+ const isCustom = Boolean(jsFunctionForField);
1068
+ const { gqlField, models, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = customOperationToGql(typeName, typeDef, authRules, isCustom, databaseType, getRefType);
1069
+ return {
1070
+ gqlField,
1071
+ models,
1072
+ jsFunctionForField,
1073
+ lambdaFunctionDefinition,
1074
+ customSqlDataSourceStrategy,
1075
+ };
1076
+ }
1077
+ function generateCustomOperationTypes({ queries, mutations, subscriptions, }) {
1078
+ const types = [];
1079
+ if (mutations.length > 0) {
1080
+ types.push(`type Mutation {\n ${mutations.join('\n ')}\n}`);
1081
+ }
1082
+ if (queries.length > 0) {
1083
+ types.push(`type Query {\n ${queries.join('\n ')}\n}`);
1084
+ }
1085
+ if (subscriptions.length > 0) {
1086
+ types.push(`type Subscription {\n ${subscriptions.join('\n ')}\n}`);
1087
+ }
1088
+ return types;
1089
+ }
1090
+ /**
1091
+ * Returns API definition from ModelSchema or string schema
1092
+ * @param arg - { schema }
1093
+ * @returns DerivedApiDefinition that conforms to IAmplifyGraphqlDefinition
1094
+ */
1095
+ function processSchema(arg) {
1096
+ const { schema, jsFunctions, functionSchemaAccess, lambdaFunctions, customSqlDataSourceStrategies, } = schemaPreprocessor(arg.schema);
1097
+ return {
1098
+ schema,
1099
+ functionSlots: [],
1100
+ jsFunctions,
1101
+ functionSchemaAccess,
1102
+ lambdaFunctions,
1103
+ customSqlDataSourceStrategies,
1104
+ };
1105
+ }
1106
+ exports.processSchema = processSchema;