Decorators on functions would be useful for frameworks that do basic reflection to infer configuration and bundle applications such as APIs, cloud functions, etc.
I think a good place to look at for examples is the Python ecosystem:
- Chalice uses decorators for wiring up function handlers to AWS resources
- Modal uses decorators to provision infrastructure and model the boundaries compute (different hosted functions)
@lambda()
async function myJob(): Promise<string> {
// does some work in a remote server somewhere
return "some result"
}
@schedule.every(1, "day")
export async function myJob() { .. }
@api(path="/my/api", method="GET")
@use(oAuth) // with middleware
export async function myApi(req) { .. }
@retry(maxAttempts=3)
export async function myJob(): Promise<string> { .. }
const queue = new Queue<string>("my-queue");
@queue.consumer()
export async function consume(message) {
message.content; // string
}
Need to be learned, are not type-safe.
// api/my/api/route.ts
export async function GET(req: NextRequest): Promise<NextResponse> { .. }
Are type-safe, but are verbose especially when chaining and cannot be hoisted
export const myJob = framework.function({
path
method: "GET"
}, async (request) => {
..
})
This is pretty standard practice:
export const myConsumer = queue.consume(message => {
//
})
But it's also very tempting to do this without exporting a function that can be bundled:
queue.consume(message => {
//
})
If TS support decorators, I think it's important for two type-level features to exist:
E.g. going back to the myJob
lambda Function, I want the decorator to augment the type so that i can extend its interface
@lambda()
async function myJob(): Promise<string> {
// does some work in a remote server somewhere
return "some result"
}
This can be called in 1 of 2 ways:
- synchronously call it and wait for the result
const result = await myJob()
- "spawn" as an asynchronous job with a handle to that job
const resultHandle = await myJob.spawn()
await resultHandle.checkResult();
// my decorator
function s3EventHandler() {
return function decorator(func: (event: S3Event) => { .. })
}
@s3EventHandler()
async function handler(event) {
// event is inferred to be of type S3Event
// i shouldn't have to add an explicit type
}