Skip to content

Refactor improve query type safety#1

Draft
MurkyTheMurloc wants to merge 9 commits into
mainfrom
refactor-improve-query-type-safety
Draft

Refactor improve query type safety#1
MurkyTheMurloc wants to merge 9 commits into
mainfrom
refactor-improve-query-type-safety

Conversation

@MurkyTheMurloc

@MurkyTheMurloc MurkyTheMurloc commented May 31, 2025

Copy link
Copy Markdown
Owner

Thank you for submitting this pull request! We appreciate you spending the time to work on these changes.

What is the motivation?

This pull request aims to make the query function and the PreparedQuery class more type safe. This includes auto-completion for query bindings and basic type validation for binding values to reduce mistakes.

Limitations?

While working on this request, I came across some limitations of typescript.

TemplateStringsArray

As described here https://github.com/microsoft/TypeScript/issues/33304 until now typescript can't be used with type safe TemplateStringsArray. This creates some limitations.
First example:

// so for example surql cant be infered correctly 
	const query = surql`RETURN [${foo},${bar}, ${1}, ${foo}, ${bar}, ${2}]`;
// instead  we need to use  the PreparedQuery Class for full typesafety  
		const query = new PreparedQuery("RETURN [$foo,$bar, $1, $foo, $bar, $2]", {
			foo: foo,
			bar: bar,
			"1": 1,
			"2": 2,
		});
   query.append([";Select * from sometable where hello_world = $helloWorld"], [{helloWorld: "Hello World"}])
        const bindings = query.bindings
image

Second example, value validation can't be inferred correctly:

// this example can be validated. 
//The expected error would be to use r`some:record`  instead of "some:record" to ensure the correct encoding
await surreal.query("Select * From  $record", {
record : "some:record"
}).execute()

// this example cant be validated correctly by typscript because of the TemplateStringsArray
await surreal.query("Select * From  $record", {
record : s`some:record`
}).execute()

A workaround would be to create a ESLint/biome extension to ensure proper value checking for query bindings.

partial generic type arguments

As described here https://github.com/microsoft/TypeScript/issues/10571 typescript doesn't support partial generic type arguments. This introduces the
problem when passing the return generic to query, for example

//Typescript errors because it also expects the other generics. 
const [output] = await surreal
   	.query<returnValue>(/* surql */ "CREATE ONLY $id", {
   		id: new RecordId("person", 90071992547409915n),
   	})

//To work around this, we need to make a significant change to the query method. 

const [output] = await surreal
   	.query(/* surql */ "CREATE ONLY $id", {
   		id: new RecordId("person", 90071992547409915n),
   	})
   	.execute<{ id: RecordId }>();

DB parameters

The last limitations are DB parameters. To make the query fully type safe, we need to know what params are set on the server. This could be achieved by adding runtime overhead inside the SDK by creating an object that will be inferred. This requires updating it when using the let and unset function, and fetch the params from the DB every time the use function is called.
Another approach would be to add a CLI tool with a watch mode similar to GraphQL-gen that generates the typescript types from the DB.

What does this change do?

Add typescript auto-completion for binding keys, value validation for strings including: UUID, Date, Record, Future. Optional bindings for rust function params. Excluding bindings for reserved variables like auth.
Add type safety for:

const query = new PreparedQuery("Select * from table where id  = $id", {
	id: "",
});

const updatedQuery = query.append(
	[
		"Select * from hello world where hello_word = $hello_world",
		"Select * from test where hello_world_2 = $hello_world_2",
	],
	[{ hello_world: "some" }, { hello_world_2: "" }],
);
const bindings = updatedQuery.bindings;
await surreal
   	.query(/* surql */ "CREATE ONLY $id", {
   		id: new RecordId("person", 90071992547409915n),
   	})
   	.execute<{ id: RecordId }>();

Adding a new query function for many queries this is useful when you write your query for example in surql files and just import them. The functions take in many surql statements and merges them into a single one.

    await surreal.queryMany([someSurqlQuery, someSurqlQuery],[someBindings,someBidnings ])

Final change: changing <T extends unknown []> to <T extends unknown[T]>

that's more a personal opinion, but I think that it's more intuitive than wrapping the response type in []

What is your testing strategy?

I played around with the SDK and a local surrealdb instance but didn't found any unexpected errors.

Is this related to any issues?

If this pull request is related to any other pull request or issue, or resolves any issues - then link all related pull requests and issues here.

Have you read the Contributing Guidelines?

@MurkyTheMurloc MurkyTheMurloc added the enhancement New feature or request label May 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant