CDK GraphqlApi with Function Using Typescript Causes Undefined or Not Exported - aws-cdk

I have a aws_appsync.GraphqlApi with a lambda resolver:
import * as cdk from 'aws-cdk-lib';
import {aws_appsync} from 'aws-cdk-lib';
import {Construct} from 'constructs';
import {AttributeType, BillingMode, Table} from "aws-cdk-lib/aws-dynamodb";
import * as path from "path";
import {FieldLogLevel} from "aws-cdk-lib/aws-appsync";
import {RetentionDays} from "aws-cdk-lib/aws-logs";
import {Code, Function, Runtime} from "aws-cdk-lib/aws-lambda";
export class RelmStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const relmTable = new Table(this, 'relm', {
billingMode: BillingMode.PAY_PER_REQUEST,
tableName: 'relm',
partitionKey: {
name: 'pk',
type: AttributeType.STRING
}, sortKey: {
name: 'sk',
type: AttributeType.STRING
}
})
const api = new aws_appsync.GraphqlApi(this, 'relm-api', {
name: 'relm-api',
logConfig: {
fieldLogLevel: FieldLogLevel.ALL,
excludeVerboseContent: true,
retention: RetentionDays.TWO_WEEKS
},
schema: aws_appsync.SchemaFile.fromAsset(path.join(__dirname, 'schema.graphql')),
authorizationConfig: {
defaultAuthorization: {
authorizationType: aws_appsync.AuthorizationType.API_KEY,
apiKeyConfig: {
name: 'relm-api-key'
}
}
}
})
const createLambda = new Function(this, 'dialog-create', {
functionName: 'dialog-create',
runtime: Runtime.NODEJS_14_X,
handler: 'index.handler',
code: Code.fromAsset('src/lambda'),
memorySize: 3008,
})
const createDataSource = api.addLambdaDataSource('create-data-source', createLambda)
createDataSource.createResolver('create-resolver', {
typeName: 'Mutation',
fieldName: 'dialogCreate'
});
relmTable.grantWriteData(createLambda);
}
}
The sources lives under src/lambda/index.ts and the code is as follows:
exports.handler = async (event: any) => {
console.log('event: ', event)
};
Super simple. When the file extension is index.js everything works. When I change it to index.ts I get an error:
"index.handler is undefined or not exported"
I've looked at many examples on how to do this and all of them seem to be using the ts extension with no problems. What am I doing wrong here?

You should use the NodejsFunction which includes transpiling TypeScript to JavaScript.
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs-readme.html

You can write your handler in typescript but you'll have to invoke a build script to transpile it into javascript to serve as your lambda handler.
This project uses cdk and tsc:
https://github.com/mavi888/cdk-typescript-lambda/blob/main/package.json
This line discusses using esbuild to transpile:
https://docs.aws.amazon.com/lambda/latest/dg/typescript-package.html

Related

Pipeline stack which uses cross-environment actions must have an explicitly set region

I am trying to create an AWS Code Pipeline to create a DynamoDB table in one of the stages. I was able to successfully deploy the same code with CDK v1. now trying to replicate the same on CDK v2. I am getting an error Pipeline stack which uses cross-environment actions must have an explicitly set region
Here's the complete code:
import { Stack, StackProps, Stage, StageProps } from "aws-cdk-lib";
import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";
import {
CodePipeline,
CodePipelineSource,
ShellStep,
} from "aws-cdk-lib/pipelines";
import { Construct } from "constructs";
export class DdbStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new Table(this, "TestTable", {
partitionKey: { name: "id", type: AttributeType.STRING },
});
}
}
class MyApplication extends Stage {
constructor(scope: Construct, id: string, props?: StageProps) {
super(scope, id, props);
new DdbStack(this, `${id}-ddb`, {});
}
}
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const pipeline = new CodePipeline(this, `${id}-PipelineStack-`, {
crossAccountKeys: true,
selfMutation: false,
pipelineName: "MangokulfiCDK",
synth: new ShellStep("Synth", {
input: CodePipelineSource.connection("gowtham91m/mango-cdk", "main", {
connectionArn:
"arn:aws:codestar-connections:us-west-2:147866640792:connection/4b18bea2-9eb6-47b1-bbdc-adb3bf6fd2a9",
}),
commands: ["npm ci", "npm run build", "npx cdk synth"],
}),
});
pipeline.addStage(
new MyApplication(this, `Staging`, {
env: {
account: "123456789123",
region: "us-west-2",
},
})
);
pipeline.buildPipeline();
}
}

esbuild How to build a product without a filesystem

I want esbuild to automatically handle the duplicate imports in the file for me, and I also want to use its tree shaking capabilities to help me optimise my code and then output it to a file, I wrote the following code to try and do the above, but I could never get it right
export interface ConfigSource extends Partial<Options> {
code: string
name: string
loader: Loader
}
export interface Config {
sources: ConfigSource[]
options?: BuildOptions
}
function babelBuildWithBundle(main: ConfigSource, config: Config) {
const buildModuleRuntime: Plugin = {
name: 'buildModuleRuntime',
setup(build) {
build.onResolve({ filter: /\.\// }, (args) => {
return { path: args.path, namespace: 'localModule' }
})
build.onLoad({ filter: /\.\//, namespace: 'localModule' }, (args) => {
const source = config.sources.find(
(source) =>
source.name.replace(/\..+$/, '') === args.path.replace(/^\.\//, '')
)
const content = source?.code || ''
return {
contents: content,
loader: source?.loader || 'js',
}
})
},
}
return build(
{
stdin: {
contents: main.code,
loader: main.loader,
sourcefile: main.name,
resolveDir: path.resolve('.'),
},
bundle: true,
write: false,
format: 'esm',
outdir: 'dist',
plugins: [buildModuleRuntime],
}
)
}
const foo = `
export const Foo = FC(() => {
return <div>gyron</div>
})
`
const app = `
import { Foo } from './foo'
function App(): any {
console.log(B)
return <Foo />
}
`
const bundle = await babelBuildWithBundle(
{
loader: 'tsx',
code: app,
name: 'app.tsx',
},
{
sources: [
{
loader: 'tsx',
code: foo,
name: 'foo.tsx',
},
],
}
)
Then the only answer I got in the final output was
console.log(bundle.outputFiles[0].text)
`
// localModule:. /foo
var Foo = FC(() => {
return /* #__PURE__ */ React.createElement("div", null, "gyron");
});
`
console.log(bundle.outputFiles[1])
`
undefined
`
I have just tried setting treeShaking to false and can pack the app into the product.

CDK and batch build

I am trying to have cypress tests run in parallel in codepipeline/codebuild as documented here: https://docs.cypress.io/guides/continuous-integration/aws-codebuild#Parallelizing-the-build
I am reading aws docs here:
https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-list
This is what I have got so far:
import * as cdk from '#aws-cdk/core';
import * as codebuild from '#aws-cdk/aws-codebuild';
import * as iam from '#aws-cdk/aws-iam';
import { defaultEnvironment, NODE_JS_VERSION } from './environments/base-environment';
import { projectEnvironmentVars } from './environments/e2e-tests-project-environment';
// import { createReportGroupJsonObject } from '../../utils/utils';
// import { Duration } from '#aws-cdk/core'
interface RunTestsProjectProps {
testsBucketName: string,
testsBucketArn: string,
targetEnv: string,
repoType: string,
role: iam.Role,
codeCovTokenArn: string,
}
export class RunTestsProject extends codebuild.PipelineProject {
constructor(scope: cdk.Construct, id: string, props: RunTestsProjectProps) {
const { testsBucketName, testsBucketArn, targetEnv, repoType, codeCovTokenArn } = props
super(scope, id, {
projectName: id,
role: props.role,
environment: defaultEnvironment,
environmentVariables: projectEnvironmentVars({ testsBucketName, testsBucketArn, targetEnv, repoType, codeCovTokenArn }),
timeout: cdk.Duration.hours(3),
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
install: {
'runtime-versions': {
nodejs: NODE_JS_VERSION
}
},
build: {
commands: [
'if [ ! -f "${CODEBUILD_SRC_DIR}/scripts/assume-cross-account-role.env" ]; then echo "assume-cross-account-this.role.env not found in repo" && aws s3 cp s3://${ARTIFACTS_BUCKET_NAME}/admin/cross-account/assume-cross-account-role.env ${CODEBUILD_SRC_DIR}/scripts/; else echo "Overriding assume-cross-account-role.env from repo"; fi',
'. ${CODEBUILD_SRC_DIR}/scripts/assume-cross-account-role.env',
'bash ${CODEBUILD_SRC_DIR}/scripts/final-tests.sh'
],
batch: {
'fail-fast': false,
'build-list': []
}
},
},
artifacts: {
files: '**/*'
},
})
});
}
}
what should I add in the build-list part to have multiple builds running
I tried
'build-list': { identifier: 'build1', identifier: 'build2' }
but this looks like incorrect syntax.
The number of builds should ideally be based on the cypress grouping. Can it be dynamic or has to be defined statically?

vue apollo composite oe batch queries -- possible?

Has anyone found away of doing composite queries with Vue Apollo (Apollo boost)?
A composite batch query
I have an array if ids [ 12, 34, 56, 76, 76, …] and for each id I need to send a graphQL query. I could end up with a 500 queries being called one after the other.
Instead I want to batch them (or send them all at the same time) using aliases. Something like
[
first: User(id: "12") {
name
email
},
second: User(id: "34") {
name
email
},
....
....
oneHundred: User(id: "34") {
name
email
}
]
With the results being popped into an array. E.g.
this.users.pop(second)
I’ve done a fair bit of reading and searching. I found this that hints that it can be done
enter link description here
Any help out there?
The answer is in 3 parts.
Does the server recognise batch requests? Apollo server does by default
Set up the client in your app to handle batches
Create the queries on the fly.
Creating a Vue Apollo client:
import VueApollo from 'vue-apollo'
import Vue from 'vue';
import { ApolloClient } from 'apollo-client'
import { ApolloLink, split } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { BatchHttpLink } from 'apollo-link-batch-http';
import { getMainDefinition } from 'apollo-utilities'
const httpLink = new HttpLink({ uri: HTTP_END_POINT });
const batchLink = new BatchHttpLink({ uri: HTTP_END_POINT });
const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
httpLink,
batchLink,
)
const client = new ApolloClient({
link: ApolloLink.from([link]),
cache: new InMemoryCache(),
connectToDevTools: true
})
Example of sending of a batch of queries inside a Vue component:
methods: {
fetchUsers() {
[<a list of user ids>].forEach(id => {
this.$apollo.query({
query: USER_QUERY,
variables: { id },
}).then(d => {
console.table(d.data.User)
this.users.push(d.data.User)
}).catch(e => console.log(e));
});
},
}
The actual query looks like:
import gql from "graphql-tag";
export const USER_QUERY = gql`
query User($id: String!) {
User(id: $id) {
firstName
lastName
}
}

How do rollup externals and globals work with esm targets

I have a question for all you Rollup gurus. I’m struggling with externals and globals. If I have a rollup.config.js like this:
const external = ['hyperhtml'];
const globals = {
'hyperhtml': 'hyperHTML'
};
export default [
{
external,
input: 'src/foo-bar.mjs',
plugins: [
],
output: {
file: 'dist/foo-bar.mjs',
format: 'es',
globals,
paths: {
hyperhtml: '../node_modules/hyperhtml/min.js'
},
}
},
];
And the entry (foo-bar.mjs) looks like this:
import { hyper } from '../node_modules/hyperhtml/min.js';
class FooBar extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.render();
}
disconnectedCallback() {}
render() {
hyper(this.shadowRoot)`
<div>something</div>
`;
}
}
customElements.get('foo-bar.mjs') || customElements.define('foo-bar.mjs', FooBar);
export { FooBar };
I would expect Rollup to replace the import {hyper} from ‘hyperhtml’ statement in the generated bundle with something like const {hyper} = hyperHTML but it doesn’t. Instead the bundle file looks like is the same as the source file. Can someone explain why?
If I remember correctly globals only works on iife modules and by extension umd ones, try with rollup-plugin-virtual (https://github.com/rollup/rollup-plugin-virtual)
export default [
{
input: 'src/foo-bar.mjs',
plugins: [
virtual ({
'node_modules/hyperhtml/min.js': `
export const hyper = someGlobalNamespace.hyperhtml.hyper;
`
})
],
output: {
file: 'dist/foo-bar.mjs',
format: 'es'
}
},
];
Not sure if it will work though...

Resources