@aws-amplify/data-schema 0.13.17 → 0.14.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.
@@ -11,6 +11,8 @@ declare const mutationBrand = "mutationCustomOperation";
11
11
  declare const subscriptionBrand = "subscriptionCustomOperation";
12
12
  type CustomOperationBrand = typeof queryBrand | typeof mutationBrand | typeof subscriptionBrand;
13
13
  type CustomArguments = Record<string, ModelField<any, any> | EnumType<EnumTypeParamShape>>;
14
+ type SubscriptionSource = RefType<any, any>;
15
+ type InternalSubscriptionSource = InternalRef;
14
16
  type CustomReturnType = RefType<any> | CustomType<any>;
15
17
  type CustomFunctionRefType = string;
16
18
  type InternalCustomArguments = Record<string, InternalField>;
@@ -25,11 +27,13 @@ type CustomData = {
25
27
  authorization: Authorization<any, any, any>[];
26
28
  typeName: CustomOperationName;
27
29
  handlers: Handler[] | null;
30
+ subscriptionSource: SubscriptionSource[];
28
31
  };
29
32
  type InternalCustomData = CustomData & {
30
33
  arguments: InternalCustomArguments;
31
34
  returnType: InternalCustomReturnType;
32
35
  functionRef: string | null;
36
+ subscriptionSource: InternalSubscriptionSource[];
33
37
  authorization: Authorization<any, any, any>[];
34
38
  };
35
39
  export type CustomOperationParamShape = {
@@ -54,6 +58,7 @@ export type CustomOperation<T extends CustomOperationParamShape, K extends keyof
54
58
  function<FunctionRef extends CustomFunctionRefType>(functionRefOrName: FunctionRef): CustomOperation<SetTypeSubArg<T, 'functionRef', FunctionRef>, K | 'function', B>;
55
59
  authorization<AuthRuleType extends Authorization<any, any, any>>(rules: AuthRuleType[]): CustomOperation<SetTypeSubArg<T, 'authorization', AuthRuleType[]>, K | 'authorization', B>;
56
60
  handler<H extends HandlerInputType>(handlers: H): CustomOperation<T, K | 'handler', B>;
61
+ for(source: SubscriptionSource | SubscriptionSource[]): CustomOperation<T, K | 'for', B>;
57
62
  }, K> & Brand<B>;
58
63
  /**
59
64
  * Internal representation of Custom Type that exposes the `data` property.
@@ -21,6 +21,7 @@ function _custom(typeName, brand) {
21
21
  authorization: [],
22
22
  typeName: typeName,
23
23
  handlers: null,
24
+ subscriptionSource: [],
24
25
  };
25
26
  const builder = brandedBuilder({
26
27
  arguments(args) {
@@ -45,6 +46,10 @@ function _custom(typeName, brand) {
45
46
  : [handlers];
46
47
  return this;
47
48
  },
49
+ for(source) {
50
+ data.subscriptionSource = Array.isArray(source) ? source : [source];
51
+ return this;
52
+ },
48
53
  }, brand);
49
54
  return { ...builder, data };
50
55
  }
@@ -23,7 +23,7 @@ type CustomHandlerInput = {
23
23
  * Defaults to 'NONE_DS'
24
24
  *
25
25
  */
26
- dataSource?: string | RefType<any, any, any>;
26
+ dataSource?: string | RefType<any>;
27
27
  /**
28
28
  * The path to the file that contains the function entry point.
29
29
  * If this is a relative path, it is computed relative to the file where this handler is defined
@@ -43,7 +43,7 @@ type IdentifierFields<T extends ModelTypeParamShape> = keyof IdentifierMap<T> &
43
43
  type IdentifierType<T extends ModelTypeParamShape, Fields extends string = IdentifierFields<T>> = Array<Fields>;
44
44
  export type ModelType<T extends ModelTypeParamShape, K extends keyof ModelType<T> = never> = Omit<{
45
45
  identifier<ID extends IdentifierType<T> = []>(identifier: ID): ModelType<SetTypeSubArg<T, 'identifier', ID>, K | 'identifier'>;
46
- secondaryIndexes<const Indexes extends readonly ModelIndexType<SecondaryIndexFields<ExtractType<T>>, SecondaryIndexFields<ExtractType<T>>, unknown, never, any>[] = readonly [], const IndexesIR extends readonly any[] = SecondaryIndexToIR<Indexes, ExtractType<T>>>(indexes: Indexes): ModelType<SetTypeSubArg<T, 'secondaryIndexes', IndexesIR>, K | 'secondaryIndexes'>;
46
+ secondaryIndexes<const SecondaryIndexPKPool extends string = SecondaryIndexFields<ExtractType<T>>, const Indexes extends readonly ModelIndexType<string, string, unknown, readonly [], any>[] = readonly [], const IndexesIR extends readonly any[] = SecondaryIndexToIR<Indexes, ExtractType<T>>>(callback: (index: <PK extends SecondaryIndexPKPool>(pk: PK) => ModelIndexType<SecondaryIndexPKPool, PK, ReadonlyArray<Exclude<SecondaryIndexPKPool, PK>>>) => Indexes): ModelType<SetTypeSubArg<T, 'secondaryIndexes', IndexesIR>, K | 'secondaryIndexes'>;
47
47
  authorization<AuthRuleType extends Authorization<any, any, any>>(rules: AuthRuleType[]): ModelType<SetTypeSubArg<T, 'authorization', AuthRuleType[]>, K | 'authorization'>;
48
48
  }, K> & Brand<typeof brandName>;
49
49
  /**
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.model = exports.isSchemaModelType = void 0;
4
4
  const util_1 = require("./util");
5
+ const ModelIndex_1 = require("./ModelIndex");
5
6
  const brandName = 'modelType';
6
7
  function _model(fields) {
7
8
  const data = {
@@ -15,8 +16,8 @@ function _model(fields) {
15
16
  data.identifier = identifier;
16
17
  return this;
17
18
  },
18
- secondaryIndexes(indexes) {
19
- data.secondaryIndexes = indexes;
19
+ secondaryIndexes(callback) {
20
+ data.secondaryIndexes = callback(ModelIndex_1.modelIndex);
20
21
  return this;
21
22
  },
22
23
  authorization(rules) {
@@ -9,6 +9,7 @@ type RefTypeData = {
9
9
  valueRequired: boolean;
10
10
  array: boolean;
11
11
  arrayRequired: boolean;
12
+ mutationOperations: MutationOperations[];
12
13
  authorization: Authorization<any, any, any>[];
13
14
  };
14
15
  export type RefTypeParamShape = {
@@ -19,6 +20,7 @@ export type RefTypeParamShape = {
19
20
  arrayRequired: boolean;
20
21
  authorization: Authorization<any, any, any>[];
21
22
  };
23
+ type MutationOperations = 'create' | 'update' | 'delete';
22
24
  export type RefType<T extends RefTypeParamShape, K extends keyof RefType<T> = never, Auth = undefined> = Omit<{
23
25
  /**
24
26
  * Marks a field as required.
@@ -33,6 +35,7 @@ export type RefType<T extends RefTypeParamShape, K extends keyof RefType<T> = ne
33
35
  * multiple authorization rules for this field.
34
36
  */
35
37
  authorization<AuthRuleType extends Authorization<any, any, any>>(rules: AuthRuleType[]): RefType<T, K | 'authorization', AuthRuleType>;
38
+ mutations(operations: MutationOperations[]): RefType<T, K | 'mutations'>;
36
39
  }, K> & {
37
40
  [__auth]?: Auth;
38
41
  } & Brand<typeof brandName>;
@@ -13,6 +13,7 @@ function _ref(link) {
13
13
  valueRequired: false,
14
14
  array: false,
15
15
  arrayRequired: false,
16
+ mutationOperations: [],
16
17
  authorization: [],
17
18
  };
18
19
  const builder = brandedBuilder({
@@ -33,6 +34,10 @@ function _ref(link) {
33
34
  data.authorization = rules;
34
35
  return this;
35
36
  },
37
+ mutations(operations) {
38
+ data.mutationOperations = operations;
39
+ return this;
40
+ },
36
41
  });
37
42
  return { ...builder, data };
38
43
  }
@@ -176,8 +176,8 @@ function transformFunctionHandler(handlers, callSignature) {
176
176
  });
177
177
  return { gqlHandlerContent, lambdaFunctionDefinition };
178
178
  }
179
- function customOperationToGql(typeName, typeDef, authorization, isCustom = false, databaseType) {
180
- const { arguments: fieldArgs, returnType, functionRef, handlers, } = typeDef.data;
179
+ function customOperationToGql(typeName, typeDef, authorization, isCustom = false, databaseType, getRefType) {
180
+ const { arguments: fieldArgs, typeName: opType, returnType, functionRef, handlers, subscriptionSource, } = typeDef.data;
181
181
  let callSignature = typeName;
182
182
  const implicitModels = [];
183
183
  const { authString } = isCustom
@@ -218,6 +218,21 @@ function customOperationToGql(typeName, typeDef, authorization, isCustom = false
218
218
  else if (databaseType === 'sql' && handler && brand === 'sqlReference') {
219
219
  gqlHandlerContent = `@sql(reference: "${(0, Handler_1.getHandlerData)(handler)}") `;
220
220
  }
221
+ if (opType === 'Subscription') {
222
+ const subscriptionSources = subscriptionSource
223
+ .flatMap((source) => {
224
+ const refTarget = source.data.link;
225
+ const { type } = getRefType(refTarget, typeName);
226
+ if (type === 'CustomOperation') {
227
+ return refTarget;
228
+ }
229
+ if (type === 'Model') {
230
+ return source.data.mutationOperations.map((op) => `${op}${refTarget}`);
231
+ }
232
+ })
233
+ .join('", "');
234
+ gqlHandlerContent += `@aws_subscribe(mutations: ["${subscriptionSources}"]) `;
235
+ }
221
236
  const gqlField = `${callSignature}: ${returnTypeName} ${gqlHandlerContent}${authString}`;
222
237
  return { gqlField, models: implicitModels, lambdaFunctionDefinition };
223
238
  }
@@ -732,6 +747,31 @@ const extractFunctionSchemaAccess = (authRules) => {
732
747
  }
733
748
  return { schemaAuth, functionSchemaAccess };
734
749
  };
750
+ /**
751
+ * Returns a closure for retrieving reference type and definition from schema
752
+ */
753
+ const getRefTypeForSchema = (schema) => {
754
+ const getRefType = (source, target) => {
755
+ const typeDef = schema.data.types[source];
756
+ if (typeDef === undefined) {
757
+ throw new Error(`Invalid ref. ${target} is referencing ${source} which is not defined in the schema`);
758
+ }
759
+ if (isInternalModel(typeDef)) {
760
+ return { type: 'Model', def: typeDef };
761
+ }
762
+ if (isCustomOperation(typeDef)) {
763
+ return { type: 'CustomOperation', def: typeDef };
764
+ }
765
+ if (isCustomType(typeDef)) {
766
+ return { type: 'CustomType', def: typeDef };
767
+ }
768
+ if (isEnumType(typeDef)) {
769
+ return { type: 'Enum', def: typeDef };
770
+ }
771
+ throw new Error(`Invalid ref. ${target} is referencing ${source} which is neither a Model, Custom Operation, Custom Type, or Enum`);
772
+ };
773
+ return getRefType;
774
+ };
735
775
  const schemaPreprocessor = (schema) => {
736
776
  const gqlModels = [];
737
777
  const customQueries = [];
@@ -746,6 +786,7 @@ const schemaPreprocessor = (schema) => {
746
786
  const fkFields = allImpliedFKs(schema);
747
787
  const topLevelTypes = Object.entries(schema.data.types);
748
788
  const { schemaAuth, functionSchemaAccess } = extractFunctionSchemaAccess(schema.data.authorization);
789
+ const getRefType = getRefTypeForSchema(schema);
749
790
  for (const [typeName, typeDef] of topLevelTypes) {
750
791
  validateAuth(typeDef.data?.authorization);
751
792
  const mostRelevantAuthRules = typeDef.data?.authorization?.length > 0
@@ -773,7 +814,7 @@ const schemaPreprocessor = (schema) => {
773
814
  }
774
815
  else if (isCustomOperation(typeDef)) {
775
816
  const { typeName: opType } = typeDef.data;
776
- const { gqlField, models, jsFunctionForField, lambdaFunctionDefinition, } = transformCustomOperations(typeDef, typeName, mostRelevantAuthRules, databaseType);
817
+ const { gqlField, models, jsFunctionForField, lambdaFunctionDefinition, } = transformCustomOperations(typeDef, typeName, mostRelevantAuthRules, databaseType, getRefType);
777
818
  lambdaFunctions = lambdaFunctionDefinition;
778
819
  topLevelTypes.push(...models);
779
820
  if (jsFunctionForField) {
@@ -849,8 +890,8 @@ const schemaPreprocessor = (schema) => {
849
890
  lambdaFunctions,
850
891
  };
851
892
  };
852
- function validateCustomOperations(typeDef, typeName, authRules) {
853
- const { functionRef, handlers } = typeDef.data;
893
+ function validateCustomOperations(typeDef, typeName, authRules, getRefType) {
894
+ const { functionRef, handlers, typeName: opType, subscriptionSource, returnType, } = typeDef.data;
854
895
  // TODO: remove `functionRef` after deprecating
855
896
  const handlerConfigured = functionRef !== null || handlers?.length;
856
897
  const authConfigured = authRules.length > 0;
@@ -873,6 +914,57 @@ function validateCustomOperations(typeDef, typeName, authRules) {
873
914
  throw new Error(`Field handlers must be of the same type. ${typeName} has been configured with ${configuredHandlersStr}`);
874
915
  }
875
916
  }
917
+ if (opType === 'Subscription') {
918
+ if (subscriptionSource.length < 1) {
919
+ throw new Error(`${typeName} is missing a mutation source. Custom subscriptions must reference a mutation source via subscription().for(a.ref('ModelOrOperationName')) `);
920
+ }
921
+ subscriptionSource.forEach((source) => {
922
+ const sourceName = source.data.link;
923
+ const { type, def } = getRefType(sourceName, typeName);
924
+ if (type !== 'Model' && source.data.mutationOperations.length > 0) {
925
+ throw new Error(`Invalid subscription definition. .mutations() modifier can only be used with a Model ref. ${typeName} is referencing ${type}`);
926
+ }
927
+ if (type === 'Model' && source.data.mutationOperations.length === 0) {
928
+ 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`);
929
+ }
930
+ if (type === 'CustomOperation' && def.data.typeName !== 'Mutation') {
931
+ throw new Error(`Invalid subscription definition. .for() can only reference a mutation. ${typeName} is referencing ${sourceName} which is a ${def.data.typeName}`);
932
+ }
933
+ // Ensure subscription return type matches the return type of triggering mutation(s)
934
+ // TODO: when we remove .returns() for custom subscriptions, minor changes will be needed here. Instead of comparing subscriptionSource return val
935
+ // to a root returnType, we'll need to ensure that each subscriptionSource has the same return type
936
+ if (returnType.data.type === 'ref') {
937
+ const returnTypeName = returnType.data.link;
938
+ if (type === 'Model') {
939
+ if (returnTypeName !== sourceName ||
940
+ returnType.data.array !== source.data.array) {
941
+ throw new Error(`Invalid subscription definition. Subscription return type must match the return type of the mutation triggering it. ${typeName} is referencing ${sourceName} which has a different return type`);
942
+ }
943
+ }
944
+ if (type === 'CustomOperation') {
945
+ const customOperationReturnType = def.data.returnType.data.link;
946
+ const customOperationReturnTypeArray = def.data.returnType.data.array;
947
+ if (returnTypeName !== customOperationReturnType ||
948
+ returnType.data.array !== customOperationReturnTypeArray) {
949
+ throw new Error(`Invalid subscription definition. Subscription return type must match the return type of the mutation triggering it. ${typeName} is referencing ${sourceName} which has a different return type`);
950
+ }
951
+ }
952
+ }
953
+ else if (returnType.data.fieldType !== undefined) {
954
+ if (type === 'Model') {
955
+ throw new Error(`Invalid subscription definition. Subscription return type must match the return type of the mutation triggering it. ${typeName} is referencing ${sourceName} which has a different return type`);
956
+ }
957
+ if (type === 'CustomOperation') {
958
+ const customOperationReturnType = def.data.returnType.data.fieldType;
959
+ const customOperationReturnTypeArray = def.data.returnType.data.array;
960
+ if (returnType.data.fieldType !== customOperationReturnType ||
961
+ returnType.data.array !== customOperationReturnTypeArray) {
962
+ throw new Error(`Invalid subscription definition. Subscription return type must match the return type of the mutation triggering it. ${typeName} is referencing ${sourceName} which has a different return type`);
963
+ }
964
+ }
965
+ }
966
+ });
967
+ }
876
968
  }
877
969
  const isCustomHandler = (handler) => {
878
970
  return Array.isArray(handler) && (0, util_1.getBrand)(handler[0]) === 'customHandler';
@@ -933,15 +1025,15 @@ const handleCustom = (handlers, opType, typeName) => {
933
1025
  };
934
1026
  return jsFn;
935
1027
  };
936
- function transformCustomOperations(typeDef, typeName, authRules, databaseType) {
1028
+ function transformCustomOperations(typeDef, typeName, authRules, databaseType, getRefType) {
937
1029
  const { typeName: opType, handlers } = typeDef.data;
938
1030
  let jsFunctionForField = undefined;
939
- validateCustomOperations(typeDef, typeName, authRules);
1031
+ validateCustomOperations(typeDef, typeName, authRules, getRefType);
940
1032
  if (isCustomHandler(handlers)) {
941
1033
  jsFunctionForField = handleCustom(handlers, opType, typeName);
942
1034
  }
943
1035
  const isCustom = Boolean(jsFunctionForField);
944
- const { gqlField, models, lambdaFunctionDefinition } = customOperationToGql(typeName, typeDef, authRules, isCustom, databaseType);
1036
+ const { gqlField, models, lambdaFunctionDefinition } = customOperationToGql(typeName, typeDef, authRules, isCustom, databaseType, getRefType);
945
1037
  return { gqlField, models, jsFunctionForField, lambdaFunctionDefinition };
946
1038
  }
947
1039
  function generateCustomOperationTypes({ queries, mutations, subscriptions, }) {
@@ -1,6 +1,5 @@
1
1
  import { schema } from './ModelSchema';
2
2
  import { model } from './ModelType';
3
- import { modelIndex } from './ModelIndex';
4
3
  import { id, string, integer, float, boolean, date, time, datetime, timestamp, email, json, phone, url, ipAddress } from './ModelField';
5
4
  import { ref } from './RefType';
6
5
  import { hasOne, hasMany, belongsTo, manyToMany } from './ModelRelationalField';
@@ -9,4 +8,4 @@ import { customType } from './CustomType';
9
8
  import { enumType } from './EnumType';
10
9
  import { query, mutation, subscription } from './CustomOperation';
11
10
  import { handler } from './Handler';
12
- export { schema, model, modelIndex as index, ref, customType, enumType as enum, query, mutation, subscription, hasOne, hasMany, belongsTo, manyToMany, allow, id, string, integer, float, boolean, date, time, datetime, timestamp, email, json, phone, url, ipAddress, handler, };
11
+ export { schema, model, ref, customType, enumType as enum, query, mutation, subscription, hasOne, hasMany, belongsTo, manyToMany, allow, id, string, integer, float, boolean, date, time, datetime, timestamp, email, json, phone, url, ipAddress, handler, };
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handler = exports.ipAddress = exports.url = exports.phone = exports.json = exports.email = exports.timestamp = exports.datetime = exports.time = exports.date = exports.boolean = exports.float = exports.integer = exports.string = exports.id = exports.allow = exports.manyToMany = exports.belongsTo = exports.hasMany = exports.hasOne = exports.subscription = exports.mutation = exports.query = exports.enum = exports.customType = exports.ref = exports.index = exports.model = exports.schema = void 0;
3
+ exports.handler = exports.ipAddress = exports.url = exports.phone = exports.json = exports.email = exports.timestamp = exports.datetime = exports.time = exports.date = exports.boolean = exports.float = exports.integer = exports.string = exports.id = exports.allow = exports.manyToMany = exports.belongsTo = exports.hasMany = exports.hasOne = exports.subscription = exports.mutation = exports.query = exports.enum = exports.customType = exports.ref = exports.model = exports.schema = void 0;
4
4
  const ModelSchema_1 = require("./ModelSchema");
5
5
  Object.defineProperty(exports, "schema", { enumerable: true, get: function () { return ModelSchema_1.schema; } });
6
6
  const ModelType_1 = require("./ModelType");
7
7
  Object.defineProperty(exports, "model", { enumerable: true, get: function () { return ModelType_1.model; } });
8
- const ModelIndex_1 = require("./ModelIndex");
9
- Object.defineProperty(exports, "index", { enumerable: true, get: function () { return ModelIndex_1.modelIndex; } });
10
8
  const ModelField_1 = require("./ModelField");
11
9
  Object.defineProperty(exports, "id", { enumerable: true, get: function () { return ModelField_1.id; } });
12
10
  Object.defineProperty(exports, "string", { enumerable: true, get: function () { return ModelField_1.string; } });