mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-10-05 19:58:31 +03:00
Added fixing of NodeJS operations files.
This commit is contained in:
parent
286117b404
commit
08c235d12e
0
waspc/Operation.js
Normal file
0
waspc/Operation.js
Normal file
@ -3,7 +3,8 @@ module Wasp.AI.GenerateNewProject
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad (forM)
|
||||
import Control.Monad (forM, forM_)
|
||||
import Data.List (nub)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import NeatInterpolation (trimming)
|
||||
@ -11,7 +12,8 @@ import StrongPath (File', Path, Rel, System)
|
||||
import Wasp.AI.CodeAgent (CodeAgent, writeToLog)
|
||||
import Wasp.AI.GenerateNewProject.Common (NewProjectDetails (..))
|
||||
import Wasp.AI.GenerateNewProject.Entity (writeEntitiesToWaspFile)
|
||||
import Wasp.AI.GenerateNewProject.Operation (OperationType (..), generateAndWriteOperation)
|
||||
import Wasp.AI.GenerateNewProject.Operation (OperationType (..), generateAndWriteOperation, getOperationJsFilePath)
|
||||
import Wasp.AI.GenerateNewProject.OperationsJsFile (fixOperationsJsFile)
|
||||
import Wasp.AI.GenerateNewProject.Page (generateAndWritePage)
|
||||
import Wasp.AI.GenerateNewProject.Plan (generatePlan)
|
||||
import qualified Wasp.AI.GenerateNewProject.Plan as Plan
|
||||
@ -64,6 +66,12 @@ generateNewProject newProjectDetails waspProjectSkeletonFiles = do
|
||||
fixWaspFile newProjectDetails waspFilePath
|
||||
writeToLog "Wasp file fixed."
|
||||
|
||||
writeToLog "Fixing any mistakes in NodeJS operations files..."
|
||||
forM_ (nub $ getOperationJsFilePath <$> (queries <> actions)) $ \opFp -> do
|
||||
fixOperationsJsFile newProjectDetails waspFilePath opFp
|
||||
writeToLog $ T.pack $ "Fixed NodeJS operations file '" <> opFp <> "'."
|
||||
writeToLog "NodeJS operations files fixed."
|
||||
|
||||
-- TODO: what about having additional step here that goes through all the files once again and
|
||||
-- fixes any stuff in them (Wasp, JS files)? REPL?
|
||||
|
||||
|
@ -5,12 +5,16 @@ module Wasp.AI.GenerateNewProject.Operation
|
||||
Operation (..),
|
||||
OperationType (..),
|
||||
OperationImpl (..),
|
||||
actionDocPrompt,
|
||||
queryDocPrompt,
|
||||
getOperationJsFilePath,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (FromJSON)
|
||||
import Data.Aeson.Types (ToJSON)
|
||||
import Data.List (find, intercalate, isInfixOf, isPrefixOf)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import GHC.Generics (Generic)
|
||||
import NeatInterpolation (trimming)
|
||||
@ -106,68 +110,6 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do
|
||||
Action -> actionDocPrompt
|
||||
Query -> queryDocPrompt
|
||||
|
||||
actionDocPrompt =
|
||||
[trimming|
|
||||
Action is implemented via Wasp declaration and corresponding NodeJS implementation.
|
||||
|
||||
Example of Wasp declaration:
|
||||
|
||||
```wasp
|
||||
action updateTaskIsDone {
|
||||
fn: import { updateTaskIsDone } from "@server/taskActions.js",
|
||||
entities: [Task] // Entities that action uses.
|
||||
}
|
||||
```
|
||||
|
||||
Example of NodeJS implementation:
|
||||
|
||||
```js
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
|
||||
export const updateTaskIsDone = (args, context) => {
|
||||
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
|
||||
|
||||
return context.entities.Task.update({ // prisma object
|
||||
where: { args.id },
|
||||
data: { args.isDone }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Action can then be easily called from the client, via Wasp's RPC mechanism.
|
||||
|]
|
||||
|
||||
queryDocPrompt =
|
||||
[trimming|
|
||||
Query is implemented via Wasp declaration and corresponding NodeJS implementation.
|
||||
It is important that Query doesn't do any mutations, be it on the server or external world.
|
||||
|
||||
Example of Wasp declaration:
|
||||
|
||||
```wasp
|
||||
query fetchFilteredTasks {
|
||||
fn: import { getFilteredTasks } from "@server/taskQueries.js",
|
||||
entities: [Task] // Entities that query uses.
|
||||
}
|
||||
```
|
||||
|
||||
Example of NodeJS implementation:
|
||||
|
||||
```js
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
|
||||
export const getFilteredTasks = async (args, context) => {
|
||||
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
|
||||
|
||||
return context.entities.Task.findMany({
|
||||
where: { isDone: args.isDone }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Query can then be easily called from the client, via Wasp's RPC mechanism.
|
||||
|]
|
||||
|
||||
fixOperationImplIfNeeded :: OperationImpl -> CodeAgent OperationImpl
|
||||
fixOperationImplIfNeeded operationImpl = do
|
||||
let issues = checkWaspDecl operationImpl <> checkJsImpl operationImpl
|
||||
@ -194,6 +136,70 @@ generateOperation operationType newProjectDetails entityPlans operationPlan = do
|
||||
}
|
||||
]
|
||||
|
||||
actionDocPrompt :: Text
|
||||
actionDocPrompt =
|
||||
[trimming|
|
||||
Action is implemented via Wasp declaration and corresponding NodeJS implementation.
|
||||
|
||||
Example of Wasp declaration:
|
||||
|
||||
```wasp
|
||||
action updateTaskIsDone {
|
||||
fn: import { updateTaskIsDone } from "@server/taskActions.js",
|
||||
entities: [Task] // Entities that action uses.
|
||||
}
|
||||
```
|
||||
|
||||
Example of NodeJS implementation:
|
||||
|
||||
```js
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
|
||||
export const updateTaskIsDone = (args, context) => {
|
||||
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
|
||||
|
||||
return context.entities.Task.update({ // prisma object
|
||||
where: { args.id },
|
||||
data: { args.isDone }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Action can then be easily called from the client, via Wasp's RPC mechanism.
|
||||
|]
|
||||
|
||||
queryDocPrompt :: Text
|
||||
queryDocPrompt =
|
||||
[trimming|
|
||||
Query is implemented via Wasp declaration and corresponding NodeJS implementation.
|
||||
It is important that Query doesn't do any mutations, be it on the server or external world.
|
||||
|
||||
Example of Wasp declaration:
|
||||
|
||||
```wasp
|
||||
query fetchFilteredTasks {
|
||||
fn: import { getFilteredTasks } from "@server/taskQueries.js",
|
||||
entities: [Task] // Entities that query uses.
|
||||
}
|
||||
```
|
||||
|
||||
Example of NodeJS implementation:
|
||||
|
||||
```js
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
|
||||
export const getFilteredTasks = async (args, context) => {
|
||||
if (!context.user) { throw new HttpError(403) } // If user needs to be authenticated.
|
||||
|
||||
return context.entities.Task.findMany({
|
||||
where: { isDone: args.isDone }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Query can then be easily called from the client, via Wasp's RPC mechanism.
|
||||
|]
|
||||
|
||||
-- TODO: This is quite manual here, checking the AST!
|
||||
-- Consider instead generating entities during assembling Plan,
|
||||
-- and then using those to manually construct the wasp decl, so we are
|
||||
@ -240,16 +246,23 @@ writeOperationToJsFile :: Operation -> CodeAgent ()
|
||||
writeOperationToJsFile operation =
|
||||
-- TODO: An issue we have here is that if other operation already did the same import,
|
||||
-- we don't know and we import it again.
|
||||
-- One thing we can do it supply chatGPT with a list of imports that are already there.
|
||||
-- Second thing we can do is to look for same lines at the start of the file, but that sounds fragile.
|
||||
-- One thing we can do is supply chatGPT with a list of imports that are already there.
|
||||
-- Second thing we can do is to look for same lines at the start of the file, but that sounds
|
||||
-- fragile.
|
||||
-- Maybe best to read and pass previous imports (we would have to do that above somewhere).
|
||||
-- Or even the whole file? Hmmmmm.
|
||||
-- Right now we fix this later, while fixing the whole operations file, but we could try to fix
|
||||
-- it here, earlier.
|
||||
writeToFile path $
|
||||
(jsImportsBlock <>) . (<> jsImpl) . maybe "" (<> "\n\n")
|
||||
where
|
||||
path = resolvePath $ Plan.opFnPath $ opPlan operation
|
||||
path = getOperationJsFilePath operation
|
||||
jsImpl = T.pack $ opJsImpl $ opImpl operation
|
||||
jsImportsBlock = T.pack $ maybe "" (<> "\n") $ opJsImports $ opImpl operation
|
||||
|
||||
getOperationJsFilePath :: Operation -> FilePath
|
||||
getOperationJsFilePath operation = resolvePath $ Plan.opFnPath $ opPlan operation
|
||||
where
|
||||
pathPrefix = "@server/"
|
||||
resolvePath p | pathPrefix `isPrefixOf` p = "src/" <> drop (length ("@" :: String)) p
|
||||
resolvePath _ = error "path incorrectly formatted, should start with " <> pathPrefix <> "."
|
||||
|
94
waspc/src/Wasp/AI/GenerateNewProject/OperationsJsFile.hs
Normal file
94
waspc/src/Wasp/AI/GenerateNewProject/OperationsJsFile.hs
Normal file
@ -0,0 +1,94 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module Wasp.AI.GenerateNewProject.OperationsJsFile
|
||||
( fixOperationsJsFile,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (FromJSON)
|
||||
import Data.Aeson.Types (ToJSON)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import GHC.Generics (Generic)
|
||||
import NeatInterpolation (trimming)
|
||||
import Wasp.AI.CodeAgent (CodeAgent, getFile, writeToFile)
|
||||
import Wasp.AI.GenerateNewProject.Common
|
||||
( NewProjectDetails,
|
||||
defaultChatGPTParams,
|
||||
queryChatGPTForJSON,
|
||||
)
|
||||
import Wasp.AI.GenerateNewProject.Common.Prompts (appDescriptionBlock)
|
||||
import qualified Wasp.AI.GenerateNewProject.Common.Prompts as Prompts
|
||||
import Wasp.AI.GenerateNewProject.Operation (actionDocPrompt, queryDocPrompt)
|
||||
import Wasp.AI.OpenAI.ChatGPT (ChatMessage (..), ChatRole (..))
|
||||
|
||||
fixOperationsJsFile :: NewProjectDetails -> FilePath -> FilePath -> CodeAgent ()
|
||||
fixOperationsJsFile newProjectDetails waspFilePath opJsFilePath = do
|
||||
currentWaspFileContent <- fromMaybe (error "couldn't find wasp file") <$> getFile waspFilePath
|
||||
currentOpJsFileContent <- fromMaybe (error "couldn't find operation js file to fix") <$> getFile opJsFilePath
|
||||
-- TODO: Would be great if we could compile it, check for compiler errors and then also provide those.
|
||||
-- For that however, we would likely need the whole Wasp file generated on the disk,
|
||||
-- with npm dependencies installed, so we skipped it for now.
|
||||
fixedOpJsFile <-
|
||||
queryChatGPTForJSON
|
||||
defaultChatGPTParams
|
||||
[ ChatMessage {role = System, content = Prompts.systemPrompt},
|
||||
ChatMessage {role = User, content = fixOpJsFilePrompt currentWaspFileContent currentOpJsFileContent}
|
||||
]
|
||||
writeToFile opJsFilePath (const $ opJsFileContent fixedOpJsFile)
|
||||
where
|
||||
fixOpJsFilePrompt currentWaspFileContent currentOpJsFileContent =
|
||||
[trimming|
|
||||
${basicWaspLangInfoPrompt}
|
||||
|
||||
${queryDocPrompt}
|
||||
|
||||
${actionDocPrompt}
|
||||
|
||||
===============
|
||||
|
||||
We are together building a new Wasp app (description at the end of prompt).
|
||||
|
||||
Here is a wasp file that we generated together so far:
|
||||
```wasp
|
||||
${currentWaspFileContent}
|
||||
```
|
||||
|
||||
Here is a NodeJS file (${opJsFilePathText}) containing some Wasp Operations
|
||||
(Queries and Actions) that we generated together earlier:
|
||||
```js
|
||||
${currentOpJsFileContent}
|
||||
```
|
||||
|
||||
===============
|
||||
|
||||
The NodeJS file with operations likely has some mistakes: let's fix it!
|
||||
|
||||
Some common mistakes to look for:
|
||||
- "TODO" comments or "..." that should be replaced with actual implementation.
|
||||
Fix these by replacing them with actual implementation.
|
||||
- Duplicate imports. If there are any, make sure to remove them.
|
||||
- Redundant imports of prisma client or of prisma entities. Those imports are not needed -> remove them!
|
||||
- There might be some invalid JS syntax -> fix it if there is any.
|
||||
- If there is some obvious refactoring that could improve code quality, go for it.
|
||||
|
||||
With this in mind, generate a new, fixed NodeJS file with operations (${opJsFilePathText}).
|
||||
Do actual fixes, don't leave comments with "TODO"!
|
||||
Please respond ONLY with a valid JSON of the format { opJsFileContent: string }.
|
||||
There should be no other text in your response. Don't wrap content with the "```" code delimiters.
|
||||
|
||||
${appDescriptionBlockText}
|
||||
|]
|
||||
appDescriptionBlockText = appDescriptionBlock newProjectDetails
|
||||
basicWaspLangInfoPrompt = Prompts.basicWaspLangInfo
|
||||
opJsFilePathText = T.pack opJsFilePath
|
||||
|
||||
data OperationsJsFile = OperationsJsFile
|
||||
{ opJsFileContent :: Text
|
||||
}
|
||||
deriving (Generic, Show)
|
||||
|
||||
instance FromJSON OperationsJsFile
|
||||
|
||||
instance ToJSON OperationsJsFile
|
@ -144,6 +144,7 @@ library
|
||||
Wasp.AI.GenerateNewProject.Common.Prompts
|
||||
Wasp.AI.GenerateNewProject.Entity
|
||||
Wasp.AI.GenerateNewProject.Operation
|
||||
Wasp.AI.GenerateNewProject.OperationsJsFile
|
||||
Wasp.AI.GenerateNewProject.Page
|
||||
Wasp.AI.GenerateNewProject.Plan
|
||||
Wasp.AI.GenerateNewProject.Skeleton
|
||||
|
Loading…
Reference in New Issue
Block a user