Skip to content

heycalmdown/faas-spec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

faas-spec

A library that auto-generates CDK code from TypeSpec API definitions. Used together with faas-stack for type-safe Lambda deployment.


Installation

npm install faas-spec

Quick Start

1. Write TypeSpec File

// typespec/faas/main.tsp
import "@typespec/http";
import "faas-spec";

using TypeSpec.Http;
using FaasSuite.FaasSpec;

@service(#{ title: "faas-my-service" })
@route("/my-service")
namespace MyService;

@post
@route("/vendors/{vendorId}/signup")
op BuyerSignUp(@path vendorId: string, @body body: SignUpRequest): SignUpResponse;

2. Generate faas-spec.ts

npm run tsp  # Compile TypeSpec

3. Use in lambda-stack.ts

import * as APIs from './faas-spec'

export class LambdaStack extends FaasStack {
  constructor(scope: App, id: string, props?: StackProps) {
    super(scope, id, props, SERVICE, STAGE)

    APIs.BuyerSignUp(this, envs())  // Type-safe!
  }
}

Problems faas-spec Solves

1. Prevents Path and Filename Mismatch

Before: Error-prone

// Typo 1: Missing hyphen in filename
this.post('/vendors/{vendorId}/signup', 'buyer-signup.ts', envs())
// Actual file: buyer-sign-up.ts → Runtime error

// Typo 2: Path typo
this.post('/vendors/{vendorId}/signpu', 'buyer-sign-up.ts', envs())
// signpu vs signup → API call fails

After: TypeSpec ensures consistency

@post
@route("/vendors/{vendorId}/signup")
op BuyerSignUp(@path vendorId: string, ...): ...;
// Auto-generated - typos impossible
export const BuyerSignUp = defineSpec(
  'BuyerSignUp',
  'buyer-sign-up.ts',  // Auto-generated from operation name
  'post',
  '/vendors/{vendorId}/signup',
  'node'
)

2. Prevents HTTP Method Mistakes

Before: Method mistakes

// Registered as POST when it should be GET
this.post('/products/{id}', 'get-product.ts', envs())

After: TypeSpec decorators determine method

@get
@route("/products/{id}")
op GetProduct(@path id: string): Product;
// Correct method used automatically
export const GetProduct = defineSpec('GetProduct', 'get-product.ts', 'get', ...)

3. Build-time File Existence Validation

Before: Errors discovered after deployment

this.post('/path', 'non-existent-file.ts', envs())
// CDK synth succeeds, deployment fails

After: Error at CDK synth time

APIs.NonExistentOperation(this)
// Error: File "./src/non-existent-operation.ts" not found by "NonExistentOperation"
// Discovered immediately before deployment!

4. IDE Autocomplete Support

Before: No autocomplete for strings

this.post('/vendors/{vendorId}/signup', 'buyer-sign-up.ts', envs())

After: Autocomplete works for functions

APIs.  // ← Ctrl+Space shows all available APIs

5. Function Metadata Access

APIs.BuyerSignUp(this, envs())

// Access created function
APIs.BuyerSignUp.func      // IFunction
APIs.BuyerSignUp.filename  // 'buyer-sign-up.ts'

// Usage example
const topic = this.findTopic('notifications')
topic.grantPublish(APIs.BuyerSignUp.func!)

faas-spec Decorators

Event Handlers

// SQS queue subscription
@event("sqs")
op OnSqsOrderCreated(@body body: OrderEvent): void;
// → Queue name: faas-{service}-order-created

// Kafka topic subscription (future support)
@event("kafka")
op OnKafkaOrderChange(@body body: OrderEvent): void;

Scheduled Tasks

// Cron expression
@schedule
@cron(#{ hour: "20", minute: "15" })  // Daily at 20:15 UTC
op OnScheduleCleanup(): {};

// Rate-based
@schedule
@rate("1h")   // Every hour
op OnScheduleHourly(): {};

@schedule
@rate("1d")   // Every day
op OnScheduleDaily(): {};

@schedule
@rate("5m")   // Every 5 minutes
op OnScheduleFrequent(): {};

Rate units:

Unit Description Example
m Minutes "5m" = 5 minutes
h Hours "1h" = 1 hour
d Days "1d" = 1 day

Language Selection

// Default: Node.js (TypeScript)
@get
@route("/products")
op GetProducts(): Product[];
// → File: src/get-products.ts

// Go language
@lang("go")
@get
@route("/hello")
op Hello(): HelloResponse;
// → File: go/hello/main.go

Generation Rules

Operation Name → Filename Conversion

Operation Filename (Node.js) Filename (Go)
GetProducts get-products.ts get-products/main.go
BuyerSignUp buyer-sign-up.ts buyer-sign-up/main.go
OnSqsOrderCreated on-sqs-order-created.ts -
OnScheduleDaily on-schedule-daily.ts -

Conversion rule: PascalCase → kebab-case

SQS Queue Name Extraction

@event("sqs")
op OnSqsOrderCreated(...): void;
//      ~~~~~~~~~~~~
//      Queue name extracted from this part: order-created

Actual queue ARN: arn:aws:sqs:{region}:{accountId}:faas-{service}-order-created


Complete Example

TypeSpec Definition

// typespec/faas/main.tsp
import "@typespec/http";
import "faas-spec";

using TypeSpec.Http;
using FaasSuite.FaasSpec;

@service(#{ title: "faas-order" })
@route("/order")
namespace Order;

// HTTP APIs
@get @route("/vendors/{vendorId}/orders")
op ListOrders(@path vendorId: string): Order[];

@post @route("/vendors/{vendorId}/orders")
op CreateOrder(@path vendorId: string, @body body: CreateOrderRequest): Order;

// Event Handler
@event("sqs")
op OnSqsOrderCreated(@body body: OrderCreatedEvent): void;

// Scheduled Tasks
@schedule @cron(#{ hour: "2", minute: "0" })
op OnScheduleOrderCleanup(): {};

@schedule @rate("1h")
op OnScheduleOrderStats(): {};

Generated faas-spec.ts

// GET /order/vendors/{vendorId}/orders
export const ListOrders = defineSpec('ListOrders', 'list-orders.ts', 'get', '/vendors/{vendorId}/orders', 'node')

// POST /order/vendors/{vendorId}/orders
export const CreateOrder = defineSpec('CreateOrder', 'create-order.ts', 'post', '/vendors/{vendorId}/orders', 'node')

// EVENT(sqs) - faas-order-order-created
export const OnSqsOrderCreated = defineSqsSpec('OnSqsOrderCreated', 'on-sqs-order-created.ts', 'order-created')

// SCHEDULE - order-cleanup
export const OnScheduleOrderCleanup = defineScheduleSpec('OnScheduleOrderCleanup', 'on-schedule-order-cleanup.ts', Schedule.cron({"minute":"0","hour":"2"}))

// SCHEDULE - order-stats
export const OnScheduleOrderStats = defineScheduleSpec('OnScheduleOrderStats', 'on-schedule-order-stats.ts', Schedule.rate(Duration.hours(1)))

lambda-stack.ts

import * as APIs from './faas-spec'

export class LambdaStack extends FaasStack {
  constructor(scope: App, id: string, props?: StackProps) {
    super(scope, id, props, SERVICE, STAGE, { shareFaasSecurityGroup: true, awsv3: true })

    // External API
    APIs.CreateOrder(this, envs())

    // Internal API
    this.switchToPrivateGateway()
    APIs.ListOrders(this, envs())

    // Event & Schedule
    APIs.OnSqsOrderCreated(this, envs())
    APIs.OnScheduleOrderCleanup(this, envs())
    APIs.OnScheduleOrderStats(this, envs())

    // Grant permissions
    const table = this.findTable('Orders')
    for (const name in this.functionCache) {
      table.grantReadWriteData(this.functionCache[name])
    }
  }
}

Validation and Error Messages

faas-spec validates the following at TypeSpec compile time:

Error Code Description
event-before-route @event must be used after @route
invalid-route-path @event route must start with /events/{type}
invalid-operation-name @event operation name must start with On{Type}
schedule-requires-cron-or-rate @schedule requires @cron or @rate
invalid-rate-unit Rate unit must be m/h/d

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors