@aws-amplify/data-schema 1.5.1 → 1.6.1
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/dist/cjs/CustomOperation.js +21 -0
- package/dist/cjs/CustomOperation.js.map +1 -1
- package/dist/cjs/Handler.js +20 -1
- package/dist/cjs/Handler.js.map +1 -1
- package/dist/cjs/SchemaProcessor.js +54 -11
- package/dist/cjs/SchemaProcessor.js.map +1 -1
- package/dist/cjs/runtime/internals/APIClient.js +3 -0
- package/dist/cjs/runtime/internals/APIClient.js.map +1 -1
- package/dist/esm/ClientSchema/Core/ClientCustomOperations.d.ts +2 -2
- package/dist/esm/CustomOperation.d.ts +16 -4
- package/dist/esm/CustomOperation.mjs +21 -1
- package/dist/esm/CustomOperation.mjs.map +1 -1
- package/dist/esm/Handler.d.ts +57 -9
- package/dist/esm/Handler.mjs +20 -1
- package/dist/esm/Handler.mjs.map +1 -1
- package/dist/esm/SchemaProcessor.mjs +54 -12
- package/dist/esm/SchemaProcessor.mjs.map +1 -1
- package/dist/esm/runtime/internals/APIClient.mjs +3 -0
- package/dist/esm/runtime/internals/APIClient.mjs.map +1 -1
- package/dist/meta/cjs.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/ClientSchema/Core/ClientCustomOperations.ts +32 -3
- package/src/CustomOperation.ts +68 -5
- package/src/Handler.ts +92 -21
- package/src/SchemaProcessor.ts +73 -23
- package/src/runtime/internals/APIClient.ts +3 -1
package/dist/esm/Handler.mjs
CHANGED
|
@@ -60,6 +60,7 @@ function custom(customHandler) {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
const functionHandlerBrand = 'functionHandler';
|
|
63
|
+
const asyncFunctionHandlerBrand = 'asyncFunctionHandler';
|
|
63
64
|
/**
|
|
64
65
|
* Use a function created via `defineFunction` to handle the custom query/mutation/subscription. In your function handler,
|
|
65
66
|
* you can use the `Schema["YOUR_QUERY_OR_MUTATION_NAME"]["functionHandler"]` utility type to type the handler function.
|
|
@@ -96,7 +97,25 @@ const functionHandlerBrand = 'functionHandler';
|
|
|
96
97
|
* @returns A handler for the query / mutation / subscription
|
|
97
98
|
*/
|
|
98
99
|
function fcn(fn) {
|
|
99
|
-
return {
|
|
100
|
+
return {
|
|
101
|
+
[dataSymbol]: {
|
|
102
|
+
handler: fn,
|
|
103
|
+
invocationType: 'RequestResponse',
|
|
104
|
+
},
|
|
105
|
+
async() {
|
|
106
|
+
return _async(this);
|
|
107
|
+
},
|
|
108
|
+
...buildHandler(functionHandlerBrand),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function _async(fnHandler) {
|
|
112
|
+
return {
|
|
113
|
+
[dataSymbol]: {
|
|
114
|
+
handler: fnHandler[dataSymbol].handler,
|
|
115
|
+
invocationType: 'Event',
|
|
116
|
+
},
|
|
117
|
+
...buildHandler(asyncFunctionHandlerBrand),
|
|
118
|
+
};
|
|
100
119
|
}
|
|
101
120
|
//#endregion
|
|
102
121
|
const handler = {
|
package/dist/esm/Handler.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Handler.mjs","sources":["../../src/Handler.ts"],"sourcesContent":["import { brand } from './util';\nconst dataSymbol = Symbol('Data');\nfunction buildHandler(brandName) {\n return brand(brandName);\n}\nexport function getHandlerData(handler) {\n return handler[dataSymbol];\n}\n//#region handler.inlineSql\nconst inlineSqlBrand = 'inlineSql';\nfunction inlineSql(sql) {\n return { [dataSymbol]: sql, ...buildHandler(inlineSqlBrand) };\n}\n//#endregion\n//#region handler.sqlReference\nconst sqlReferenceBrand = 'sqlReference';\nfunction sqlReference(sqlFilePath) {\n // used to determine caller directory in order to resolve relative path downstream\n const stack = new Error().stack;\n return {\n [dataSymbol]: { stack, entry: sqlFilePath },\n ...buildHandler(sqlReferenceBrand),\n };\n}\nconst customHandlerBrand = 'customHandler';\n/**\n * Use a custom JavaScript resolver to handle a query, mutation, or subscription.\n * @see {@link https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/#step-2---configure-custom-business-logic-handler-code}\n * @param customHandler `{ entry: \"path-to-javascript-resolver-file.js\", dataSource: \"Data Source name added via \"backend.data.add*DataSoruce(...)\"}`\n * @returns A JavaScript resolver attached to the query, mutation, or subscription.\n * @example\n * const schema = a.schema({\n * Post: a.model({\n * content: a.string(),\n * likes: a.integer()\n * .authorization(allow => [allow.authenticated().to(['read'])])\n * }).authorization(allow => [\n * allow.owner(),\n * allow.authenticated().to(['read'])\n * ]),\n *\n * likePost: a\n * .mutation()\n * .arguments({ postId: a.id() })\n * .returns(a.ref('Post'))\n * .authorization(allow => [allow.authenticated()])\n * .handler(a.handler.custom({\n * dataSource: a.ref('Post'),\n * entry: './increment-like.js'\n * }))\n * });\n */\nfunction custom(customHandler) {\n // used to determine caller directory in order to resolve relative path downstream\n const stack = new Error().stack;\n return {\n [dataSymbol]: { ...customHandler, stack },\n ...buildHandler(customHandlerBrand),\n };\n}\nconst functionHandlerBrand = 'functionHandler';\n/**\n * Use a function created via `defineFunction` to handle the custom query/mutation/subscription. In your function handler,\n * you can use the `Schema[\"YOUR_QUERY_OR_MUTATION_NAME\"][\"functionHandler\"]` utility type to type the handler function.\n * @example\n * import {\n * type ClientSchema,\n * a,\n * defineData,\n * defineFunction // 1.Import \"defineFunction\" to create new functions\n * } from '@aws-amplify/backend';\n *\n * // 2. define a function\n * const echoHandler = defineFunction({\n * entry: './echo-handler/handler.ts'\n * })\n *\n * const schema = a.schema({\n * EchoResponse: a.customType({\n * content: a.string(),\n * executionDuration: a.float()\n * }),\n *\n * echo: a\n * .query()\n * .arguments({ content: a.string() })\n * .returns(a.ref('EchoResponse'))\n * .authorization(allow => [allow.publicApiKey()])\n * // 3. set the function has the handler\n * .handler(a.handler.function(echoHandler))\n * });\n * @see {@link https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/}\n * @param fn A function created via `defineFunction`. Alternatively, you can pass in a \"string\" of the function name and pass\n * in a corresponding value into the `functionMap` property of defineData.\n * @returns A handler for the query / mutation / subscription\n */\nfunction fcn(fn) {\n return {
|
|
1
|
+
{"version":3,"file":"Handler.mjs","sources":["../../src/Handler.ts"],"sourcesContent":["import { brand } from './util';\nconst dataSymbol = Symbol('Data');\nfunction buildHandler(brandName) {\n return brand(brandName);\n}\nexport function getHandlerData(handler) {\n return handler[dataSymbol];\n}\n//#region handler.inlineSql\nconst inlineSqlBrand = 'inlineSql';\nfunction inlineSql(sql) {\n return { [dataSymbol]: sql, ...buildHandler(inlineSqlBrand) };\n}\n//#endregion\n//#region handler.sqlReference\nconst sqlReferenceBrand = 'sqlReference';\nfunction sqlReference(sqlFilePath) {\n // used to determine caller directory in order to resolve relative path downstream\n const stack = new Error().stack;\n return {\n [dataSymbol]: { stack, entry: sqlFilePath },\n ...buildHandler(sqlReferenceBrand),\n };\n}\nconst customHandlerBrand = 'customHandler';\n/**\n * Use a custom JavaScript resolver to handle a query, mutation, or subscription.\n * @see {@link https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/#step-2---configure-custom-business-logic-handler-code}\n * @param customHandler `{ entry: \"path-to-javascript-resolver-file.js\", dataSource: \"Data Source name added via \"backend.data.add*DataSoruce(...)\"}`\n * @returns A JavaScript resolver attached to the query, mutation, or subscription.\n * @example\n * const schema = a.schema({\n * Post: a.model({\n * content: a.string(),\n * likes: a.integer()\n * .authorization(allow => [allow.authenticated().to(['read'])])\n * }).authorization(allow => [\n * allow.owner(),\n * allow.authenticated().to(['read'])\n * ]),\n *\n * likePost: a\n * .mutation()\n * .arguments({ postId: a.id() })\n * .returns(a.ref('Post'))\n * .authorization(allow => [allow.authenticated()])\n * .handler(a.handler.custom({\n * dataSource: a.ref('Post'),\n * entry: './increment-like.js'\n * }))\n * });\n */\nfunction custom(customHandler) {\n // used to determine caller directory in order to resolve relative path downstream\n const stack = new Error().stack;\n return {\n [dataSymbol]: { ...customHandler, stack },\n ...buildHandler(customHandlerBrand),\n };\n}\nconst functionHandlerBrand = 'functionHandler';\nconst asyncFunctionHandlerBrand = 'asyncFunctionHandler';\n/**\n * Use a function created via `defineFunction` to handle the custom query/mutation/subscription. In your function handler,\n * you can use the `Schema[\"YOUR_QUERY_OR_MUTATION_NAME\"][\"functionHandler\"]` utility type to type the handler function.\n * @example\n * import {\n * type ClientSchema,\n * a,\n * defineData,\n * defineFunction // 1.Import \"defineFunction\" to create new functions\n * } from '@aws-amplify/backend';\n *\n * // 2. define a function\n * const echoHandler = defineFunction({\n * entry: './echo-handler/handler.ts'\n * })\n *\n * const schema = a.schema({\n * EchoResponse: a.customType({\n * content: a.string(),\n * executionDuration: a.float()\n * }),\n *\n * echo: a\n * .query()\n * .arguments({ content: a.string() })\n * .returns(a.ref('EchoResponse'))\n * .authorization(allow => [allow.publicApiKey()])\n * // 3. set the function has the handler\n * .handler(a.handler.function(echoHandler))\n * });\n * @see {@link https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/}\n * @param fn A function created via `defineFunction`. Alternatively, you can pass in a \"string\" of the function name and pass\n * in a corresponding value into the `functionMap` property of defineData.\n * @returns A handler for the query / mutation / subscription\n */\nfunction fcn(fn) {\n return {\n [dataSymbol]: {\n handler: fn,\n invocationType: 'RequestResponse',\n },\n async() {\n return _async(this);\n },\n ...buildHandler(functionHandlerBrand),\n };\n}\nfunction _async(fnHandler) {\n return {\n [dataSymbol]: {\n handler: fnHandler[dataSymbol].handler,\n invocationType: 'Event',\n },\n ...buildHandler(asyncFunctionHandlerBrand),\n };\n}\n//#endregion\nexport const handler = {\n inlineSql,\n sqlReference,\n custom,\n function: fcn,\n};\n"],"names":[],"mappings":";;AACA,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAClC,SAAS,YAAY,CAAC,SAAS,EAAE;AACjC,IAAI,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC;AAC5B,CAAC;AACM,SAAS,cAAc,CAAC,OAAO,EAAE;AACxC,IAAI,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;AAC/B,CAAC;AACD;AACA,MAAM,cAAc,GAAG,WAAW,CAAC;AACnC,SAAS,SAAS,CAAC,GAAG,EAAE;AACxB,IAAI,OAAO,EAAE,CAAC,UAAU,GAAG,GAAG,EAAE,GAAG,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;AAClE,CAAC;AACD;AACA;AACA,MAAM,iBAAiB,GAAG,cAAc,CAAC;AACzC,SAAS,YAAY,CAAC,WAAW,EAAE;AACnC;AACA,IAAI,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC;AACpC,IAAI,OAAO;AACX,QAAQ,CAAC,UAAU,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE;AACnD,QAAQ,GAAG,YAAY,CAAC,iBAAiB,CAAC;AAC1C,KAAK,CAAC;AACN,CAAC;AACD,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,MAAM,CAAC,aAAa,EAAE;AAC/B;AACA,IAAI,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC;AACpC,IAAI,OAAO;AACX,QAAQ,CAAC,UAAU,GAAG,EAAE,GAAG,aAAa,EAAE,KAAK,EAAE;AACjD,QAAQ,GAAG,YAAY,CAAC,kBAAkB,CAAC;AAC3C,KAAK,CAAC;AACN,CAAC;AACD,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAC/C,MAAM,yBAAyB,GAAG,sBAAsB,CAAC;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,GAAG,CAAC,EAAE,EAAE;AACjB,IAAI,OAAO;AACX,QAAQ,CAAC,UAAU,GAAG;AACtB,YAAY,OAAO,EAAE,EAAE;AACvB,YAAY,cAAc,EAAE,iBAAiB;AAC7C,SAAS;AACT,QAAQ,KAAK,GAAG;AAChB,YAAY,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AAChC,SAAS;AACT,QAAQ,GAAG,YAAY,CAAC,oBAAoB,CAAC;AAC7C,KAAK,CAAC;AACN,CAAC;AACD,SAAS,MAAM,CAAC,SAAS,EAAE;AAC3B,IAAI,OAAO;AACX,QAAQ,CAAC,UAAU,GAAG;AACtB,YAAY,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC,OAAO;AAClD,YAAY,cAAc,EAAE,OAAO;AACnC,SAAS;AACT,QAAQ,GAAG,YAAY,CAAC,yBAAyB,CAAC;AAClD,KAAK,CAAC;AACN,CAAC;AACD;AACY,MAAC,OAAO,GAAG;AACvB,IAAI,SAAS;AACb,IAAI,YAAY;AAChB,IAAI,MAAM;AACV,IAAI,QAAQ,EAAE,GAAG;AACjB;;;;"}
|
|
@@ -2,7 +2,7 @@ import { ModelFieldType, string } from './ModelField.mjs';
|
|
|
2
2
|
import { ModelRelationshipTypes } from './ModelRelationalField.mjs';
|
|
3
3
|
import { accessData, accessSchemaData } from './Authorization.mjs';
|
|
4
4
|
import { CustomOperationNames } from './CustomOperation.mjs';
|
|
5
|
-
import { getBrand } from './util/Brand.mjs';
|
|
5
|
+
import { getBrand, brandSymbol } from './util/Brand.mjs';
|
|
6
6
|
import { getHandlerData } from './Handler.mjs';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import * as path from 'path';
|
|
@@ -149,19 +149,23 @@ function transformFunctionHandler(handlers, functionFieldName) {
|
|
|
149
149
|
const lambdaFunctionDefinition = {};
|
|
150
150
|
handlers.forEach((handler, idx) => {
|
|
151
151
|
const handlerData = getHandlerData(handler);
|
|
152
|
-
if (typeof handlerData === 'string') {
|
|
153
|
-
gqlHandlerContent += `@function(name: "${handlerData}") `;
|
|
152
|
+
if (typeof handlerData.handler === 'string') {
|
|
153
|
+
gqlHandlerContent += `@function(name: "${handlerData.handler}") `;
|
|
154
154
|
}
|
|
155
|
-
else if (typeof handlerData.getInstance === 'function') {
|
|
155
|
+
else if (typeof handlerData.handler.getInstance === 'function') {
|
|
156
156
|
const fnName = `Fn${capitalize(functionFieldName)}${idx === 0 ? '' : `${idx + 1}`}`;
|
|
157
|
-
lambdaFunctionDefinition[fnName] = handlerData;
|
|
158
|
-
|
|
157
|
+
lambdaFunctionDefinition[fnName] = handlerData.handler;
|
|
158
|
+
const invocationTypeArg = handlerData.invocationType === 'Event' ? ', invocationType: Event)' : ')';
|
|
159
|
+
gqlHandlerContent += `@function(name: "${fnName}"${invocationTypeArg} `;
|
|
159
160
|
}
|
|
160
161
|
else {
|
|
161
162
|
throw new Error(`Invalid value specified for ${functionFieldName} handler.function(). Expected: defineFunction or string.`);
|
|
162
163
|
}
|
|
163
164
|
});
|
|
164
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
gqlHandlerContent,
|
|
167
|
+
lambdaFunctionDefinition,
|
|
168
|
+
};
|
|
165
169
|
}
|
|
166
170
|
function customOperationToGql(typeName, typeDef, authorization, isCustom = false, databaseType, getRefType) {
|
|
167
171
|
const { arguments: fieldArgs, typeName: opType, returnType, handlers, subscriptionSource, } = typeDef.data;
|
|
@@ -814,6 +818,18 @@ const schemaPreprocessor = (schema) => {
|
|
|
814
818
|
? 'dynamodb'
|
|
815
819
|
: 'sql';
|
|
816
820
|
const staticSchema = databaseType === 'sql';
|
|
821
|
+
// If the schema contains a custom operation with an async lambda handler,
|
|
822
|
+
// we need to add the EventInvocationResponse custom type to the schema.
|
|
823
|
+
// This is done here so that:
|
|
824
|
+
// - it only happens once per schema
|
|
825
|
+
// - downstream validation based on `getRefTypeForSchema` finds the EventInvocationResponse type
|
|
826
|
+
const containsAsyncLambdaCustomOperation = Object.entries(schema.data.types).find(([_, typeDef]) => {
|
|
827
|
+
return isCustomOperation(typeDef)
|
|
828
|
+
&& finalHandlerIsAsyncFunctionHandler(typeDef.data.handlers);
|
|
829
|
+
});
|
|
830
|
+
if (containsAsyncLambdaCustomOperation) {
|
|
831
|
+
schema.data.types['EventInvocationResponse'] = eventInvocationResponseCustomType;
|
|
832
|
+
}
|
|
817
833
|
const topLevelTypes = sortTopLevelTypes(Object.entries(schema.data.types));
|
|
818
834
|
const { schemaAuth, functionSchemaAccess } = extractFunctionSchemaAccess(schema.data.authorization);
|
|
819
835
|
const getRefType = getRefTypeForSchema(schema);
|
|
@@ -976,14 +992,22 @@ function validateCustomOperations(typeDef, typeName, authRules, getRefType) {
|
|
|
976
992
|
configuredHandlers.add(getBrand(handler));
|
|
977
993
|
}
|
|
978
994
|
if (configuredHandlers.size > 1) {
|
|
979
|
-
|
|
980
|
-
|
|
995
|
+
configuredHandlers.delete('asyncFunctionHandler');
|
|
996
|
+
configuredHandlers.delete('functionHandler');
|
|
997
|
+
if (configuredHandlers.size > 0) {
|
|
998
|
+
const configuredHandlersStr = JSON.stringify(Array.from(configuredHandlers));
|
|
999
|
+
throw new Error(`Field handlers must be of the same type. ${typeName} has been configured with ${configuredHandlersStr}`);
|
|
1000
|
+
}
|
|
981
1001
|
}
|
|
982
1002
|
}
|
|
983
1003
|
if (typeDef.data.returnType === null &&
|
|
984
1004
|
(opType === 'Query' || opType === 'Mutation' || opType === 'Generation')) {
|
|
985
|
-
|
|
986
|
-
|
|
1005
|
+
// TODO: There should be a more elegant and readable way to handle this check.
|
|
1006
|
+
// Maybe it's not even necessary anymore since we're the setting returnType in the handler() method.
|
|
1007
|
+
if (!handlers || handlers.length === 0 || handlers[handlers.length - 1][brandSymbol] !== 'asyncFunctionHandler') {
|
|
1008
|
+
const typeDescription = opType === 'Generation' ? 'Generation Route' : `Custom ${opType}`;
|
|
1009
|
+
throw new Error(`Invalid ${typeDescription} definition. A ${typeDescription} must include a return type. ${typeName} has no return type specified.`);
|
|
1010
|
+
}
|
|
987
1011
|
}
|
|
988
1012
|
if (opType !== 'Subscription' && subscriptionSource.length > 0) {
|
|
989
1013
|
throw new Error(`The .for() modifier function can only be used with a custom subscription. ${typeName} is not a custom subscription.`);
|
|
@@ -1038,7 +1062,10 @@ const isCustomHandler = (handler) => {
|
|
|
1038
1062
|
return Array.isArray(handler) && getBrand(handler[0]) === 'customHandler';
|
|
1039
1063
|
};
|
|
1040
1064
|
const isFunctionHandler = (handler) => {
|
|
1041
|
-
return Array.isArray(handler) && getBrand(handler[0])
|
|
1065
|
+
return Array.isArray(handler) && ['functionHandler', 'asyncFunctionHandler'].includes(getBrand(handler[0]));
|
|
1066
|
+
};
|
|
1067
|
+
const finalHandlerIsAsyncFunctionHandler = (handler) => {
|
|
1068
|
+
return Array.isArray(handler) && getBrand(handler[handler.length - 1]) === 'asyncFunctionHandler';
|
|
1042
1069
|
};
|
|
1043
1070
|
const normalizeDataSourceName = (dataSource) => {
|
|
1044
1071
|
// default data source
|
|
@@ -1092,6 +1119,21 @@ const handleCustom = (handlers, opType, typeName) => {
|
|
|
1092
1119
|
};
|
|
1093
1120
|
return jsFn;
|
|
1094
1121
|
};
|
|
1122
|
+
const eventInvocationResponseCustomType = {
|
|
1123
|
+
data: {
|
|
1124
|
+
fields: {
|
|
1125
|
+
success: {
|
|
1126
|
+
data: {
|
|
1127
|
+
fieldType: ModelFieldType.Boolean,
|
|
1128
|
+
required: true,
|
|
1129
|
+
array: false,
|
|
1130
|
+
arrayRequired: false,
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
type: 'customType'
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1095
1137
|
function transformCustomOperations(typeDef, typeName, authRules, databaseType, getRefType) {
|
|
1096
1138
|
const { typeName: opType, handlers } = typeDef.data;
|
|
1097
1139
|
let jsFunctionForField = undefined;
|