Add manual TypeScript support for Operations (#945)

* Support typing backend queries

* Implement types for actions and extract entities

* Add CR fixes

* Add type to imports and exports for entities

* Change query extensions back to .js

* Tidy up default arguments for operation types

* Don't throw syntax error when there are no entities

* Update e2e tests

* Fix formatting

* Fix typo in comment

* Update changelog and bump project version
This commit is contained in:
Filip Sodić 2023-01-24 14:45:35 +01:00 committed by GitHub
parent 2ed921a442
commit 81ffa6c27c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 404 additions and 48 deletions

View File

@ -1,5 +1,32 @@
# Changelog
## v0.8.1
### Import Wasp entity types on the backend
You can now import and use the types of Wasp entities in your backend code:
```typescript
import { Task } from '@wasp/entities/Task'
const getTasks = (args, context): Task[] => {
const tasks: Task[] = // ...
// ...
}
```
### TypeScript support for Queries and Actions
Wasp now includes generic type constructors for typing Queries and Actions.
This features works hand-in-hand with Wasp entity types described in the
previous chapter:
```typescript
import { Query } from '@wasp/queries'
import { Task } from '@wasp/entities/task'
const getTasks: Query<[Task]> = (args, context) => {
// The compiler knows the types of 'args' and 'context'
// inside the function's body.
}
```
## v0.8.0
### BREAKING CHANGES
@ -117,9 +144,9 @@ directory `foo`, you should:
// This previously resolved to ext/LoginPage.js
component: import Login from "@ext/LoginPage.js"
}
// ...
query getTasks {
// This previously resolved to ext/queries.js
fn: import { getTasks } from "@ext/queries.js",
@ -133,16 +160,16 @@ directory `foo`, you should:
// This resolves to src/client/LoginPage.js
component: import Login from "@client/LoginPage"
}
// ...
query getTasks {
// This resolves to src/server/queries.js
fn: import { getTasks } from "@server/queries.js",
}
```
Do this for all external imports in your `.wasp` file. After you're done, there shouldn't be any occurences of the string `"@ext"`.
That's it! You should now have a fully working Wasp project in the `foo` directory.
### [NEW FEATURE] TypeScript support

View File

@ -6,11 +6,13 @@ import prisma from '../dbClient.js'
{=! TODO: This template is exactly the same at the moment as one for queries,
consider in the future if it is worth removing this duplication. =}
export default async (args, context) => {
context = { ...context, entities: {
{=# entities =}
{= name =}: prisma.{= prismaIdentifier =},
{=/ entities =}
}}
return {= jsFnIdentifier =}(args, context)
export default async function (args, context) {
return {= jsFnIdentifier =}(args, {
...context,
entities: {
{=# entities =}
{= name =}: prisma.{= prismaIdentifier =},
{=/ entities =}
},
})
}

View File

@ -44,6 +44,10 @@ const auth = handleRejection(async (req, res, next) => {
return res.status(401).send()
}
// TODO: This logic must match the type in types/index.ts (if we remove the
// password field from the object here, we must to do the same there).
// Ideally, these two things would live in the same place:
// https://github.com/wasp-lang/wasp/issues/965
const { password, ...userView } = user
req.user = userView

View File

@ -0,0 +1,18 @@
{{={= =}=}}
import {
{=# entities =}
type {= name =},
{=/ entities =}
} from "@prisma/client"
export {
{=# entities =}
type {= name =},
{=/ entities =}
} from "@prisma/client"
export type WaspEntity =
{=# entities =}
| {= name =}
{=/ entities =}
| never

View File

@ -3,14 +3,16 @@ import prisma from '../dbClient.js'
{=& jsFnImportStatement =}
{=! TODO: This template is exactly the same at the moment as one for queries,
{=! TODO: This template is exactly the same at the moment as one for actions,
consider in the future if it is worth removing this duplication. =}
export default async (args, context) => {
context = { ...context, entities: {
{=# entities =}
{= name =}: prisma.{= prismaIdentifier =},
{=/ entities =}
}}
return {= jsFnIdentifier =}(args, context)
export default async function (args, context) {
return {= jsFnIdentifier =}(args, {
...context,
entities: {
{=# entities =}
{= name =}: prisma.{= prismaIdentifier =},
{=/ entities =}
},
})
}

View File

@ -12,7 +12,7 @@ import {= importIdentifier =} from '{= importPath =}'
const router = express.Router()
{=# operationRoutes =}
router.post('{= routePath =}', {=# isUsingAuth =} auth, {=/ isUsingAuth =} {= importIdentifier =})
router.post('{= routePath =}',{=# isUsingAuth =} auth,{=/ isUsingAuth =} {= importIdentifier =})
{=/ operationRoutes =}
export default router

View File

@ -0,0 +1,57 @@
{{={= =}=}}
import prisma from "../dbClient.js"
import {
type WaspEntity,
{=# entities =}
type {= name =},
{=/ entities =}
} from "../entities"
export type Query<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
export type Action<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
{=# isAuthEnabled =}
export type AuthenticatedQuery<Entities extends WaspEntity[] = [], Result = unknown> =
AuthenticatedOperation<Entities, Result>
export type AuthenticatedAction<Entities extends WaspEntity[] = [], Result = unknown> =
AuthenticatedOperation<Entities, Result>
type AuthenticatedOperation<Entities extends WaspEntity[], Result> = (
args: any,
context: {
user: {= userViewName =},
entities: EntityMap<Entities>,
},
) => Promise<Result>
// TODO: This type must match the logic in core/auth.js (if we remove the
// password field from the object there, we must do the same here). Ideally,
// these two things would live in the same place:
// https://github.com/wasp-lang/wasp/issues/965
type {= userViewName =} = Omit<{= userEntityName =}, 'password'>
{=/ isAuthEnabled =}
type Operation<Entities extends WaspEntity[], Result> = (
args: any,
context: {
entities: EntityMap<Entities>,
},
) => Promise<Result>
type PrismaDelegateFor<EntityName extends string> =
{=# entities =}
EntityName extends "{= name =}" ? typeof prisma.{= prismaIdentifier =} :
{=/ entities =}
never
type WaspNameFor<Entity extends WaspEntity> =
{=# entities =}
Entity extends {= name =} ? "{= name =}" :
{=/ entities =}
never
type EntityMap<Entities extends WaspEntity[]> = {
[EntityName in WaspNameFor<Entities[number]>]: PrismaDelegateFor<EntityName>
}

View File

@ -12,6 +12,7 @@ waspBuild/.wasp/build/server/src/config.js
waspBuild/.wasp/build/server/src/core/AuthError.js
waspBuild/.wasp/build/server/src/core/HttpError.js
waspBuild/.wasp/build/server/src/dbClient.js
waspBuild/.wasp/build/server/src/entities/index.ts
waspBuild/.wasp/build/server/src/jobs/core/Job.js
waspBuild/.wasp/build/server/src/jobs/core/SubmittedJob.js
waspBuild/.wasp/build/server/src/jobs/core/allJobs.js
@ -21,6 +22,7 @@ waspBuild/.wasp/build/server/src/jobs/core/simpleJob.js
waspBuild/.wasp/build/server/src/routes/index.js
waspBuild/.wasp/build/server/src/routes/operations/index.js
waspBuild/.wasp/build/server/src/server.ts
waspBuild/.wasp/build/server/src/types/index.ts
waspBuild/.wasp/build/server/src/utils.js
waspBuild/.wasp/build/server/tsconfig.json
waspBuild/.wasp/build/web-app/.npmrc

View File

@ -90,6 +90,13 @@
],
"20c67ca197da3de2d37528ceaff2e40af910be8177f346c6d5c2b2f983810c43"
],
[
[
"file",
"server/src/entities/index.ts"
],
"d9a8968d93bb382e1473765db426fb37b0cbe4a6dc4a158140065e3e657a8ac6"
],
[
[
"file",
@ -153,6 +160,13 @@
],
"c6114654819216004f0ef4a8771a0c1213c655cfd41760fbd835077c53a6b6ba"
],
[
[
"file",
"server/src/types/index.ts"
],
"5e097ef2cf636d2b481c303987aaabeb5276900a6dcbd0fd6fa031c95616058b"
],
[
[
"file",

View File

@ -0,0 +1,8 @@
import {
} from "@prisma/client"
export {
} from "@prisma/client"
export type WaspEntity =
| never

View File

@ -0,0 +1,26 @@
import prisma from "../dbClient.js"
import {
type WaspEntity,
} from "../entities"
export type Query<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
export type Action<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
type Operation<Entities extends WaspEntity[], Result> = (
args: any,
context: {
entities: EntityMap<Entities>,
},
) => Promise<Result>
type PrismaDelegateFor<EntityName extends string> =
never
type WaspNameFor<Entity extends WaspEntity> =
never
type EntityMap<Entities extends WaspEntity[]> = {
[EntityName in WaspNameFor<Entities[number]>]: PrismaDelegateFor<EntityName>
}

View File

@ -12,6 +12,7 @@ waspCompile/.wasp/out/server/src/config.js
waspCompile/.wasp/out/server/src/core/AuthError.js
waspCompile/.wasp/out/server/src/core/HttpError.js
waspCompile/.wasp/out/server/src/dbClient.js
waspCompile/.wasp/out/server/src/entities/index.ts
waspCompile/.wasp/out/server/src/jobs/core/Job.js
waspCompile/.wasp/out/server/src/jobs/core/SubmittedJob.js
waspCompile/.wasp/out/server/src/jobs/core/allJobs.js
@ -21,6 +22,7 @@ waspCompile/.wasp/out/server/src/jobs/core/simpleJob.js
waspCompile/.wasp/out/server/src/routes/index.js
waspCompile/.wasp/out/server/src/routes/operations/index.js
waspCompile/.wasp/out/server/src/server.ts
waspCompile/.wasp/out/server/src/types/index.ts
waspCompile/.wasp/out/server/src/utils.js
waspCompile/.wasp/out/server/tsconfig.json
waspCompile/.wasp/out/web-app/.npmrc

View File

@ -90,6 +90,13 @@
],
"20c67ca197da3de2d37528ceaff2e40af910be8177f346c6d5c2b2f983810c43"
],
[
[
"file",
"server/src/entities/index.ts"
],
"d9a8968d93bb382e1473765db426fb37b0cbe4a6dc4a158140065e3e657a8ac6"
],
[
[
"file",
@ -153,6 +160,13 @@
],
"c6114654819216004f0ef4a8771a0c1213c655cfd41760fbd835077c53a6b6ba"
],
[
[
"file",
"server/src/types/index.ts"
],
"5e097ef2cf636d2b481c303987aaabeb5276900a6dcbd0fd6fa031c95616058b"
],
[
[
"file",

View File

@ -0,0 +1,8 @@
import {
} from "@prisma/client"
export {
} from "@prisma/client"
export type WaspEntity =
| never

View File

@ -0,0 +1,26 @@
import prisma from "../dbClient.js"
import {
type WaspEntity,
} from "../entities"
export type Query<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
export type Action<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
type Operation<Entities extends WaspEntity[], Result> = (
args: any,
context: {
entities: EntityMap<Entities>,
},
) => Promise<Result>
type PrismaDelegateFor<EntityName extends string> =
never
type WaspNameFor<Entity extends WaspEntity> =
never
type EntityMap<Entities extends WaspEntity[]> = {
[EntityName in WaspNameFor<Entities[number]>]: PrismaDelegateFor<EntityName>
}

View File

@ -12,6 +12,7 @@ waspJob/.wasp/out/server/src/config.js
waspJob/.wasp/out/server/src/core/AuthError.js
waspJob/.wasp/out/server/src/core/HttpError.js
waspJob/.wasp/out/server/src/dbClient.js
waspJob/.wasp/out/server/src/entities/index.ts
waspJob/.wasp/out/server/src/ext-src/jobs/bar.js
waspJob/.wasp/out/server/src/jobs/MySpecialJob.js
waspJob/.wasp/out/server/src/jobs/core/Job.js
@ -23,6 +24,7 @@ waspJob/.wasp/out/server/src/jobs/core/simpleJob.js
waspJob/.wasp/out/server/src/routes/index.js
waspJob/.wasp/out/server/src/routes/operations/index.js
waspJob/.wasp/out/server/src/server.ts
waspJob/.wasp/out/server/src/types/index.ts
waspJob/.wasp/out/server/src/utils.js
waspJob/.wasp/out/server/tsconfig.json
waspJob/.wasp/out/web-app/.npmrc

View File

@ -90,6 +90,13 @@
],
"20c67ca197da3de2d37528ceaff2e40af910be8177f346c6d5c2b2f983810c43"
],
[
[
"file",
"server/src/entities/index.ts"
],
"d9a8968d93bb382e1473765db426fb37b0cbe4a6dc4a158140065e3e657a8ac6"
],
[
[
"file",
@ -167,6 +174,13 @@
],
"3680f7afaf38fb9e32cb3bc9c64641224e2d2867bdc912730114cbf9360bf9f0"
],
[
[
"file",
"server/src/types/index.ts"
],
"5e097ef2cf636d2b481c303987aaabeb5276900a6dcbd0fd6fa031c95616058b"
],
[
[
"file",

View File

@ -0,0 +1,8 @@
import {
} from "@prisma/client"
export {
} from "@prisma/client"
export type WaspEntity =
| never

View File

@ -0,0 +1,26 @@
import prisma from "../dbClient.js"
import {
type WaspEntity,
} from "../entities"
export type Query<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
export type Action<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
type Operation<Entities extends WaspEntity[], Result> = (
args: any,
context: {
entities: EntityMap<Entities>,
},
) => Promise<Result>
type PrismaDelegateFor<EntityName extends string> =
never
type WaspNameFor<Entity extends WaspEntity> =
never
type EntityMap<Entities extends WaspEntity[]> = {
[EntityName in WaspNameFor<Entities[number]>]: PrismaDelegateFor<EntityName>
}

View File

@ -17,6 +17,7 @@ waspMigrate/.wasp/out/server/src/config.js
waspMigrate/.wasp/out/server/src/core/AuthError.js
waspMigrate/.wasp/out/server/src/core/HttpError.js
waspMigrate/.wasp/out/server/src/dbClient.js
waspMigrate/.wasp/out/server/src/entities/index.ts
waspMigrate/.wasp/out/server/src/jobs/core/Job.js
waspMigrate/.wasp/out/server/src/jobs/core/SubmittedJob.js
waspMigrate/.wasp/out/server/src/jobs/core/allJobs.js
@ -26,6 +27,7 @@ waspMigrate/.wasp/out/server/src/jobs/core/simpleJob.js
waspMigrate/.wasp/out/server/src/routes/index.js
waspMigrate/.wasp/out/server/src/routes/operations/index.js
waspMigrate/.wasp/out/server/src/server.ts
waspMigrate/.wasp/out/server/src/types/index.ts
waspMigrate/.wasp/out/server/src/utils.js
waspMigrate/.wasp/out/server/tsconfig.json
waspMigrate/.wasp/out/web-app/.npmrc

View File

@ -90,6 +90,13 @@
],
"20c67ca197da3de2d37528ceaff2e40af910be8177f346c6d5c2b2f983810c43"
],
[
[
"file",
"server/src/entities/index.ts"
],
"372c0ae48ae3aeee517586bbc9e02705e7f1398ce6326c7a0876a12e9d8e74e1"
],
[
[
"file",
@ -153,6 +160,13 @@
],
"c6114654819216004f0ef4a8771a0c1213c655cfd41760fbd835077c53a6b6ba"
],
[
[
"file",
"server/src/types/index.ts"
],
"3105fd2e1e6d8dc5481b7f4db21290571e945eb2610cd945cee86254b0e92c6c"
],
[
[
"file",

View File

@ -0,0 +1,11 @@
import {
type Task,
} from "@prisma/client"
export {
type Task,
} from "@prisma/client"
export type WaspEntity =
| Task
| never

View File

@ -0,0 +1,29 @@
import prisma from "../dbClient.js"
import {
type WaspEntity,
type Task,
} from "../entities"
export type Query<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
export type Action<Entities extends WaspEntity[] = [], Result = unknown> = Operation<Entities, Result>
type Operation<Entities extends WaspEntity[], Result> = (
args: any,
context: {
entities: EntityMap<Entities>,
},
) => Promise<Result>
type PrismaDelegateFor<EntityName extends string> =
EntityName extends "Task" ? typeof prisma.task :
never
type WaspNameFor<Entity extends WaspEntity> =
Entity extends Task ? "Task" :
never
type EntityMap<Entities extends WaspEntity[]> = {
[EntityName in WaspNameFor<Entities[number]>]: PrismaDelegateFor<EntityName>
}

View File

@ -1,8 +1,9 @@
import HttpError from '@wasp/core/HttpError.js'
import { Task } from '@wasp/entities/'
import { AuthenticatedAction } from '@wasp/types'
import { getSomeResource } from './serverSetup.js'
export const createTask = async (task, context) => {
export const createTask: AuthenticatedAction<[Task]> = async (task, context) => {
if (!context.user) {
throw new HttpError(401)
}
@ -21,7 +22,7 @@ export const createTask = async (task, context) => {
})
}
export const updateTaskIsDone = async ({ id, isDone }, context) => {
export const updateTaskIsDone: AuthenticatedAction<[Task]> = async ({ id, isDone }, context) => {
if (!context.user) {
throw new HttpError(401)
}
@ -37,7 +38,7 @@ export const updateTaskIsDone = async ({ id, isDone }, context) => {
})
}
export const deleteCompletedTasks = async (args, context) => {
export const deleteCompletedTasks: AuthenticatedAction<[Task]> = async (args, context) => {
if (!context.user) {
throw new HttpError(401)
}
@ -48,7 +49,7 @@ export const deleteCompletedTasks = async (args, context) => {
})
}
export const toggleAllTasks = async (args, context) => {
export const toggleAllTasks: AuthenticatedAction<[Task]> = async (args, context) => {
if (!context.user) {
throw new HttpError(401)
}

View File

@ -1,6 +1,8 @@
import HttpError from '@wasp/core/HttpError.js'
import { Task } from '@wasp/entities'
import { AuthenticatedQuery } from '@wasp/types'
export const getTasks = async (args, context) => {
export const getTasks: AuthenticatedQuery<[Task], Task[]> = async (args, context) => {
if (!context.user) {
throw new HttpError(401)
}
@ -17,11 +19,11 @@ export const getTasks = async (args, context) => {
return tasks
}
export const getNumTasks = async (args, context) => {
export const getNumTasks: AuthenticatedQuery<[Task], number> = async (args, context) => {
return context.entities.Task.count()
}
export const getTask = async ({ id }, context) => {
export const getTask: AuthenticatedQuery<[Task], Task> = async ({ id }, context) => {
if (!context.user) {
throw new HttpError(401)
}

View File

@ -173,6 +173,7 @@ genSrcDir spec =
genServerJs spec
]
<++> genRoutesDir spec
<++> genTypesAndEntitiesDirs spec
<++> genOperationsRoutes spec
<++> genOperations spec
<++> genAuth spec
@ -236,6 +237,32 @@ genRoutesDir spec =
)
]
genTypesAndEntitiesDirs :: AppSpec -> Generator [FileDraft]
genTypesAndEntitiesDirs spec = return [entitiesIndexFileDraft, typesIndexFileDraft]
where
entitiesIndexFileDraft =
C.mkTmplFdWithDstAndData
[relfile|src/entities/index.ts|]
[relfile|src/entities/index.ts|]
(Just $ object ["entities" .= allEntities])
typesIndexFileDraft =
C.mkTmplFdWithDstAndData
[relfile|src/types/index.ts|]
[relfile|src/types/index.ts|]
( Just $
object
[ "entities" .= allEntities,
"isAuthEnabled" .= isJust userEntityName,
"userEntityName" .= fromMaybe "" userEntityName,
"userViewName" .= fromMaybe "" userViewName
]
)
allEntities = map (C.buildEntityData . fst) $ AS.getDecls @AS.Entity.Entity spec
userEntityName = AS.refName . AS.App.Auth.userEntity <$> AS.App.auth (snd $ getApp spec)
-- We might want to move this to a more global location in the future, but
-- it is currently used only in these two files.
userViewName = (++ "View") <$> userEntityName
operationsRouteInRootRouter :: String
operationsRouteInRootRouter = "operations"

View File

@ -12,6 +12,8 @@ module Wasp.Generator.ServerGenerator.Common
asServerFile,
asServerSrcFile,
entityNameToPrismaIdentifier,
buildEntityData,
toESModulesImportPath,
ServerRootDir,
ServerSrcDir,
ServerTemplatesDir,
@ -19,10 +21,12 @@ module Wasp.Generator.ServerGenerator.Common
)
where
import Data.Aeson (object, (.=))
import qualified Data.Aeson as Aeson
import Data.Char (toLower)
import StrongPath (Dir, File', Path', Rel, reldir, relfile, (</>))
import qualified StrongPath as SP
import System.FilePath (splitExtension)
import Wasp.Common (WaspProjectDir)
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
@ -99,3 +103,21 @@ dotEnvServer = [relfile|.env.server|]
-- client SDK identifiers. Useful when creating `context.entities` JS objects in Wasp templates.
entityNameToPrismaIdentifier :: String -> String
entityNameToPrismaIdentifier entityName = toLower (head entityName) : tail entityName
buildEntityData :: String -> Aeson.Value
buildEntityData name =
object
[ "name" .= name,
"prismaIdentifier" .= entityNameToPrismaIdentifier name
]
-- Converts the real name of the source file (i.e., name on disk) into a name
-- that can be used in an ESNext import.
-- Specifically, when using the ESNext module system, all source files must be
-- imported with a '.js' extension (even if they are '.ts' files).
--
-- Details: https://github.com/wasp-lang/wasp/issues/812#issuecomment-1335579353
toESModulesImportPath :: FilePath -> FilePath
toESModulesImportPath = changeExtensionTo "js"
where
changeExtensionTo ext = (++ '.' : ext) . fst . splitExtension

View File

@ -65,7 +65,7 @@ genJob (jobName, job) =
"jobSchedule" .= Aeson.Text.encodeToLazyText (fromMaybe Aeson.Null maybeJobSchedule),
"jobPerformOptions" .= show (fromMaybe AS.JSON.emptyObject maybeJobPerformOptions),
"executorJobRelFP" .= toFilePath (executorJobTemplateInJobsDir (J.executor job)),
"entities" .= maybe [] (map (buildEntityData . AS.refName)) (J.entities job)
"entities" .= maybe [] (map (C.buildEntityData . AS.refName)) (J.entities job)
]
)
where
@ -81,13 +81,6 @@ genJob (jobName, job) =
]
maybeJobSchedule = jobScheduleTmplData <$> J.schedule job
buildEntityData :: String -> Aeson.Value
buildEntityData entityName =
object
[ "name" .= entityName,
"prismaIdentifier" .= C.entityNameToPrismaIdentifier entityName
]
-- Creates a file that is imported on the server to ensure all job JS modules are loaded
-- even if they are not referenced by user code. This ensures schedules are started, etc.
genAllJobImports :: AppSpec -> FileDraft

View File

@ -80,15 +80,9 @@ operationTmplData operation =
object
[ "jsFnImportStatement" .= importStmt,
"jsFnIdentifier" .= importIdentifier,
"entities" .= maybe [] (map (buildEntityData . AS.refName)) (AS.Operation.getEntities operation)
"entities" .= maybe [] (map (C.buildEntityData . AS.refName)) (AS.Operation.getEntities operation)
]
where
(importIdentifier, importStmt) =
getJsImportDetailsForExtFnImport relPosixPathFromOperationFileToExtSrcDir $
AS.Operation.getFn operation
buildEntityData :: String -> Aeson.Value
buildEntityData entityName =
object
[ "name" .= entityName,
"prismaIdentifier" .= C.entityNameToPrismaIdentifier entityName
]

View File

@ -65,9 +65,10 @@ genOperationRoute spec operation tmplFile = return $ C.mkTmplFdWithDstAndData tm
baseTmplData
operationImportPath =
SP.fromRelFileP $
relPosixPathFromOperationsRoutesDirToSrcDir
</> fromJust (SP.relFileToPosix $ operationFileInSrcDir operation)
C.toESModulesImportPath $
SP.fromRelFileP $
relPosixPathFromOperationsRoutesDirToSrcDir
</> fromJust (SP.relFileToPosix $ operationFileInSrcDir operation)
data OperationsRoutesDir

View File

@ -6,7 +6,7 @@ cabal-version: 2.4
-- Consider using hpack, or maybe even hpack-dhall.
name: waspc
version: 0.8.0
version: 0.8.1
description: Please see the README on GitHub at <https://github.com/wasp-lang/wasp/waspc#readme>
homepage: https://github.com/wasp-lang/wasp/waspc#readme
bug-reports: https://github.com/wasp-lang/wasp/issues