Description
Problem Statement
It is common to embed queries in tagged template expressions within typescript. For example, you might use sql:
const result = await querySQL(sql`SELECT * FROM users WHERE id = ${userID}`);
You also see a similar pattern with graphql:
const result = await queryGraph(graphql`query { user { id, name } }`);
Currently, there is no way to provide type information about what is returned by queryGraph
/querySQL
. A lot of potential type information is lost at the boundary here.
I obviously don't expect typescript to read the graphql or SQL schema, as that's way out of scope. What I would like is a way to provide this information as a module author.
Proposal
I believe that all that's needed is a way to write plugins that answer the question "given a tagged template expression, what type does it return". This would only affect the type checker. It would not change generated code or add any new syntax.
A new compilerConfig
option called taggedTemplateHandlers
would be added, that would take an array of paths to typescript files. An example might look something like:
import {parseSqlQuery, convertSqlTypeToTypeScriptType} from 'sql-helpers';
import * as ts_module from "typescript/lib/tsserverlibrary";
export default {
tag: 'sql',
templateHandler(modules: {typescript: typeof ts_module}, strings: string[], ...expressions: Array<ts_module.Expression>) {
const ts = modules.typescript;
const fields = parseSqlQuery(strings.join('"EXPRESSION"'));
return ts.createTypeReferenceNode(
ts.createIdentifier('SqlQuery'), // typeName
ts.createNodeArray([ // typeArguments
ts.createTypeLiteralNode(Object.keys(fields).map(field => {
return ts.createPropertySignature(
ts.createIdentifier(field),
undefined, // question token
convertSqlTypeToTypeScriptType(fields[field]), // type
undefined, // initializer
)
}))
])
);
}
};
You could then define querySQL
like:
define function querySQL<TResult>(query: SqlQuery<TResult>): Promise<TResult>;
Language Feature Checklist
- Syntactic - no new changes
- Semantic
- When a tagged template is encountered by the type-checker
- See if a
taggedTemplateHandlers
has been registered for that tag - Call that
taggedTemplateHandler
if one exists. - Use the
TypeNode
returned by thetaggedTemplateHandler
in place of the default behaviour.
- See if a
- When a tagged template is encountered by the type-checker
- Emit - no new changes
- Compatibility - no new syntax is added/changed, so it should be fully backwards/forwards compatible.
- Other
- I expect there will be some performance impact, but hopefully the fact that both plugins and the compiler are written in typescript should make this minimal.
- Ideally, this information would be used for autocomplete helpers as well as in the typechecker, I do not know if that would require extra work.
I'm happy to do my best to help implement this, but I would need some pointers on where to start.
P.S. would it be possible to pass in the type of the expressions, in place of the actual expressions themselves?