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 - Part 1 #2393

Merged
merged 8 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ replaceTemplatePlaceholdersInFileOnDisk appName projectName file = do
("__waspProjectName__", show projectName),
("__waspVersion__", defaultWaspVersionBounds)
]
-- TODO: We do this in all files, but not all files have all placeholders
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
updateFileContentWith (replacePlaceholders waspTemplateReplacements) file
where
updateFileContentWith :: (Text -> Text) -> Path' Abs (File f) -> IO ()
Expand Down
20 changes: 10 additions & 10 deletions waspc/packages/wasp-config/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'

export default [
pluginJs.configs.recommended,
...tseslint.configs.strict,
// Todo: explore typed-linting: https://typescript-eslint.io/getting-started/typed-linting
{
languageOptions: {
globals: globals.node,
},
},
// global ignore
{
ignores: ["node_modules/", "dist/"],
ignores: ['node_modules/', 'dist/'],
},
{
rules: {
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-empty-function": "warn",
"no-empty": "warn",
"no-constant-condition": "warn",
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'no-empty': 'warn',
'no-constant-condition': 'warn',
'object-shorthand': 'warn',
},
},
];
]
188 changes: 117 additions & 71 deletions waspc/packages/wasp-config/src/appSpec.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
/** This module is a mirror implementation of AppSpec Decls in TypeScript.
* The original implemention is in Haskell (waspc).
/** This module is a mirror implementation of FromJSON for AppSpec Decls in
* TypeScript. The original implemention is in Haskell (waspc).
*
* IMPORTANT: Do not change this file without updating the AppSpec in waspc.
*/

export type Decl =
| { declType: 'App'; declName: string; declValue: App }
| { declType: 'Page'; declName: string; declValue: Page }
| { declType: 'Route'; declName: string; declValue: Route }
| { declType: 'Query'; declName: string; declValue: Query }
| { declType: 'Action'; declName: string; declValue: Action }
| { declType: 'App'; declName: string; declValue: App }
| { declType: 'Job'; declName: string; declValue: Job }
| { declType: 'Api'; declName: string; declValue: Api }
| { declType: 'ApiNamespace'; declName: string; declValue: ApiNamespace }
| { declType: 'Crud'; declName: string; declValue: Crud }
export type Decl = {
[Type in keyof DeclTypeToValue]: {
declType: Type
declName: string
declValue: DeclTypeToValue[Type]
}
}[keyof DeclTypeToValue]

export type DeclTypeToValue = {
App: App
Page: Page
Route: Route
Query: Query
Action: Action
Job: Job
Api: Api
ApiNamespace: ApiNamespace
Crud: Crud
}

export type GetDeclForType<T extends Decl['declType']> = Extract<
Decl,
{ declType: T }
>

// NOTE: Entities are defined in the schema.prisma file, but they can still be
// referenced.
export type DeclType = Decl['declType'] | 'Entity'

export type Page = {
component: ExtImport
authRequired?: boolean
authRequired: Optional<boolean>
}

export type Route = {
Expand All @@ -32,39 +45,39 @@ export type Route = {

export type Action = {
fn: ExtImport
entities?: Ref<'Entity'>[]
auth?: boolean
entities: Optional<Ref<'Entity'>[]>
auth: Optional<boolean>
}

export type Query = {
fn: ExtImport
entities?: Ref<'Entity'>[]
auth?: boolean
entities: Optional<Ref<'Entity'>[]>
auth: Optional<boolean>
}

export type Job = {
executor: JobExecutor
perform: Perform
schedule?: Schedule
entities?: Ref<'Entity'>[]
schedule: Optional<Schedule>
entities: Optional<Ref<'Entity'>[]>
}
export type Schedule = {
cron: string
args?: object
executorOptions?: ExecutorOptions
args: Optional<object>
executorOptions: Optional<ExecutorOptions>
}

export type Perform = {
fn: ExtImport
executorOptions?: ExecutorOptions
executorOptions: Optional<ExecutorOptions>
}

export type Api = {
fn: ExtImport
middlewareConfigFn?: ExtImport
entities?: Ref<'Entity'>[]
middlewareConfigFn: Optional<ExtImport>
entities: Optional<Ref<'Entity'>[]>
httpRoute: HttpRoute
auth?: boolean
auth: Optional<boolean>
}

export type ApiNamespace = {
Expand All @@ -80,13 +93,13 @@ export type Crud = {
export type App = {
wasp: Wasp
title: string
head?: string[]
auth?: Auth
server?: Server
client?: Client
db?: Db
emailSender?: EmailSender
webSocket?: WebSocket
head: Optional<string[]>
auth: Optional<Auth>
server: Optional<Server>
client: Optional<Client>
db: Optional<Db>
emailSender: Optional<EmailSender>
webSocket: Optional<WebSocket>
}

export type ExtImport = {
Expand All @@ -98,89 +111,87 @@ export type ExtImport = {
export type JobExecutor = 'PgBoss'

export type ExecutorOptions = {
pgBoss?: object
pgBoss: Optional<object>
}

export type HttpMethod = 'ALL' | 'GET' | 'POST' | 'PUT' | 'DELETE'

export type HttpRoute = [HttpMethod, string]

export type CrudOperations = {
get?: CrudOperationOptions
getAll?: CrudOperationOptions
create?: CrudOperationOptions
update?: CrudOperationOptions
delete?: CrudOperationOptions
get: Optional<CrudOperationOptions>
getAll: Optional<CrudOperationOptions>
create: Optional<CrudOperationOptions>
update: Optional<CrudOperationOptions>
delete: Optional<CrudOperationOptions>
}

export type CrudOperationOptions = {
isPublic?: boolean
overrideFn?: ExtImport
isPublic: Optional<boolean>
overrideFn: Optional<ExtImport>
}

export type Wasp = {
// TODO: Check semver in export type system?
version: string
}

export type Auth = {
userEntity: Ref<'Entity'>
externalAuthEntity?: Ref<'Entity'>
externalAuthEntity: Optional<Ref<'Entity'>>
methods: AuthMethods
onAuthFailedRedirectTo: string
onAuthSucceededRedirectTo?: string
onBeforeSignup?: ExtImport
onAfterSignup?: ExtImport
onBeforeOAuthRedirect?: ExtImport
onBeforeLogin?: ExtImport
onAfterLogin?: ExtImport
onAuthSucceededRedirectTo: Optional<string>
onBeforeSignup: Optional<ExtImport>
onAfterSignup: Optional<ExtImport>
onBeforeOAuthRedirect: Optional<ExtImport>
onBeforeLogin: Optional<ExtImport>
onAfterLogin: Optional<ExtImport>
}

export type AuthMethods = {
usernameAndPassword?: UsernameAndPasswordConfig
discord?: ExternalAuthConfig
google?: ExternalAuthConfig
gitHub?: ExternalAuthConfig
keycloak?: ExternalAuthConfig
email?: EmailAuthConfig
usernameAndPassword: Optional<UsernameAndPasswordConfig>
discord: Optional<ExternalAuthConfig>
google: Optional<ExternalAuthConfig>
gitHub: Optional<ExternalAuthConfig>
keycloak: Optional<ExternalAuthConfig>
email: Optional<EmailAuthConfig>
}

export type UsernameAndPasswordConfig = {
userSignupFields?: ExtImport
userSignupFields: Optional<ExtImport>
}

export type ExternalAuthConfig = {
configFn?: ExtImport
userSignupFields?: ExtImport
configFn: Optional<ExtImport>
userSignupFields: Optional<ExtImport>
}

export type EmailAuthConfig = {
userSignupFields?: ExtImport
userSignupFields: Optional<ExtImport>
fromField: EmailFromField
emailVerification: EmailVerificationConfig
passwordReset: PasswordResetConfig
}

export type EmailSender = {
provider: EmailProvider
defaultFrom?: EmailFromField
defaultFrom: Optional<EmailFromField>
}

// TODO: duplication
export type EmailProvider = 'SMTP' | 'SendGrid' | 'Mailgun' | 'Dummy'

export type EmailFromField = {
name?: string
name: Optional<string>
email: string
}

export type EmailVerificationConfig = {
getEmailContentFn?: ExtImport
getEmailContentFn: Optional<ExtImport>
clientRoute: Ref<'Route'>
}

export type PasswordResetConfig = {
getEmailContentFn?: ExtImport
getEmailContentFn: Optional<ExtImport>
clientRoute: Ref<'Route'>
}

Expand All @@ -190,21 +201,56 @@ export type Ref<T extends DeclType> = {
}

export type Server = {
setupFn?: ExtImport
middlewareConfigFn?: ExtImport
setupFn: Optional<ExtImport>
middlewareConfigFn: Optional<ExtImport>
}

export type Client = {
setupFn?: ExtImport
rootComponent?: ExtImport
baseDir?: `/${string}`
setupFn: Optional<ExtImport>
rootComponent: Optional<ExtImport>
baseDir: Optional<`/${string}`>
}

export type Db = {
seeds?: ExtImport[]
seeds: Optional<ExtImport[]>
}

export type WebSocket = {
fn: ExtImport
autoConnect?: boolean
autoConnect: Optional<boolean>
}

/**
* We want to explicitly set all optional (Maybe) AppSpec fields to `undefined`
* (instead of using an optional field with a questionmark).
*
* Doing so doesn't change any functionality and ensures (at compile-time) we
* don't forget to include an existing optional field in a declaration object.
*
* For example, let's say `bar` is optional (both for the user and for the app
* spec). This would be the correct mapping code:
* ```
Martinsos marked this conversation as resolved.
Show resolved Hide resolved
* const { foo, bar } = userConfig
* const decl: SomeDecl = {
* foo: mapForAppSpec(foo),
* bar: mapForAppSpec(bar)
* }
* ```
* The code below is wrong. It forgets to map `bar` even though it might exist
* in `userConfig`:
* ```
* const { foo } = userConfig
* const decl: SomeDecl = {
* foo: mapForAppSpec(foo),
* }
* ```
* If `bar` is an optional field of `SomeDecl` (`bar?: string`), TypeScript
* doesn't catch this error.
*
* If `bar` is a mandatory field of `SomeDecl` that can be set to `undefined`
* (`bar: Optional<string>`), TypeScript catches the error.
*
* Explicitly setting optional fields to `undefined` doesn't impact JSON
* serialization since fields set to `undefined` are treated as missing fields.
*/
type Optional<T> = T | undefined
Loading
Loading