-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Strong-typed queries #138
Comments
This is super cool and absolutely crazy. I love it. I've spent the hour reading and processing it. Could you elaborate more on what the goal for this is and what use cases you see? I think some of the errors of the playground link might come from mixups between the |
I also had some concerns and did some quick experiments in the same vein to flesh out my thinking. Handling non-primitive types will also present issues. Mainly when using nested selects (Child.GrandChild), but there might be other issues as well. The return values of the queries won't be more strongly typed than before. We will only validate that it's not an invalid query, but we still won't know if the API has the information to return or if the property will return empty. A final problem is that the logic needed to get this to work will be too complicated which causes issues both with maintaining and also, for hypothetical future users, tracking back and parsing why they get the dreaded red squigglies will require really good code comments and would probably still take some effort. All in all, I really like the idea and attempt but I have a hard time seeing that the practical applications (Unless I'm failing to imagine big practical uses) will be worth investing the time and effort that would be required to finalize this. |
Thank you for chiming in. I agree with your assessment. It makes a lot of sense, and makes me think the same. The reason I'd be interested in this is due to us often changing our schema. We'd like to potentially catch errors in that regard that might happen at runtime. But I think e2e tests are better suited for that at second glance. Oh well, it was fun to experiment with! 😁 |
Thank you. What about a builder pattern approach? Something like: session.query(q => q
.from(f => f.Foo)
.withProjections(
p => p.fooProperty1,
p => p.fooProperty2,
p => p.fooProperty3
)
.withCriteria(c => c
.and(
a => a
.property(p => p.fooProperty1)
.is("hello world"),
a => a
.property(p => p.fooProperty2)
.lessThan(42),
a => a
.property(p => p.fooProperty2)
.notIn(56, 57)
)
)
.build()
)
//would produce: "select fooProperty1, fooProperty2, fooProperty3 from Foo where fooProperty1 is 'hello world' and fooProperty2 < 42 and fooProperty2 not_in (56, 57)" We could strong-type it, and even retain backwards compatibility by checking if the query is Now, it could use a clever trick to get the names of properties of interfaces and types: This package (under the hood) uses EcmaScript (and Node's) Full code of the function getPropertyNameInternal<T = unknown>(expression: (instance: T) => any, options: {
isDeep: boolean
}) {
let propertyThatWasAccessed = "";
var proxy: any = new Proxy({} as any, {
get: function(_: any, prop: any) {
if(options.isDeep) {
if(propertyThatWasAccessed)
propertyThatWasAccessed += ".";
propertyThatWasAccessed += prop;
} else {
propertyThatWasAccessed = prop;
}
return proxy;
}
});
expression(proxy);
return propertyThatWasAccessed;
}
export function getPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: false
});
}
export function getDeepPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: true
});
} And here's usage example: import { getPropertyName, getDeepPropertyName } from '@fluffy-spoon/name-of';
interface SomeType {
foo: boolean;
someNestedObject: {
bar: string;
}
}
console.log(getPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "bar"
console.log(getDeepPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "someNestedObject.bar" |
I tried making this in the playground. In my opinion, it works quite well. Here is the link. |
as @gismya mentioned in the PR, this is cool but not within the scope of the api client |
Yeah. Good call on cleaning it up. |
This is going to sound a bit crazy, but hear me out. There are currently a number of issues with this proposal, but it exists solely for the sake of demonstration. We can probably get around the limitations.
I have been experimenting with making the query function strong-typed, so that queries are validated at compile-time.
Features
where
), including.string
only allows:=
andis
.!=
andis_not
.in
andnot_in
.like
andnot_like
.number
only allows:=
andis
.!=
andis_not
.in
andnot_in
.>
,after
andgreater_than
.<
,before
andless_than
.>=
and<=
.boolean
only allows:=
andis
.!=
andis_not
.in
andnot_in
.object
only allows:has
.any
.where
values.Current limitations
If you like where this is headed, I am willing to invest in trying to resolve these limitations. So please don't see them as direct show-stoppers. Let's have a discussion about it.
infer
, so that instead of writingstrongTypedQuery<Foo>('select fooProperty1 from Foo')
, it can just be inferred fromstrongTypedQuery('select fooProperty1 from Foo')
, sinceFoo
is already a valid type name. This would be awesome, and would automatically add types for all queries, especially in combination with the type generator project.has
andany
is not being validated. Can easily add this.Whitespace
string literal only accepts a single whitespace. So an arbitrary amount of whitespaces between for instance the property names is not allowed. This could be a problem with multiline etc. And adding more might impact the type checking performance.and
andor
is not supported in thewhere
clause. This is easy to add, but only with a specific maximum amount of potential properties being selected (due to TypeScript not supporting recursive string literal types). There may be ways to work around this.The code
Here's the code. Notice the
strongTypedQuery
function. That's where the magic happens. For every call to that, I've commented particular cases that the strong-typings catch.Playground link
Play around with it here. Right now it gives an error in line 90, but it doesn't seem to be a valid error. I think it actually might be a bug in TypeScript.
The text was updated successfully, but these errors were encountered: