I tried this: ```export default function main(app...
# guide
r
I tried this:
Copy code
export default function main(app) {
  app.setDefaultFunctionProps({
    runtime: "nodejs16.x",
    srcPath: "backend",
    bundle: {
      format: "esm",
    },
  })

  const storageStack = new StorageStack(app, "storage")

  app.stack(storageStack).stack(ApiStack)
}
but now get:
Copy code
Stacks: Building changes...
Stacks: Synthesizing changes...

TypeError: fn is not a function
    at stack (/home/rudi/projects/notes/node_modules/@serverless-stack/resources/src/FunctionalStack.ts:23:19)
    at App.stack (/home/rudi/projects/notes/node_modules/@serverless-stack/resources/src/App.ts:389:17)
    at Object.main (/home/rudi/projects/notes/stacks/index.js:19:7)
    at Object.<anonymous> (/home/rudi/projects/notes/.build/run.js:94:16)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
t
Hey you don't need to do
new StorageStack
if you are using functional stacks
how is StorageStack defined?
if it's a class you can remove
app.stack(storageStack).stack(ApiStack)
our guide is transitioning to our new way of defining stacks so might be a bit off
r
Copy code
import * as sst from "@serverless-stack/resources"

export default class StorageStack extends sst.Stack {
  // Public reference to the bucket
  bucket

  // Public reference to the table
  table

  constructor(scope, id, props) {
    super(scope, id, props)

    // Create the DynamoDB table
    this.table = new sst.Table(this, "Notes", {
      fields: {
        userId: sst.TableFieldType.STRING,
        noteId: sst.TableFieldType.STRING,
      },
      primaryIndex: { partitionKey: "userId", sortKey: "noteId" },
    })

    // Create an S3 bucket
    this.bucket = new sst.Bucket(this, "Uploads")
  }
}
Exported class
t
yep can remove that
app.stack(...)
line
r
ApiStack is set how then?
Copy code
import StorageStack from "./StorageStack"
import { ApiStack } from "./ApiStack"

// export default function main(app) {
//   new StorageStack(app, "storage")
// }

export default function main(app) {
  app.setDefaultFunctionProps({
    runtime: "nodejs16.x",
    srcPath: "backend",
    bundle: {
      format: "esm",
    },
  })

  new StorageStack(app, "storage")
}
t
Copy code
new ApiStack(...)
r
OK .. so it's not chained functions calls, at least for now in the docs
t
if you'd like to use the new way of defining stacks (functional stacks) you can checkout this document: https://docs.serverless-stack.com/constructs/Stack and this video:

https://www.youtube.com/watch?v=cqzgAJvUQCg

r
Copy code
import StorageStack from "./StorageStack"
import { ApiStack } from "./ApiStack"

// export default function main(app) {
//   new StorageStack(app, "storage")
// }

export default function main(app) {
  app.setDefaultFunctionProps({
    runtime: "nodejs16.x",
    srcPath: "backend",
    bundle: {
      format: "esm",
    },
  })

  new StorageStack(app, "storage")
  new ApiStack(app, "api")
}
t
sorry it's a bit confusing because we're transitioning but the video lays it out in a way that should be clear
r
No probs .. I'd like to do the guide as is with JS, then do it again with TS I'm also a fan of anything functional
will follow the video, thx!
FYI, I did the guide some time again with the Serverless Framework, I like the CDK style better.
This blows up:
Copy code
import { Api, use } from "@serverless-stack/resources"
import StorageStack from "./StorageStack"

export function ApiStack({ stack, app }) {
  const { table } = use(StorageStack)
with:
Copy code
Stacks: Building changes...
Stacks: Synthesizing changes...

Error: No app is set
    at use (/home/rudi/projects/notes/node_modules/@serverless-stack/resources/src/FunctionalStack.ts:43:26)
    at new ApiStack (/home/rudi/projects/notes/stacks/ApiStack.js:5:21)
    at Object.main (/home/rudi/projects/notes/stacks/index.js:18:3)
    at Object.<anonymous> (/home/rudi/projects/notes/.build/run.js:94:16)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
    at internal/main/run_main_module.js:17:47
so I'll need to alter that bit too...
ApiStack wants the functional StorageStack looks like
k
Copy code
export function ApiStack({ stack, app }) {
 var api = new Api(stack, 'api', {
    routes: {
      'GET /': 'functions/lambda.handler'
    }
  });
  stack.addOutputs({ apiUrl: api.url });
  return api;
}
r
@Klaus but it's the StorageStack that needs to refactor from an exported Class to Function
k
@Rudi https://docs.serverless-stack.com/constructs/Table should provide more examples if you use DynamoDB. A simple one is below MyStacks.ts:
Copy code
import {
  Table,
  Function,
  Api,
  use,
  StackContext
} from '@serverless-stack/resources';

export function FuncStack({ stack, app }: StackContext) {
  const fn = new Function(stack, `myFunc`, {
    handler: 'lambda.handler',
    srcPath: 'backend/functions'
  });
  return fn;
}

export function TableStack({ stack, app }: StackContext) {
  var fn = use(FuncStack);
  var tbl = new Table(stack, 'Notes', {
    fields: {
      noteId: 'string'
    },
    stream: true,
    primaryIndex: { partitionKey: 'noteId' },
    consumers: {
      consumer1: fn
    }
  });
  fn.addEnvironment('Table', tbl.tableArn);
  return tbl;
}

export function ApiStack({ stack, app }: StackContext) {
  var fn = use(FuncStack);
  var api = new Api(stack, 'api', {
    routes: {
      'GET /': fn
    }
  });
  stack.addOutputs({ apiUrl: api.url });
  return api;
}
index.ts
Copy code
import { FuncStack, TableStack, ApiStack } from './MyStacks';
import * as sst from '@serverless-stack/resources';
export default function (app: <http://sst.App|sst.App>) {
  app.setDefaultFunctionProps({
    runtime: 'python3.8',
    srcPath: 'backend',
    memorySize: 512
    // environment: { ...env_vars }
  });
  app.stack(FuncStack).stack(TableStack).stack(ApiStack);
}
r
@Klaus wow, thanks!
Hi, I've updated the guide StorageStack to be functional and the application deploys OK now:
Copy code
import * as sst from "@serverless-stack/resources"

/**
 export default class StorageStack extends sst.Stack {
   // Public reference to the bucket
  bucket
  
  // Public reference to the table
  table
  
  constructor(scope, id, props) {
    super(scope, id, props)
    
    // Create the DynamoDB table
    this.table = new sst.Table(this, "Notes", {
      fields: {
        userId: sst.TableFieldType.STRING,
        noteId: sst.TableFieldType.STRING,
      },
      primaryIndex: { partitionKey: "userId", sortKey: "noteId" },
    })
    
    // Create an S3 bucket
    this.bucket = new sst.Bucket(this, "Uploads")
  }
}
*/

export function StorageStack(ctx) {
  // Create the DynamoDB table
  const table = new sst.Table(ctx.stack, "Notes", {
    fields: {
      userId: sst.TableFieldType.STRING,
      noteId: sst.TableFieldType.STRING,
    },
    primaryIndex: { partitionKey: "userId", sortKey: "noteId" },
  })

  // Create an S3 bucket
  const bucket = new sst.Bucket(ctx.stack, "Uploads")

  return { table, bucket }
}
But I get an unset env TABLE_NAME error:
Copy code
23a8a76b-5a9f-4701-82ca-f1a69d7debdc RESPONSE {"statusCode":500,"body":"{\"error\":\"Missing required key 'TableName' in params\"}"}
I've tried debugging but am still stuck .. here you can see me trying some things within the ApiStack
Copy code
import { StorageStack } from "./StorageStack"
import { Api, use } from "@serverless-stack/resources"

export function ApiStack({ stack, app }) {
  const { table } = use(StorageStack)

  console.log(table)
  console.log("tn", table.tableName)

  // Create the API
  const api = new Api(stack, "Api", {
    defaults: {
      function: {
        permissions: [table],
        environment: {
          // TABLE_NAME: table.tableName,
          TABLE_NAME: "Notes",
        },
      },
    },
    routes: {
      "POST /notes": "functions/create.main",
    },
  })

  // Show the API endpoint in the output
  stack.addOutputs({
    ApiEndpoint: api.url,
  })

  // Return the API resource
  return {
    api,
  }
}
Not sure where I'm going wrong?
Some console logging output:
Copy code
tableArn: '${Token[TOKEN.204]}',
    tableName: '${Token[TOKEN.206]}',
    tableStreamArn: undefined,
    scalingRole: <ref *3> Import {
      node: [Node],
      stack: [EmptyStack],
      env: [Object],
      _physicalName: undefined,
      _allowCrossEnvironment: false,
      physicalName: '${Token[TOKEN.207]}',
      grantPrincipal: [Circular *3],
      principalAccount: '771316991410',
      assumeRoleAction: 'sts:AssumeRole',
      policyFragment: [PrincipalPolicyFragment],
      roleArn: 'arn:${Token[AWS.Partition.10]}:iam::771316991410:role/aws-service-role/dynamodb.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_DynamoDBTable',
      roleName: 'AWSServiceRoleForApplicationAutoScaling_DynamoDBTable',
      attachedPolicies: [AttachedPolicies],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    tablePartitionKey: { name: 'userId', type: 'S' },
    tableSortKey: { name: 'noteId', type: 'S' },
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  },
  [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [ [Circular *1] ] }
}
tn ${Token[TOKEN.206]}

Stacks: No changes to deploy.
23a8a76b-5a9f-4701-82ca-f1a69d7debdc REQUEST rudijs-notes-ApiStack-ApiLambdaPOSTnotes3B067E1B-XatYmYjQpgBB [functions/create.main] invoked by API POST /notes
TABLE_NAME undefined
23a8a76b-5a9f-4701-82ca-f1a69d7debdc RESPONSE {"statusCode":500,"body":"{\"error\":\"Missing required key 'TableName' in params\"}"}
process.env.TABLE_NAME is undefined in create.js
Copy code
import * as uuid from "uuid"
import AWS from "aws-sdk"

const dynamoDb = new AWS.DynamoDB.DocumentClient()

export async function main(event) {
  console.log("TABLE_NAME", process.env.TABLE_NAME)
  // Request body is passed in as a JSON encoded string in 'event.body'
  const data = JSON.parse(event.body)

  const params = {
    TableName: process.env.TABLE_NAME,
    Item: {
      // The attributes of the item to be created
      userId: "123", // The id of the author
      noteId: uuid.v1(), // A unique uuid
      content: data.content, // Parsed from request body
      attachment: data.attachment, // Parsed from request body
      createdAt: Date.now(), // Current Unix timestamp
    },
  }

  try {
    await dynamoDb.put(params).promise()

    return {
      statusCode: 200,
      body: JSON.stringify(params.Item),
    }
  } catch (e) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: e.message }),
    }
  }
}
k
@Rudi What happens if you replace
Copy code
const api = new Api(stack, "Api", {
    defaults: {
      function: {
        permissions: [table],
        environment: {
          // TABLE_NAME: table.tableName,
          TABLE_NAME: "Notes",
        },
      },
    },
by
Copy code
const api = new Api(stack, "Api", {
    defaults: {
      function: {
        permissions: [table, bucket], // <-- also grant bucket permissions 
        environment: {
          TABLE_NAME: table.tableName, // <-- set the table name correctly
          // TABLE_NAME: "Notes",
        },
      },
    },
the console log suggest the table reference is working, but it doesn't seem to be set in the environment correctly
r
I've tried both, same undefined
It's odd I know.
k
it works for me here
r
Hmmm, I'll remove all and start again.
k
are you on latest SST? If yes, I'd remove the stack completely + delete s3 / DynamoDB + try again
r
When I remove the stack, I have to manually remove s3 bucket and dynamodb table right?
k
yes- the happend to me at least - I think there is a way to configure auto-removal, but that needs to be in the code when creating
r
I'll do that in a few minutes, just stepping out for a quick bite to eat.
t
serverless first, food after!
Copy code
if (!["prod", "stage"].includes(app.stage))
    app.setDefaultRemovalPolicy("destroy")
fyi for auto removal
k
@thdxr when working
serverless
it's hard getting food to be served by someone else😜
r
Yap, server less food today. Prepared it myself 😊
Hmm .. remove and redeployed, still no joy. There must be something small I'm overlooking.
Copy code
export function StorageStack(ctx) {
  // Create the DynamoDB table
  const table = new sst.Table(ctx.stack, "Notes", {
    fields: {
      userId: sst.TableFieldType.STRING,
      noteId: sst.TableFieldType.STRING,
    },
    primaryIndex: { partitionKey: "userId", sortKey: "noteId" },
  })

  // Create an S3 bucket
  const bucket = new sst.Bucket(ctx.stack, "Uploads")

  return { table, bucket }
}
..
Copy code
export function ApiStack({ stack, app }) {
  const { table } = use(StorageStack)

  // Create the API
  const api = new Api(stack, "Api", {
    defaults: {
      function: {
        permissions: [table],
        environment: {
          TABLE_NAME: table.tableName,
        },
      },
    },
    routes: {
      "POST /notes": "functions/create.main",
    },
  })

  // Show the API endpoint in the output
  stack.addOutputs({
    ApiEndpoint: api.url,
  })

  // Return the API resource
  return {
    api,
  }
}
..
Copy code
import { StorageStack } from "./StorageStack"
import { ApiStack } from "./ApiStack"

// export default function main(app) {
//   new StorageStack(app, "storage")
// }

export default function main(app) {
  app.setDefaultFunctionProps({
    runtime: "nodejs14.x",
    srcPath: "src/backend",
    bundle: {
      format: "esm",
    },
  })

  if (!["prod", "stage"].includes(app.stage)) app.setDefaultRemovalPolicy("destroy")

  app.stack(StorageStack).stack(ApiStack)
}
output:
Copy code
==========================
 Starting Live Lambda Dev
==========================

SST Console: <https://console.serverless-stack.com/notes/rudijs/local>
Debug session started. Listening for requests...
82f2897d-06fd-4b64-9727-f2048beac283 REQUEST rudijs-notes-ApiStack-ApiLambdaPOSTnotes3B067E1B-YqzFbYWPVrdr [functions/create.main] invoked by API POST /notes
TABLE_NAME undefined
82f2897d-06fd-4b64-9727-f2048beac283 RESPONSE {"statusCode":500,"body":"{\"error\":\"Missing required key 'TableName' in params\"}"}
It deploys OK, but env is not being set.
I can share the source code in github if that helps. I'm just following along the guide and have refactored the StorageStack from class to function.
t
Yeah can you share in GitHub
r
Copy code
npx sst --version                                                                                                                                                                         main
SST: 0.69.7
CDK: 2.15.0
npx@latest ?
k
I'd install SST at the latest version
t
yeah looks like the package.json doesn't have latest
it's 1.2.3
in latest we use string union so this should be
Copy code
userId: "string",
      noteId: "string",
otherwise it looks good to me
k
npm init sst
should create a repo with latest SST
r
Ok, will update sst version and remove/deploy ...
t
shouldn't have to remove btw
just sst start after should do it
k
or try
npx sst update
and check you package.json
r
Copy code
npx sst update                                                                                                                                                                  
Updating @serverless-stack/cli to latest
Updating @serverless-stack/resources to latest
Updating aws-cdk-lib@2.24.0
SST: 1.2.3
CDK: 2.24.0
@thdxr yep the older STRING failed using the updated sst version
Copy code
TypeError: Cannot read property 'STRING' of undefined
updating to "string" and re-deploying ..
Hmm..
Copy code
===============
 Deploying app
===============


ReferenceError: require is not defined
    at App.applyRemovalPolicy (file:///home/rudi/projects/notes/node_modules/@serverless-stack/resources/dist/App.js:245:42)
    at file:///home/rudi/projects/notes/node_modules/@serverless-stack/resources/dist/App.js:276:58
    at Array.forEach (<anonymous>)
t
@Rudi can you actually remove the removal policy line
we just did a big migration from cjs -> esm of the codebase, working out some kinks
r
sure, one sec
deployed OK, but did notice this:
Copy code
===============                                                                                       
 Deploying app                                                                                                                                                                                               
===============                                                                                       
                                                                                                      

/home/rudi/projects/notes/node_modules/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.d.ts (672,15): Cannot find name 'Set'. Do you need to change your target library? Try changing the 'lib' compiler o
ption to 'es2015' or later.
670.      */                                                                                                                                                                                                 
671.     _metrics: Set<GroupMetric>;                                                                  
672.     constructor(...metrics: GroupMetric[]);                                                      
Deploying stacks
testing the API now
Sweet!
I'll continue with the Guide now, I'm still early on just at the first API part: https://serverless-stack.com/chapters/add-an-api-to-create-a-note.html
And for now have left out:
Copy code
// if (!["prod", "stage"].includes(app.stage)) app.setDefaultRemovalPolicy("destroy")
but looking forward to when that's good to go again
@thdxr @Klaus Many thanks for your assistance and valuable time.
k
When I tried the default removal, I had the same issue before. Then removed the old stack manually and on the next
sst start
it worked fine.
So perhaps the policy is stored + somehow clashes when there are existing assets without it
t
I just pushed a commit to fix it, will release soon