# Serverless.com project with Lerna and NPM workspaces

Hello. I'm writing a Lambda "tick" function to simulate the universe for my game. It's a different architecture than my previous idea of maintaining a 24/7 server, which simulates a busy real-time game loop. I wanted to give it a try.

In this post, I want to share the project structure I used, which consists of Lerna, and two packages: one for the game's logic and one for the [serverless.com](http://serverless.com) framework for deploying the Lambda. I had some fun with dependencies and Lambda not finding the game logic's dependency.

## Project Structure

Here's what my project looks like:

```plaintext
.
├── lerna.json
├── package-lock.json
├── package.json
└── packages
    ├── logic
    │   ├── build
    │   │   ├── index.d.ts
    │   │   └── index.js
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── src
    │   │   └── index.ts
    │   └── tsconfig.json
    └── serverless
        ├── functions
        │   └── handler.ts
        ├── package.json
        ├── serverless.yml
        └── tsconfig.json
```

## Game Logic Package Setup

Nothing too interesting here. I decided to understand CJS modules and ES modules' output this time. Targeting the Node 18.x runtime by AWS Lambda, I saw that ES modules are supported and decided to go with the future of everything by making TypeScript emit an ES module. It looks something like this:

```json
# PARTIAL tsconfig.json compiler options
{
  "target": "ES2016",
  "module": "Node16",
  "declaration": true,
  "outDir": "./build"
}
```

```json
# PARTIAL package.json, scripts/license/boring parts omitted
{
  "name": "@ags9/logic",
  "exports": {
    "import": "./build/index.js"
  },
  "types": "./build/index.d.ts",
  "type": "module",
  "scripts": {
    "test": "jest",
    "build": "tsc -p .",
    "start": "node build/index.js",
    "dev": "tsc -w -p ."
  },
}
```

Note the `"type": "module"` which tells Node to run `.js` files as ES modules. `"types"` for letting the importer of this package know it has nice types (`"declaration": true` in `tsconfig.json`, important for type-safety in the Lambda handler). The `exports.import` field is for telling ES module importers of `@ags9/logic` what to load. Notice the matching `"outDir"` path in `tsconfig.json`.

## Serverless Setup

I decided against using the `aws-nodejs-typescript` serverless template as it was too complicated and outdated. Instead, I used the regular `aws-nodejs` template and installed this [serverless-typescript plugin](https://www.serverless.com/plugins/serverless-plugin-typescript) for TypeScript support.

The interesting setup here would be two key aspects. First, the `tsconfig.json` which references the projects' packages. Second, the patterns for including/excluding the right files from the Lambda package by Serverless.

### How did I find out nothing works and why?

I used `sls package`, which gave me a zip file under the `.serverless` output folder. Examining the contents of the zip file, I noticed that no `node_modules` existed there, especially my `@ags9/logic` package. I then tried some combinations for the `package.patterns` in `serverless.yml` and eventually nailed the correct configuration, which looks like this:

```yaml
# part of serverless.yml

package:
  individually: true
  patterns:
    - ../../node_modules/@ags9/**
    - "!../../node_modules/@ags9/*/src/**"
    - "!../../node_modules/@ags9/serverless/**"
```

It's a bit confusing that in my filesystem I omitted `@ags9/` from the path, but inside `node_modules`, the correct package name from the `package.json` is used, e.g. `ags9/logic`.

## Next Steps For Me

1. Install regular NPM packages in both `logic` and `serverless` and ensure they are packaged correctly by Serverless.
    
2. Build a game.
