-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
TS Config cleanup #2393
base: main
Are you sure you want to change the base?
TS Config cleanup #2393
Conversation
type Result<Value, Error> = | ||
| { status: 'ok'; value: Value } | ||
| { status: 'error'; error: 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'm using this for normal control flow errors (e.g., user forgets something).
I'm using exceptions for unexpected errors/compiler bugs. For example, an exception is thrown if waspc
doesn't pass the correct arguments to this executable.
For the normal control flow, I am using the Result
type instead of exceptions because:
- The error users see is nicer (no stack trace)
- They can slip through the type system.
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! If only there was a language that already used this kind of approach for errors/exceptions as a default :D!
function makeDeclsArray(decls: { | ||
[Type in AppSpec.Decl['declType']]: AppSpec.GetDeclForType<Type>[] | ||
}): AppSpec.Decl[] { | ||
return Object.values(decls).flatMap((decl) => [...decl]) | ||
} | ||
|
||
return decls | ||
function mapToDecls<T, const DeclType extends AppSpec.Decl['declType']>( | ||
configs: Map<string, T>, | ||
type: DeclType, | ||
configToDeclValue: ( | ||
config: T | ||
) => AppSpec.GetDeclForType<DeclType>['declValue'] | ||
) { | ||
return [...configs].map(([name, config]) => ({ | ||
declType: type, | ||
declName: name, | ||
declValue: configToDeclValue(config), | ||
})) |
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 might look too complex compared to what we had before (the for
loops), but comes with many benefits:
No repetition
We don't repeat the same map iteration and Decl
object construction a bunch of times. Plus, the code is shorter.
The entire decls
array is built at once, without pushing
Because we're functional bros.
The compiler ensures we don't forget to include any declarations
This is the biggest benefit IMO, courtesy of the makeDeclsArray
function.
For example, see what happens if we forget to include Crud:
So, when we add a new declaration, the compiler tells us what to implement.
The compiler ensures we don't mix anything up
mapToDecls
ensures that the Decl
returned by the mapping function matches the DeclType
provided as the second argument, so we can't mix different Decl
types and values (we had this before, but it's preserved):
configToDeclValue: ( | ||
config: T | ||
) => AppSpec.GetDeclForType<DeclType>['declValue'] | ||
) { |
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 can't specify a return type of this function because of some weird error, but I stopped digging into it.
If you're curious, here's the playground.
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.
LGTM
Some nice code improvements. Left some minor comments.
declValue: mapCrud(crudConfig, parseEntityRef), | ||
}) | ||
} | ||
function makeDeclsArray(decls: { |
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.
Sweet
|
||
return decls | ||
function mapToDecls<T, const DeclType extends AppSpec.Decl['declType']>( |
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.
What does const
do 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.
Nothing, it's a leftover I think. Nice catch.
...(externalAuthEntity && { | ||
externalAuthEntity: parseEntityRef(externalAuthEntity), | ||
}), | ||
externalAuthEntity: |
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.
For entities you did entities: entities && entities.map(parseEntityRef),
why are you are explicitly checking for undefined
?
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.
Because it can technically be an empty string.
@@ -7,13 +7,15 @@ export class App { | |||
#userSpec: UserSpec; | |||
|
|||
// NOTE: Using a non-public symbol gives us a pacakge-private property. |
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.
// NOTE: Using a non-public symbol gives us a pacakge-private property. | |
// NOTE: Using a non-public symbol gives us a package-private property. |
@@ -58,6 +58,10 @@ import qualified Wasp.SemanticVersion as SV | |||
-- describing the web app specification with all the details needed to generate it. | |||
-- It is standalone and de-coupled from other parts of the compiler and knows nothing about them, | |||
-- instead other parts are using it: Analyzer produces AppSpec while Generator consumes it. | |||
-- | |||
-- IMPORTANT: Do not change this data structure without updating the AppSpec in |
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 had this random 💩 idea, run a LLM on each commit if this files changed or the TS file changed. "Are these two things the same implementation?"
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.
Hah that would be wild but it makes sense :D!
I guess we could also do somethign more deterministic, like collect checksums of AppSpec files form haskell side and compare with the checksum written in the .ts file. Or maybe have Haskell write out types of AppSpec and all types it uses, using sometihng like Generics, in a kind of a "snapshot" file like we use for e2e tests, probably during compile time using TH. Might get a lot of false positives hm but still.
@@ -9,6 +9,9 @@ dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) | |||
for package in $(ls "$dir/../packages"); do | |||
package_dir="$dir/../packages/$package" | |||
if [[ -d "$package_dir" ]]; then | |||
# We're only installing the dependencines here to verify that the build | |||
# works, that's why the node_modules folder is removed immediately after. | |||
# The real dependency installatino happens in Haskell. |
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.
# The real dependency installatino happens in Haskell. | |
# The real dependency installation happens in Haskell. |
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.
@sodic nice stuff and looks good! I didn't dive into some details, but I did a high-level review and looked at some parts I remember us discussing. Left a couple of small comments, all minor.
waspc/cli/src/Wasp/Cli/Command/CreateNewProject/StarterTemplates/Templating.hs
Show resolved
Hide resolved
@@ -58,6 +58,10 @@ import qualified Wasp.SemanticVersion as SV | |||
-- describing the web app specification with all the details needed to generate it. | |||
-- It is standalone and de-coupled from other parts of the compiler and knows nothing about them, | |||
-- instead other parts are using it: Analyzer produces AppSpec while Generator consumes it. | |||
-- | |||
-- IMPORTANT: Do not change this data structure without updating the AppSpec in |
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.
Hah that would be wild but it makes sense :D!
I guess we could also do somethign more deterministic, like collect checksums of AppSpec files form haskell side and compare with the checksum written in the .ts file. Or maybe have Haskell write out types of AppSpec and all types it uses, using sometihng like Generics, in a kind of a "snapshot" file like we use for e2e tests, probably during compile time using TH. Might get a lot of false positives hm but still.
No description provided.