faas-spec
v1.0.0
Published
A library that auto-generates CDK code from TypeSpec API definitions. Used together with [faas-stack](https://github.com/heycalmdown/faas-stack) for **type-safe Lambda deployment**.
Downloads
110
Readme
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-specQuick 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 TypeSpec3. 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 failsAfter: 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 failsAfter: 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 APIs5. 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.goGeneration 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-createdActual 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")
@tag("faas.plugo.world")
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 faas.plugo.town/order/vendors/{vendorId}/orders
export const ListOrders = defineSpec('ListOrders', 'list-orders.ts', 'get', '/vendors/{vendorId}/orders', 'node')
// POST faas.plugo.world/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 |
