@aws-amplify/data-schema 0.13.6 → 0.13.8

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.
@@ -1,10 +1,37 @@
1
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
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.processSchema = void 0;
4
27
  const ModelField_1 = require("./ModelField");
5
28
  const ModelRelationalField_1 = require("./ModelRelationalField");
6
29
  const Authorization_1 = require("./Authorization");
7
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"));
8
35
  function isInternalModel(model) {
9
36
  if (model.data &&
10
37
  !isCustomType(model) &&
@@ -41,13 +68,18 @@ function isRefFieldDef(data) {
41
68
  return data?.type === 'ref';
42
69
  }
43
70
  function isModelField(field) {
44
- return isModelFieldDef(field.data);
71
+ return isModelFieldDef(field?.data);
72
+ }
73
+ function dataSourceIsRef(dataSource) {
74
+ return (typeof dataSource !== 'string' &&
75
+ dataSource?.data &&
76
+ dataSource.data.type === 'ref');
45
77
  }
46
78
  function isScalarField(field) {
47
- return isScalarFieldDef(field.data);
79
+ return isScalarFieldDef(field?.data);
48
80
  }
49
81
  function isRefField(field) {
50
- return isRefFieldDef(field.data);
82
+ return isRefFieldDef(field?.data);
51
83
  }
52
84
  function scalarFieldToGql(fieldDef, identifier, secondaryIndexes = []) {
53
85
  const { fieldType, required, array, arrayRequired, default: _default, } = fieldDef;
@@ -123,21 +155,23 @@ function refFieldToGql(fieldDef) {
123
155
  // }
124
156
  return field;
125
157
  }
126
- function customOperationToGql(typeName, typeDef, authorization) {
158
+ function customOperationToGql(typeName, typeDef, authorization, isCustom = false) {
127
159
  const { arguments: fieldArgs, returnType, functionRef } = typeDef.data;
128
160
  let callSignature = typeName;
129
161
  const implicitModels = [];
130
- const { authString } = calculateAuth(authorization);
162
+ const { authString } = isCustom
163
+ ? calculateCustomAuth(authorization)
164
+ : calculateAuth(authorization);
131
165
  let returnTypeName;
132
166
  if (isRefField(returnType)) {
133
- returnTypeName = refFieldToGql(returnType.data);
167
+ returnTypeName = refFieldToGql(returnType?.data);
134
168
  }
135
169
  else if (isCustomType(returnType)) {
136
170
  returnTypeName = `${capitalize(typeName)}ReturnType`;
137
171
  implicitModels.push([returnTypeName, returnType]);
138
172
  }
139
173
  else if (isScalarField(returnType)) {
140
- returnTypeName = scalarFieldToGql(returnType.data);
174
+ returnTypeName = scalarFieldToGql(returnType?.data);
141
175
  }
142
176
  else {
143
177
  throw new Error(`Unrecognized return type on ${typeName}`);
@@ -282,6 +316,77 @@ function calculateAuth(authorization) {
282
316
  const authString = rules.length > 0 ? `@auth(rules: [${rules.join(',\n ')}])` : '';
283
317
  return { authString, authFields };
284
318
  }
319
+ function validateCustomAuthRule(rule) {
320
+ if (rule.operations) {
321
+ throw new Error('.to() modifier is not supported for custom queries/mutations');
322
+ }
323
+ if (rule.groupOrOwnerField) {
324
+ throw new Error('Dynamic auth (owner or dynamic groups) is not supported for custom queries/mutations');
325
+ }
326
+ // identityClaim
327
+ if (rule.identityClaim) {
328
+ throw new Error('identityClaim attr is not supported with a.handler.custom');
329
+ }
330
+ // groupClaim
331
+ if (rule.groupClaim) {
332
+ throw new Error('groupClaim attr is not supported with a.handler.custom');
333
+ }
334
+ if (rule.groups && rule.provider === 'oidc') {
335
+ throw new Error('OIDC group auth is not supported with a.handler.custom');
336
+ }
337
+ }
338
+ function getCustomAuthProvider(rule) {
339
+ const strategyDict = {
340
+ public: {
341
+ default: '@aws_api_key',
342
+ apiKey: '@aws_api_key',
343
+ iam: '@aws_iam',
344
+ },
345
+ private: {
346
+ default: '@aws_cognito_user_pools',
347
+ userPools: '@aws_cognito_user_pools',
348
+ oidc: '@aws_oidc',
349
+ iam: '@aws_iam',
350
+ },
351
+ groups: {
352
+ default: '@aws_cognito_user_pools',
353
+ userPools: '@aws_cognito_user_pools',
354
+ },
355
+ custom: {
356
+ default: '@aws_lambda',
357
+ function: '@aws_lambda',
358
+ },
359
+ };
360
+ const stratProviders = strategyDict[rule.strategy];
361
+ if (stratProviders === undefined) {
362
+ throw new Error(`Unsupported auth strategy for custom handlers: ${rule.strategy}`);
363
+ }
364
+ const provider = rule.provider || 'default';
365
+ const stratProvider = stratProviders[provider];
366
+ if (stratProvider === undefined) {
367
+ throw new Error(`Unsupported provider for custom handlers: ${rule.provider}`);
368
+ }
369
+ return stratProvider;
370
+ }
371
+ function calculateCustomAuth(authorization) {
372
+ const rules = [];
373
+ for (const entry of authorization) {
374
+ const rule = (0, Authorization_1.accessData)(entry);
375
+ validateCustomAuthRule(rule);
376
+ const provider = getCustomAuthProvider(rule);
377
+ if (rule.groups) {
378
+ // example: (cognito_groups: ["Bloggers", "Readers"])
379
+ rules.push(`${provider}(cognito_groups: [${rule.groups
380
+ .map((group) => `"${group}"`)
381
+ .join(', ')}])`);
382
+ }
383
+ else {
384
+ rules.push(provider);
385
+ }
386
+ }
387
+ const authString = rules.join(' ');
388
+ return { authString };
389
+ }
285
390
  function capitalize(s) {
286
391
  return `${s[0].toUpperCase()}${s.slice(1)}`;
287
392
  }
@@ -521,6 +626,7 @@ const schemaPreprocessor = (schema) => {
521
626
  const customQueries = [];
522
627
  const customMutations = [];
523
628
  const customSubscriptions = [];
629
+ const jsFunctions = [];
524
630
  const fkFields = allImpliedFKs(schema);
525
631
  const topLevelTypes = Object.entries(schema.data.types);
526
632
  for (const [typeName, typeDef] of topLevelTypes) {
@@ -537,9 +643,10 @@ const schemaPreprocessor = (schema) => {
537
643
  }
538
644
  else if (isCustomType(typeDef)) {
539
645
  const fields = typeDef.data.fields;
646
+ const fieldAuthApplicableFields = Object.fromEntries(Object.entries(fields).filter((pair) => isModelField(pair[1])));
540
647
  const authString = '';
541
648
  const authFields = {};
542
- const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
649
+ const fieldLevelAuthRules = processFieldLevelAuthRules(fieldAuthApplicableFields, authFields);
543
650
  const { gqlFields, models } = processFields(typeName, fields, fieldLevelAuthRules);
544
651
  topLevelTypes.push(...models);
545
652
  const joined = gqlFields.join('\n ');
@@ -548,16 +655,11 @@ const schemaPreprocessor = (schema) => {
548
655
  }
549
656
  else if (isCustomOperation(typeDef)) {
550
657
  const { typeName: opType } = typeDef.data;
551
- if ((mostRelevantAuthRules.length > 0 && !typeDef.data.functionRef) ||
552
- (typeDef.data.functionRef && mostRelevantAuthRules.length < 1)) {
553
- // Deploying a custom operation with auth and no handler reference OR
554
- // with a handler reference but not auth
555
- // causes the CFN stack to reach an unrecoverable state. Ideally, this should be fixed
556
- // in the CDK construct, but we're catching it early here as a stopgap
557
- throw new Error(`Custom operation ${typeName} requires both an authorization rule and a handler reference`);
558
- }
559
- const { gqlField, models } = customOperationToGql(typeName, typeDef, mostRelevantAuthRules);
658
+ const { gqlField, models, jsFunctionForField } = transformCustomOperations(typeDef, typeName, mostRelevantAuthRules);
560
659
  topLevelTypes.push(...models);
660
+ if (jsFunctionForField) {
661
+ jsFunctions.push(jsFunctionForField);
662
+ }
561
663
  switch (opType) {
562
664
  case 'Query':
563
665
  customQueries.push(gqlField);
@@ -604,8 +706,100 @@ const schemaPreprocessor = (schema) => {
604
706
  };
605
707
  gqlModels.push(...generateCustomOperationTypes(customOperations));
606
708
  const processedSchema = gqlModels.join('\n\n');
607
- return processedSchema;
709
+ return { schema: processedSchema, jsFunctions };
710
+ };
711
+ function validateCustomOperations(typeDef, typeName, authRules) {
712
+ const { functionRef, handlers } = typeDef.data;
713
+ // TODO: remove `functionRef` after deprecating
714
+ const handlerConfigured = functionRef !== null || handlers?.length;
715
+ const authConfigured = authRules.length > 0;
716
+ if ((authConfigured && !handlerConfigured) ||
717
+ (handlerConfigured && !authConfigured)) {
718
+ // Deploying a custom operation with auth and no handler reference OR
719
+ // with a handler reference but no auth
720
+ // causes the CFN stack to reach an unrecoverable state. Ideally, this should be fixed
721
+ // in the CDK construct, but we're catching it early here as a stopgap
722
+ throw new Error(`Custom operation ${typeName} requires both an authorization rule and a handler reference`);
723
+ }
724
+ // Handlers must all be of the same type
725
+ if (handlers?.length) {
726
+ const configuredHandlers = new Set();
727
+ for (const handler of handlers) {
728
+ configuredHandlers.add((0, util_1.getBrand)(handler));
729
+ }
730
+ if (configuredHandlers.size > 1) {
731
+ const configuredHandlersStr = JSON.stringify(Array.from(configuredHandlers));
732
+ throw new Error(`Field handlers must be of the same type. ${typeName} has been configured with ${configuredHandlersStr}`);
733
+ }
734
+ }
735
+ }
736
+ const isCustomHandler = (handler) => {
737
+ return Array.isArray(handler) && (0, util_1.getBrand)(handler[0]) === 'customHandler';
738
+ };
739
+ const normalizeDataSourceName = (dataSource) => {
740
+ // default data source
741
+ const noneDataSourceName = 'NONE_DS';
742
+ if (dataSource === undefined) {
743
+ return noneDataSourceName;
744
+ }
745
+ if (dataSourceIsRef(dataSource)) {
746
+ return `${dataSource.data.link}Table`;
747
+ }
748
+ return dataSource;
749
+ };
750
+ const sanitizeStackTrace = (stackTrace) => {
751
+ // normalize EOL to \n so that parsing is consistent across platforms
752
+ const normalizedStackTrace = stackTrace.replaceAll(os.EOL, '\n');
753
+ return (normalizedStackTrace
754
+ .split('\n')
755
+ .map((line) => line.trim())
756
+ // filters out noise not relevant to the stack trace. All stack trace lines begin with 'at'
757
+ .filter((line) => line.startsWith('at')) || []);
608
758
  };
759
+ // copied from the defineFunction path resolution impl:
760
+ // https://github.com/aws-amplify/amplify-backend/blob/main/packages/backend-function/src/get_caller_directory.ts
761
+ const resolveCustomHandlerEntryPath = (data) => {
762
+ if (path.isAbsolute(data.entry)) {
763
+ return data.entry;
764
+ }
765
+ const unresolvedImportLocationError = new Error('Could not determine import path to construct absolute code path for custom handler. Consider using an absolute path instead.');
766
+ if (!data.stack) {
767
+ throw unresolvedImportLocationError;
768
+ }
769
+ const stackTraceLines = sanitizeStackTrace(data.stack);
770
+ if (stackTraceLines.length < 2) {
771
+ throw unresolvedImportLocationError;
772
+ }
773
+ 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
774
+ // if entry is relative, compute with respect to the caller directory
775
+ return { relativePath: data.entry, importLine: stackTraceImportLine };
776
+ };
777
+ const handleCustom = (handlers, opType, typeName) => {
778
+ const transformedHandlers = handlers.map((handler) => {
779
+ const handlerData = (0, Handler_1.getHandlerData)(handler);
780
+ return {
781
+ dataSource: normalizeDataSourceName(handlerData.dataSource),
782
+ entry: resolveCustomHandlerEntryPath(handlerData),
783
+ };
784
+ });
785
+ const jsFn = {
786
+ typeName: opType,
787
+ fieldName: typeName,
788
+ handlers: transformedHandlers,
789
+ };
790
+ return jsFn;
791
+ };
792
+ function transformCustomOperations(typeDef, typeName, authRules) {
793
+ const { typeName: opType, handlers } = typeDef.data;
794
+ let jsFunctionForField = undefined;
795
+ validateCustomOperations(typeDef, typeName, authRules);
796
+ if (isCustomHandler(handlers)) {
797
+ jsFunctionForField = handleCustom(handlers, opType, typeName);
798
+ }
799
+ const isCustom = Boolean(jsFunctionForField);
800
+ const { gqlField, models } = customOperationToGql(typeName, typeDef, authRules, isCustom);
801
+ return { gqlField, models, jsFunctionForField };
802
+ }
609
803
  function generateCustomOperationTypes({ queries, mutations, subscriptions, }) {
610
804
  const types = [];
611
805
  if (mutations.length > 0) {
@@ -625,7 +819,7 @@ function generateCustomOperationTypes({ queries, mutations, subscriptions, }) {
625
819
  * @returns DerivedApiDefinition that conforms to IAmplifyGraphqlDefinition
626
820
  */
627
821
  function processSchema(arg) {
628
- const schema = schemaPreprocessor(arg.schema);
629
- return { schema, functionSlots: [] };
822
+ const { schema, jsFunctions } = schemaPreprocessor(arg.schema);
823
+ return { schema, functionSlots: [], jsFunctions };
630
824
  }
631
825
  exports.processSchema = processSchema;
@@ -26,4 +26,10 @@ export type Brand<BrandStr extends string> = {
26
26
  * const myType = {content: "default content", ...brand<'example'>}
27
27
  */
28
28
  export declare function brand<BrandStr extends string>(brand: BrandStr): Brand<BrandStr>;
29
+ /**
30
+ *
31
+ * @param branded: Branded object
32
+ * @returns The string brand value
33
+ */
34
+ export declare function getBrand(branded: Brand<string>): string;
29
35
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.brand = void 0;
3
+ exports.getBrand = exports.brand = void 0;
4
4
  const brandSymbol = Symbol('brand');
5
5
  /**
6
6
  * Create an object of a specific type Brand
@@ -21,3 +21,12 @@ function brand(brand) {
21
21
  };
22
22
  }
23
23
  exports.brand = brand;
24
+ /**
25
+ *
26
+ * @param branded: Branded object
27
+ * @returns The string brand value
28
+ */
29
+ function getBrand(branded) {
30
+ return branded[brandSymbol];
31
+ }
32
+ exports.getBrand = getBrand;
@@ -1 +1 @@
1
- export { Brand, brand } from './Brand';
1
+ export { Brand, brand, getBrand } from './Brand';
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.brand = void 0;
3
+ exports.getBrand = exports.brand = void 0;
4
4
  var Brand_1 = require("./Brand");
5
5
  Object.defineProperty(exports, "brand", { enumerable: true, get: function () { return Brand_1.brand; } });
6
+ Object.defineProperty(exports, "getBrand", { enumerable: true, get: function () { return Brand_1.getBrand; } });