Increase type safety
Feel free to reject this as something out of cloudform's scope:
It'd be great if cf.Fn.Ref() calls failed to compile if the referenced resource doesn't exist in the template.
I have an example project that deploys an ECS cluster and a service with cloudform: https://github.com/wolverian/ts-ecs-example. From writing it (and from using cloudform in another, private project), one of the biggest pain points I've experienced is making sure I give the correct string values to cf.Fn.Ref() and cf.Fn.GetAtt().
I don't have a concrete suggestion how to implement this, but any ideas are welcome.
Yes, I've been thinking about a solution for this and although I haven't published anything yet, I have some ideas. I need to weight whether the API complexity added is worth it. I'll elaborate soon.
By now, what I do is to use a dictionary object with all the resource names defined and reference these names through that object only. See const Resource in the example. But yes, this is only a convention, not a type safety measure.
By now, what I do is to use a dictionary object with all the resource names defined and reference these names through that object only. See const Resource in the example. But yes, this is only a convention, not a type safety measure.
That’s a good pattern, thanks!
After a bit of pondering, I think there has to be a type-level link between Ref and Template so that they can both be parametrised to accept only resources of the same type (where “same type” here is a type level restriction like keyof MyResources).
Yes, although requiring the API user to always specify that MyResources generic parameter would kill the usefulness for me.
cloudform<MyResources>({
Resources: {
x: {
Ref: Fn.Ref<MyResources>('x')
}
}
})
I'm now thinking about exposing that type-safe Ref via the typed callback function like this:
const MyResources = {
x: 'x'
}
cloudform<typeof MyResources>(context: (Context<typeof MyResources>) => ({
x: {
Ref: context.Ref('x')
}
}))
or maybe better by passing this MyResources definition in:
// cloudform<T>(resourcesDefinition: T, templateBuilder: (context: Context<T>) => Template)
cloudform(MyResources, context => ({
x: {
Ref: context.Ref('x')
}
}))
WDYT?
LGTM. I think I prefer the latter approach, as it looks simpler. I'll play around with this approach a bit and see how it works. I'll report if I bump into problems. I'd be happy to contribute a PR later, though it's fine if you implement this before I get around to it. 👍
I strongly recommend to add a linter step (we use cfn-lint) over the CloudForm result for additional validation. It's not perfect, but this can grab not only resource reference issues, but some other errors too.