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
Fix directly calling operations on the frontend #1992
base: main
Are you sure you want to change the base?
Conversation
async function query(queryKey, queryArgs) { | ||
async function query(queryArgs) { | ||
// Assumes `addMetadataToQuery` added the `queryCacheKey` property to the query. | ||
const queryKey = (query as any).queryCacheKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo: fix the issue in operations/core.ts#useQuery
where they call it with the cache key.
Test for queries that have a payload.
4e4cf5a
to
83d24dc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right. I've pulled a Miho.
In the future, we'll probably want to replace this with an existing type testing library:
https://stackoverflow.com/a/58831534
Action<Parameters<BackendAction>[0], _Awaited<_ReturnType<BackendAction>>> | ||
|
||
export type ActionFor<BackendQuery extends GenericBackendAction> = | ||
Parameters<BackendQuery> extends [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo: comment on why this was necessary.
) { | ||
query.queryCacheKey = [relativeQueryPath] | ||
// PRIVATE API (used in SDK) | ||
export function buildAndRegisterQuery<Input, Output>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a proper function (i.e., a smart constructor) seemed cleaner than having a function with side effects that you might forget to call.
// PRIVATE API (used in SDK) | ||
export function buildAndRegisterQuery<Input, Output>( | ||
queryFn: ClientOperation<Input, Output>, | ||
{ queryCacheKey, queryRoute, entitiesUsed }: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took the creation of the queryCacheKey
out to make it easily accessible in createQuery
. The bonus is that this function now has fewer responsibilities.
export type QueryFor<BackendQuery extends GenericBackendOperation> = | ||
QueryForFunction<QueryFunctionFor<BackendQuery>> | ||
|
||
type QueryFunctionFor<BackendQuery extends GenericBackendOperation> = | ||
OperationRpcFor<BackendQuery> | ||
|
||
// PRIVATE API (needed in SDK) | ||
type QueryForFunction<QF extends QueryFunction<never, unknown>> = | ||
QF & QueryMetadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to avoid duplicating the same code for Queries and Actions this time around, mostly because that's what caused an incorrect type in RPC calls for queries.
export type Query<Input, Output> = QueryFunction<Input, Output> & QueryMetadata | ||
|
||
// PRIVATE API (but should maybe be public, users define values of this type) | ||
export type Action<Input, Output> = ClientOperation<Input, Output> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@infomiho What do you think? Should these types be added to our public API?
Won't do this now, but I can create an issue.
export type QueryMetadata = { | ||
queryCacheKey: string[] | ||
route: Route | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to expose both route
and queryCacheKey
to our users:
queryCacheKey
- Because they need it to unlock all the capabilities of tanstack-query (as our docs tell them)route
- Because I didn't want to complicate things by introducing another "internal" type.
Let me know if you think this is a problem
|
||
// PRIVATE API (needed in SDK) | ||
export type OperationRpcFor<BackendOperation extends GenericBackendOperation> = | ||
Parameters<BackendOperation> extends [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A playground link that explains why we need Parameters<BackendOperation> extends []
.
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)) | ||
await sleep(5000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll comment this out after testing.
async function logAll() { | ||
const tasks = await getTasks() | ||
console.info('Got tasks:', tasks) | ||
|
||
const someId = tasks.map((task) => task.id).find((id) => id) | ||
if (!someId) { | ||
console.info('No tasks found') | ||
} else { | ||
const task = await getTask({ id: someId }) | ||
console.info(`Got task with id ${someId}`, task) | ||
} | ||
|
||
const date = await getDate() | ||
console.info('Got date:', date) | ||
|
||
const anything = await getAnything() | ||
console.info('Got anything:', anything) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll comment this out after testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only formatting.
export const getAnything: GetAnything = async () => { | ||
return 'anything' | ||
} | ||
|
||
export const getTrueVoid = (async () => { | ||
return 'anything' | ||
}) satisfies GetTrueVoid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is all that's new. I've added these Queries to cover all edge cases with my types tests in TestRpcTypes
.
The rest of the file is formatting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff!
I like the cleanup you did, left some minor comments
type GetTask, | ||
type GetTasks, | ||
type GetDate, | ||
GetAnything, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not prefix it with type
as well?
@@ -43,8 +43,8 @@ export const updateTaskIsDone: UpdateTaskIsDone< | |||
} | |||
|
|||
// Uncomment to test optimistic updates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to comment this again or we want to keep it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I guess it's better if we keep it. It will make it obvious that optimistic updates are broken. Good call
export function areThereAnyTasks( | ||
tasks: Task[] | undefined | ||
): tasks is NonEmptyArray<Task> { | ||
return !!(tasks && tasks.length > 0) | ||
} | ||
|
||
const Todo = () => { | ||
logAll() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be a lot of work, but maybe output the values you received here and show it on the client somehow and then maybe create a headless test to check? Something like https://github.com/wasp-lang/wasp/pull/1995/files#diff-4ff0892633d41d0e5068ff93600cab7bf876e4b1bf2e1195e23836b513017fc5R1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, but too much work for now (I was going to remove these logs).
I'll definitely create an issue for it though.
waspc/examples/todoApp/src/App.tsx
Outdated
|
||
import './Main.css' | ||
import { getName } from './user' | ||
// Necessary to trigger type tests. | ||
import './TestRpcTypes' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend maybe creating a type-tests
folder or something like that and then import a index.ts
from that folder to keep things a bit more organised.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then you could put the helpers in typeTestHelpers
or something like that and then the RPC tests just in its own file.
// For the details of this specification, see | ||
// https://github.com/wasp-lang/wasp/pull/1090#discussion_r1159732471 | ||
|
||
// This should be [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe put a TODO here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do you one better: #2004
@@ -12,5 +12,5 @@ export const {= operationName =}: QueryFor<{= operationTypeName =}> = createQuer | |||
) | |||
{=/ queries =} | |||
// PRIVATE API | |||
export { addMetadataToQuery } from './core' | |||
// PRIVATE API (used in SDK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If used only in SDK, do you need to expose in the package.json
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's what we'll do as part of #1922.
But for now, yes (since other parts of SDK use it as a package import).
query.queryCacheKey = [relativeQueryPath] | ||
// PRIVATE API (used in SDK) | ||
export function buildAndRegisterQuery<Input, Output>( | ||
queryFn: ClientOperation<Input, Output>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How not use QueryFunction
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch. I introduced QueryFunction
after writing this code and forgot to update it.
{ queryCacheKey, queryRoute, entitiesUsed }: | ||
{ queryCacheKey: string[], queryRoute: Route, entitiesUsed: string[] } | ||
): QueryForFunction<typeof queryFn> { | ||
const query = queryFn as QueryForFunction<typeof queryFn> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice cast
query.route = queryRoute | ||
addResourcesUsedByQuery(query.queryCacheKey, entitiesUsed) | ||
|
||
return query | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: extra new line
export type QueryFor<BackendQuery extends GenericBackendOperation> = | ||
QueryForFunction<QueryFunctionFor<BackendQuery>> | ||
|
||
type QueryFunctionFor<BackendQuery extends GenericBackendOperation> = | ||
OperationRpcFor<BackendQuery> | ||
|
||
// PRIVATE API (needed in SDK) | ||
type QueryForFunction<QF extends QueryFunction<never, unknown>> = | ||
QF & QueryMetadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this block hard to parse, maybe put some why comment on top to make it easier for us in the future to get back into this code.
Fixes #1923.
I'll do all the testing once this gets approved.
I've already tested it a couple of times and it all works, but I'll hold of the final just-in-case tests until all changes are final.
Tasks:
Query
,Action
,Operation
,QueryFor
,OperationFor
. Should we expose some of them and remove duplication?