(no title)
drostie | 7 years ago
I can definitely confirm for others reading this that it indeed has been something like 10 years in the making; I was working with a prototype of it 8-9 years ago and found those data models so nice that I actually reimplemented the core idea in a repository on GitHub, though it didn't really go anywhere except for my own web site. I have also been able to reimplement it in TypeScript more recently, so that there is a non-Turing-complete subset of algebraic data types (though maybe I'll be able to add a fixpoint operator, who knows) as runtime objects with highly specific TypeScript types that are inferred from the functions you use to construct them. So then a parametric TypeScript construct,
ValueOfType<typeof mySchema>
embraces values that match the schema that you just specified. You can use this trick to write functions like myHTTPRouter.get('/objects/by-id/:objectguid', {
params: {
objectguid: {
type: 'guid'
}
},
async handler(params) {
// inside of here, params has type {objectguid: string},
// and VSCode knows this, because params is ValueOfType<schema> where
// the schema is specified in the `params` key above.
return response.json({success: true})
}
})
It's a really fun perspective on programming to have these schemas available at both runtime and compile-time, very DRY.
mcintyre1994|7 years ago
drostie|7 years ago
The `string` type here comes from a mapping that the router is using. That is, the router ultimately type-evaluates a `ValueOfType<{type: 'guid'}>` to `string`. But because it's a runtime object, the router can also, at runtime, validate that URL param, "did they actually give me a UUID?" -- and sanitize it, e.g. "convert all UUIDs to lowercase."
(In fact the benefit of having this TypeScript type at runtime is even bigger than that. With Express.js, the router can rewrite the route param so that the route doesn't even match if you don't provide a UUID, which matters because there is often a lot of accidental ambiguity in HTTP APIs -- but here you can embed the UUID regex into Express paths. The router can then also do some other trickery like confirm at initial load time that all params in URLs match params in this `params` dict, and it can convert all of its routes to OpenAPI/Swagger docs so that you can define another route which just gives you your OpenAPI JSON. Literally in what I have written the above would be a type error because the `Router` class would complain that `params` has the wrong type because the `objectguid` descriptor needs a key called `doc` which is a string for parameter documentation for OpenAPI.)
klageveen|7 years ago
drostie|7 years ago
This is great work, and I think you're burying one lede, which is that it looks like you've embedded a declarative permissions model in this thing, and I built one of those and the time saved can be huge when authorization is handled at the model level rather than everywhere in the business logic.