Users can define their operations to either expect a payload or not expect a payload (configured through the operation's type), and Wasp must consider this when generating the server-side API: If the operation's definition expects a payload, so should its generated API (and vice versa).
The generated server-side API also depends on whether the application is authenticated (i.e., auth: true):
If the operation is authenticated, the generated API must accept a context object containing the user.
If the operation isn't authenticated, the generated API doesn't require a context.
This leaves us with four possible call signatures for an operation's server-side API:
auth: true
auth: false
With payload
getFoo(payload, context)
getFoo(payload)
Without payload
getFoo(context)
getFoo()
Behavior
Unauthenticated operations
For unauthenticated operations, everything works as expected.
TypeScript enforces one of the call signatures from the second column. So, if the user's using TypeScript, everything's OK.
If the user isn't using TypeScript, the provided payload (or lack thereof) is always forwarded to the operation's definition. This is consistent with JS's behavior:
Call signature
Called with
Behavior
getFoo()
getFoo(payload)
✅ All good, payload is ignored .
getFoo(payload)
getFoo()
✅ All good, payload is undefined.
Unauthenticated operations
This is where things get messy.
Since neither the Generator nor the runtime knows the function's true type signature (i.e., its actual number of arguments), the runtime must "guess" which of the two possible signatures is correct (i.e., whether the operation expects a payload or not) and act accordingly:
When the user is using TypeScript, TypeScript enforces that the user sends the correct number of arguments to this function, while the function's runtime decides on the proper implementation based on the number of arguments it receives. Therefore, if the user's using TypeScript, everything's OK.
If the user isn't using TypeScript, there are possible discrepancies between the function's true type and the type runtime decides on:
Call signature
Called with
Behavior
getFoo(context)
getFoo()
✅ All good, an appropriate error is thrown (illegal number of arguments)
getFoo(context)
getFoo(arg1, arg2)
❌ Not good, arg1 is treated as the payload, arg2 is treated as the context. The action's definition is called with (arg1, { ...arg2, entities }), while the expected call would be (undefined, { ...arg1, entities }).
getFoo(payload, context)
getFoo()
✅ All good, the appropriate error is thrown (illegal number of arguments).
getFoo(payload, context)
getFoo(arg1)
❌ Not good, arg1 is treated as the context. The action's definition is called with (undefined, { ...arg1, entities }), while the expected call would be (arg1, { ...undefined, entities }).
Solutions
Ideas for fixing this include:
Changing the operations (both their definitions and APIs) to work with named arguments instead of positional arguments
Parsing TS files to get type information in the generator.
Context
Consider the definition of an authenticated operation
getFoo
:As of https://github.com/wasp-lang/wasp/pull/2044, Wasp generates a server-side API that users can use to call operations from the server's runtime.
Users can define their operations to either expect a payload or not expect a payload (configured through the operation's type), and Wasp must consider this when generating the server-side API: If the operation's definition expects a payload, so should its generated API (and vice versa).
The generated server-side API also depends on whether the application is authenticated (i.e.,
auth: true
):context
object containing the user.This leaves us with four possible call signatures for an operation's server-side API:
getFoo(payload, context)
getFoo(payload)
getFoo(context)
getFoo()
Behavior
Unauthenticated operations
For unauthenticated operations, everything works as expected.
TypeScript enforces one of the call signatures from the second column. So, if the user's using TypeScript, everything's OK.
If the user isn't using TypeScript, the provided
payload
(or lack thereof) is always forwarded to the operation's definition. This is consistent with JS's behavior:getFoo()
getFoo(payload)
payload
is ignored .getFoo(payload)
getFoo()
payload
isundefined
.Unauthenticated operations
This is where things get messy.
Since neither the Generator nor the runtime knows the function's true type signature (i.e., its actual number of arguments), the runtime must "guess" which of the two possible signatures is correct (i.e., whether the operation expects a payload or not) and act accordingly:
When the user is using TypeScript, TypeScript enforces that the user sends the correct number of arguments to this function, while the function's runtime decides on the proper implementation based on the number of arguments it receives. Therefore, if the user's using TypeScript, everything's OK.
If the user isn't using TypeScript, there are possible discrepancies between the function's true type and the type runtime decides on:
getFoo(context)
getFoo()
getFoo(context)
getFoo(arg1, arg2)
arg1
is treated as the payload,arg2
is treated as the context. The action's definition is called with(arg1, { ...arg2, entities })
, while the expected call would be(undefined, { ...arg1, entities })
.getFoo(payload, context)
getFoo()
getFoo(payload, context)
getFoo(arg1)
arg1
is treated as the context. The action's definition is called with(undefined, { ...arg1, entities })
, while the expected call would be(arg1, { ...undefined, entities })
.Solutions
Ideas for fixing this include: