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
Support for Next.JS projects in generated clients #289
Conversation
All committers have signed the CLA. |
cedb1f8
to
f1b9a20
Compare
This commit adds support for generated Typescript clients to be compitable with [Next.JS](https://nextjs.org/) applications with the new `--nextjs` argument. The existing client was not compiatble as we where using namespaces to mimic the packages within the Encore application, however namespaces are somewhat depcreated and not all transpliers support them; the key one here being Babel (which Next.JS uses under the hood). When turned on the new `--nextjs` flag, therefore prevents the code generator from emitting namespaces, and instead it uses the package name as a prefix for generated types; such as `$package_$struct`. To make NextJS development easier, this flag also tells the TypeScript code generator to generate [SWR](https://swr.vercel.app/) wrappers for all API's, while maintaining the promise based functions if direct calls are still required (i.e. to perform actions). If the original api call was: ``` const api = new Client() const rsp: Promise<Data> = api.MySvc.MyAPI("segment", { name: "bob" }) ``` The SWR wrapper will be: ``` const api = new Client() // this needs to be a singleton for the application instance const rsp: SWRResponse<Data> = api.MySvc.useMyAPI("segment", { name: "bob" }, { refreshInternval: 1000 }) ```
f1b9a20
to
82361e2
Compare
This looks great! My only concern is that Next.JS is a moving target, and I can imagine lots of other scenarios where the same code generation options (no namespaces, I think a better solution is to introduce code generation options that map to specific functionality rather than moving targets. So in this case we could have |
Hmm; what we we do both; |
@eandre; I've updated the CLI options and per your suggestion an introduced a |
e8e1d39
to
3dbe7b6
Compare
3dbe7b6
to
15192c6
Compare
@@ -214,12 +235,16 @@ export namespace echo { | |||
|
|||
// Now make the actual call to the API | |||
const resp = await this.baseClient.callAPI("POST", `/echo.HeadersEcho`, undefined, {headers}) | |||
try { | |||
|
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.
With the addition of try {
this newline seems out of place. Remove?
try { | ||
return await resp.json() as EmptyData | ||
} catch (err) { | ||
throw new APIError(500, { code: ErrCode.DataLoss, message: "unable to unmarshal the response", details: err }) |
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.
DataLoss
is not meant for this but rather fatal, permanent data loss on the server side. Change to Internal
@@ -44,7 +45,7 @@ func Detect(path string) (lang Lang, ok bool) { | |||
} | |||
|
|||
// Client generates an API client based on the given app metadata. | |||
func Client(lang Lang, appSlug string, md *meta.Data) (code []byte, err error) { | |||
func Client(lang Lang, appSlug string, md *meta.Data, tsOptions *daemonpb.GenClientRequest_TypeScriptOptions) (code []byte, err error) { |
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 guess making the typescript options part of this signature is intended as a short-term thing? Ideally a nicer interface would be nicer to represent lang+options
as a single protobuf message with a oneof
@@ -54,7 +55,11 @@ func Client(lang Lang, appSlug string, md *meta.Data) (code []byte, err error) { | |||
var gen generator | |||
switch lang { | |||
case LangTypeScript: | |||
gen = &typescript{generatorVersion: typescriptGenLatestVersion} | |||
if tsOptions == nil { | |||
tsOptions = &daemonpb.GenClientRequest_TypeScriptOptions{Namespaces: true} |
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.
Instead of doing this here we could make the client always pass in options as it's the thing that's aware of the presets, and the base case of "no preset" is essentially just the default preset
|
||
|
||
|
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.
Lots of empty newlines here, is that intended?
} | ||
// CallParameters is the type of the parameters to a method call, but require headers to be a Record type |
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.
Should be a newline 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.
Should be single newline?
lang = string(codegen.LangTypeScript) | ||
tsOptions = &daemonpb.GenClientRequest_TypeScriptOptions{ | ||
Namespaces: false, | ||
Swr: true, |
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'm not sold on coupling swr with next.js. Yes, swr is also developed by the same authors but I believe React Query is increasingly becoming the library of choice for this sort of thing.
What's more, the useBlah
helpers are only valid for endpoints that fetch data, not for mutations, since they regularly fire multiple times in the background. Unintentionally calling a stateful, mutating endpoint when not intending to is recipe for disaster, so unless we have a good way of representing which endpoints actually do that and which don't we shouldn't generate such wrappers I don't think.
Support presets are: | ||
nextjs: Generates a TypScript client for use within a Next.js application |
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.
Support presets are: | |
nextjs: Generates a TypScript client for use within a Next.js application | |
Supported presets are: | |
nextjs: Generates a TypeScript client for use within a Next.js application |
if output == "" && lang == "" { | ||
fatal("specify at least one of --output or --lang.") | ||
if output == "" && lang == "" && preset == "" { | ||
fatal("specify at least one of --output, --lang or --preset.") |
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 am concerned that this makes for quite the analysis paralysis when trying to decide how to use this command. Could we combine lang
and preset
somehow to just a single option? We could have --lang=ts
choose the default preset and --lang=ts.nextjs
(or --lang=ts/nextjs
) choose the nextjs preset, or whatever.
We have earlier had compilation errors with namespaces in the generated TS client defined after the class in which they are referenced. However, as of NextJS 12.2.0, this is no longer an issue for us. The generated client compiles and works without any problems. I have not managed to locate the PR that solves this, but have not looked to much at it. I thought it be good for you to know this since it might change the scope of this PR! |
I'm going to close this PR for now as I'm not sure we need it anymore. We can re-open it if we needed it agian |
Pull request was closed
This commit adds support for generated Typescript clients to be
compitable with Next.JS applications with the new
--preset nextjs
argument.The existing client was not compiatble as we where using namespaces
to mimic the packages within the Encore application, however namespaces
are somewhat depcreated and not all transpliers support them; the key one
here being Babel (which Next.JS uses under the hood).
When turned on the new
--preset nextjs
flag, therefore prevents the code generatorfrom emitting namespaces, and instead it uses the package name as a prefix for
generated types; such as
$package_$struct
.To make NextJS development easier, this flag also tells the TypeScript code generator
to generate SWR wrappers for all API's, while maintaining the
promise based functions if direct calls are still required (i.e. to perform actions).
If the original api call was:
The SWR wrapper will be: