Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
Slaven Ivanov authored and Slaven Ivanov committed Aug 12, 2022
0 parents commit 9804714
Show file tree
Hide file tree
Showing 13 changed files with 7,508 additions and 0 deletions.
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Build output
lib

# Dependency directories
node_modules/

# OSX
.DS_Store

# Logs
logs
*.log
npm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage
*.lcov

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Stores VSCode versions used for testing VSCode extensions
.vscode-test
244 changes: 244 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# AWS Lambda Utility Package

AWS Lambda validations made easy 🏄‍♀️

```typescript
class SpamBot {
@Handler()
static async handle(@Body() { email }: BodyType) {
sendSpam(email) // -> Hi👋 ️👆 I'm validated
}
}

class BodyType {
@IsEmail
email: string
}
```

## Table of Contents

- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Decorators](#decorators)
- [Method Decorators](#method-decorators)
- [Function Param Decorators](#function-param-decorators)
- [Http Errors](#httperrors)
- [Http Responses](#httpresponses)

## Installation

First off we need to install the package

```shell
TODO!!! 🤯
```

Since we use ```class-validator``` under the hood we need to install it for its validation decorators

```shell
npm i class-validator
```

Next we need to enable these options in our ```.tsconfig``` file

```json
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
```

## Basic Usage

[//]: # (TODO account delete rename into something different for different iterations)

```typescript
class CustomBodyType {
@IsEmail()
email: string
}

class AccountDelete {
@Handler()
static async handle(@Body() { email }: CustomBodyType) {
await deleteAccount(email)
return ok()
}
}

export const handler = AccountDelete.handle
```

#### Let's break it down.

1. We create a class with the shape we expect ```CustomBodyType```
2. We decorate the properties we want validated with any of
the [decorators of class-validator](https://github.com/typestack/class-validator#validation-decorators)
e.g. ```@IsEmail()```
3. We create a class that would hold our handler method, in this case ```AccountDeleteHandler```
and ```static async handle(){}```
4. We decorate ```handle()``` with the ```@Handler()``` decorator
5. We decorate the method's parameter with ```@Body()``` and cast it to the expected shape i.e. ```CustomBodyType```
6. ???
7. We can readily use the automatically validated method parameter, in this case the 🅱️ody

#### Decorators can be mixed and matched:

```typescript
class KitchenSink {
@Handler()
static async handle(@Event() evt: APIGatewayProxyEventBase<T>,
@Ctx() ctx: Context,
@Body() body: BodyType,
@Paths() paths: PathsType,
@Queries() queries: QueriesType) {
return ok({ body, paths, queries, evt, ctx })
}
}
```

[//]: # (TODO show what happens when the request doesn't comply)

## Decorators

### Method Decorators

### ```@Handler()```

This decorator needs to be applied to the handler of our http event. The handler function **needs** to be `async` or
needs
to return a `Promise`.

```typescript
class AccountDelete {
@Handler()
static async handle() {
}
}
```

When applied, `@Handler()` enables the following:

1. Validation and injection of method parameters, decorated with [@Paths()](#paths), [@Body()](#body)
,[@Queries()](#queries) parameters
2. Injection of method parameters, decorated with [@Event()](#event) and [Ctx()](#ctx)
3. Out of the box error handling and custom error handling via throwing [HttpError](#httperror-)

## Validation and Injection

### Function Param Decorators

### ```@Event()```

Injects the `APIGatewayProxyEventBase<T>` object, passed on to the function at runtime.

```typescript
class AccountDelete {
@Handler()
static async handle(@Event() evt) {
}
}
```

### ```@Ctx()```

Injects the `Context` object, passed on to the function at runtime.

```typescript
class AccountDelete {
@Handler()
static async handle(@Ctx() context) {
}
}
```

### ```@Paths()```

Validates the http event's path parameters and injects them into the decorated method parameter.

For example a handler, attached to the path `/cars/{color}` ,would look like so:

```typescript
class PathType {
@IsHexColor()
color: string
}

class CarFetch {
@Handler()
static async handle(@Paths() paths: PathType) {
}
}
```

### ```@Body()```

Validates the http event's body and injects them it into the decorated method parameter.

```typescript
class BodyType {
@IsSemVer()
appVersion: string
}

class CarHandler {
@Handler()
static async handle(@Body() paths: BodyType) {
}
}
```

### ```@Queries()```

Validates the http event's query parameters and injects them into the decorated method parameter.

For example making a http request like this `/inflated?balloonId={someUUID}` would be handled like this:

```typescript
class QueriesType {
@IsUUID()
balloonId: string
}

class IsBalloonInflated {
@Handler()
static async handle(@Queries() queries: QueriesType) {
}
}
```

## HttpErrors

### ```HttpError ```

### `DynamoError`

## HttpResponses

```response(code: number, body?: object)```

```ok(body?: object)```

```ok(body?: object)```

```created(body?: object)```

```badRequest(body?: object)```

```unauthorized(body?: object)```

```notFound(body?: object)```

```imaTeapot(body?: object)```

```internalServerError(body?: object)```

# TODO

- [ ] Linting
- [ ] Prettier
- [ ] Documentation
- [ ] possibly cull undefined properties in validated objects
- [ ] rollup build to minify package
- [ ] non promise response functions
11 changes: 11 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Config } from '@jest/types'

const config: Config.InitialOptions = {
verbose: true,
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['./test'],
collectCoverage: true
}

export default config
Loading

0 comments on commit 9804714

Please sign in to comment.