Skip to content
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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

TS Config cleanup #2393

wants to merge 6 commits into from

Conversation

sodic
Copy link
Contributor

@sodic sodic commented Nov 29, 2024

No description provided.

Comment on lines +107 to +109
type Result<Value, Error> =
| { status: 'ok'; value: Value }
| { status: 'error'; error: Error }
Copy link
Contributor Author

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.

Copy link
Member

@Martinsos Martinsos Dec 12, 2024

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!

@sodic sodic mentioned this pull request Nov 29, 2024
Comment on lines 89 to 106
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),
}))
Copy link
Contributor Author

@sodic sodic Dec 2, 2024

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:

image

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):

image

configToDeclValue: (
config: T
) => AppSpec.GetDeclForType<DeclType>['declValue']
) {
Copy link
Contributor Author

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.

@sodic sodic marked this pull request as ready for review December 9, 2024 15:30
Copy link
Contributor

@infomiho infomiho left a 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: {
Copy link
Contributor

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']>(
Copy link
Contributor

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?

Copy link
Contributor Author

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:
Copy link
Contributor

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?

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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
Copy link
Contributor

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?"

Copy link
Member

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# The real dependency installatino happens in Haskell.
# The real dependency installation happens in Haskell.

Copy link
Member

@Martinsos Martinsos left a 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/packages/wasp-config/src/appSpec.ts Outdated 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
Copy link
Member

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.

waspc/tools/install_packages_to_data_dir.sh Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants