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

Fixes CRUD feature to work with SDK #1656

Merged
merged 11 commits into from
Jan 25, 2024
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{{={= =}=}}
import { createAction } from "../actions/core";
import { useAction } from "../actions";
import { createQuery } from "../queries/core";
import { useQuery } from "../queries";
import { createAction } from "wasp/rpc/actions/core";
import { useAction } from "wasp/rpc";
import { createQuery } from "wasp/rpc/queries/core";
import { useQuery } from "wasp/rpc";
import {
{=# operations.Get =}
GetQueryResolved,
Expand All @@ -19,7 +19,7 @@ import {
{=# operations.Delete =}
DeleteActionResolved,
{=/ operations.Delete =}
} from '../../../server/src/crud/{= name =}'
} from 'wasp/server/crud/{= name =}'

function createCrud() {
{=# operations.Get =}
Expand Down
10 changes: 9 additions & 1 deletion waspc/data/Generator/templates/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@
"./rpc": "./dist/rpc/index.js",
{=! Used by users, documented. =}
"./rpc/queries": "./dist/rpc/queries/index.js",
{=! Used by our code, uncodumented (but accessible) for users. =}
"./rpc/queries/core": "./dist/rpc/queries/core.js",
{=! Used by users, documented. =}
"./rpc/actions": "./dist/rpc/actions/index.js",
{=! Used by our code, uncodumented (but accessible) for users. =}
"./rpc/actions/core": "./dist/rpc/actions/core.js",
{=! Used by our code, uncodumented (but accessible) for users. =}
"./rpc/queryClient": "./dist/rpc/queryClient.js",
{=! Used by users, documented. =}
"./types": "./dist/types/index.js",
Expand Down Expand Up @@ -95,7 +99,11 @@
{=! Used by our code, uncodumented (but accessible) for users. =}
"./server/actions": "./dist/server/actions/index.js",
{=! Used by our code, uncodumented (but accessible) for users. =}
"./server/queries": "./dist/server/queries/index.js"
"./server/queries": "./dist/server/queries/index.js",
{=! Used by users, documented. =}
"./crud/*": "./dist/crud/*",
{=! Used by our code, uncodumented (but accessible) for users. =}
"./server/crud/*": "./dist/server/crud/*"
},
"license": "ISC",
"include": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import type {
Query,
{=/ isAuthEnabled =}
_{= crud.entityUpper =},
} from "../_types";
} from "wasp/server/_types";
import type { Prisma } from "@prisma/client";
import { Payload } from "../_types/serialization.js";
import { Payload } from "wasp/server/_types/serialization";
import type {
{= crud.entityUpper =},
} from "../entities";
} from "wasp/entities";
{=# isAuthEnabled =}
import { throwInvalidCredentialsError } from '../auth/utils.js'
import { throwInvalidCredentialsError } from 'wasp/auth/utils'
{=/ isAuthEnabled =}
{=# overrides.GetAll.isDefined =}
{=& overrides.GetAll.importStatement =}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{={= =}=}}
import express from 'express'
import * as crud from '../../crud/{= crud.name =}.js'
import * as crud from 'wasp/server/crud/{= crud.name =}.js'
import { createAction, createQuery } from '../../middleware/operations.js'
{=# isAuthEnabled =}
import auth from 'wasp/core/auth'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"./core/auth": "./dist/core/auth.js",
"./rpc": "./dist/rpc/index.js",
"./rpc/queries": "./dist/rpc/queries/index.js",
"./rpc/queries/core": "./dist/rpc/queries/core.js",
"./rpc/actions": "./dist/rpc/actions/index.js",
"./rpc/actions/core": "./dist/rpc/actions/core.js",
"./rpc/queryClient": "./dist/rpc/queryClient.js",
"./types": "./dist/types/index.js",
"./auth/login": "./dist/auth/login.js",
Expand Down Expand Up @@ -48,7 +50,9 @@
"./server/config": "./dist/server/config.js",
"./server/utils": "./dist/server/utils.js",
"./server/actions": "./dist/server/actions/index.js",
"./server/queries": "./dist/server/queries/index.js"
"./server/queries": "./dist/server/queries/index.js",
"./crud/*": "./dist/crud/*",
"./server/crud/*": "./dist/server/crud/*"
},
"license": "ISC",
"include": [
Expand Down
13 changes: 13 additions & 0 deletions waspc/examples/todo-typescript/main.wasp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ entity Task {=psl
userId Int
psl=}

crud Tasks {
entity: Task,
operations: {
getAll: {
overrideFn: import { getAllQuery } from "@src/task/crud.js",
},
create: {},
update: {},
get: {},
delete: {}
}
}

route RootRoute { path: "/", to: MainPage }
page MainPage {
authRequired: true,
Expand Down
11 changes: 8 additions & 3 deletions waspc/examples/todo-typescript/src/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import './Main.css'
import "./Main.css";
import React, { useEffect, FormEventHandler, FormEvent } from 'react'
import logout from 'wasp/auth/logout'
import { useQuery, useAction } from 'wasp/rpc' // Wasp uses a thin wrapper around react-query
Expand All @@ -8,12 +8,15 @@ import waspLogo from './waspLogo.png'
import type { Task } from 'wasp/entities'
import type { User } from 'wasp/auth/types'
import { getFirstProviderUserId } from 'wasp/auth/user'
import { Tasks } from 'wasp/crud/Tasks'

export const MainPage = ({ user }: { user: User }) => {
const { data: tasks, isLoading, error } = useQuery(getTasks)

if (isLoading) return 'Loading...'
if (error) return 'Error: ' + error
const { data: allTasks } = Tasks.getAll.useQuery()

if (isLoading) return "Loading..."
if (error) return "Error: " + error

const completed = tasks?.filter((task) => task.isDone).map((task) => task.id)

Expand All @@ -28,6 +31,8 @@ export const MainPage = ({ user }: { user: User }) => {
)}
<NewTaskForm />
{tasks && <TasksList tasks={tasks} />}
<h2>All</h2>
{allTasks && <TasksList tasks={allTasks} />}
<div className="buttons">
<button
className="logout"
Expand Down
6 changes: 6 additions & 0 deletions waspc/examples/todo-typescript/src/task/crud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Task } from "wasp/entities";
import { GetAllQuery } from "wasp/server/crud/Tasks";

export const getAllQuery = ((args, context) => {
return context.entities.Task.findMany({});
}) satisfies GetAllQuery<{}, Task[]>;
11 changes: 10 additions & 1 deletion waspc/src/Wasp/Generator/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ module Wasp.Generator.Common
makeJsonWithEntityData,
GeneratedSrcDir,
makeJsArrayFromHaskellList,
dropExtensionFromImportPath,
)
where

import Data.Aeson (KeyValue ((.=)), object)
import qualified Data.Aeson as Aeson
import Data.List (intercalate)
import StrongPath (Dir, Rel, reldir)
import Data.Maybe (fromJust)
import StrongPath (Dir, File, Path, Posix, Rel, reldir)
import qualified StrongPath as SP
import StrongPath.Types (Path')
import System.FilePath (splitExtension)
import Wasp.Generator.Templates (TemplatesDir)
import qualified Wasp.SemanticVersion as SV
import Wasp.Util (toLowerFirst)
Expand Down Expand Up @@ -72,3 +76,8 @@ makeJsArrayFromHaskellList :: [String] -> String
makeJsArrayFromHaskellList list = "[" ++ intercalate ", " listOfJsStrings ++ "]"
where
listOfJsStrings = map (\s -> "'" ++ s ++ "'") list

dropExtensionFromImportPath :: Path Posix (Rel r) (File f) -> Path Posix (Rel r) (File f)
dropExtensionFromImportPath = fromJust . SP.parseRelFileP . dropExtension . SP.fromRelFileP
where
dropExtension = fst . splitExtension
2 changes: 2 additions & 0 deletions waspc/src/Wasp/Generator/SdkGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import Wasp.Generator.Monad (Generator)
import qualified Wasp.Generator.NpmDependencies as N
import Wasp.Generator.SdkGenerator.AuthG (genAuth)
import qualified Wasp.Generator.SdkGenerator.Common as C
import Wasp.Generator.SdkGenerator.CrudG (genCrud)
import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc)
import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations)
import qualified Wasp.Generator.ServerGenerator.AuthG as ServerAuthG
Expand Down Expand Up @@ -90,6 +91,7 @@ genSdkReal spec =
<++> genUniversalDir
<++> genExternalCodeDir (AS.externalCodeFiles spec)
<++> genEntitiesAndServerTypesDirs spec
<++> genCrud spec
where
genFileCopy = return . C.mkTmplFd

Expand Down
82 changes: 82 additions & 0 deletions waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
module Wasp.Generator.SdkGenerator.CrudG
( genCrud,
)
where

import Data.Aeson (KeyValue ((.=)), object)
import qualified Data.Aeson.Types as Aeson.Types
import Data.Maybe (fromJust)
import StrongPath (reldir, relfile, (</>))
import qualified StrongPath as SP
import Wasp.AppSpec (AppSpec, getCruds)
import qualified Wasp.AppSpec as AS
import qualified Wasp.AppSpec.App as AS.App
import qualified Wasp.AppSpec.App.Auth as AS.Auth
import qualified Wasp.AppSpec.Crud as AS.Crud
import Wasp.AppSpec.Valid (getApp, getIdFieldFromCrudEntity, isAuthEnabled)
import Wasp.Generator.Crud (crudDeclarationToOperationsList, getCrudFilePath, getCrudOperationJson, makeCrudOperationKeyAndJsonPair)
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.JsImport as GJI
import Wasp.Generator.Monad (Generator)
import qualified Wasp.Generator.SdkGenerator.Common as C
import Wasp.Generator.SdkGenerator.ServerOpsGenerator (extImportToJsImport)
import Wasp.Util ((<++>))

genCrud :: AppSpec -> Generator [FileDraft]
genCrud spec =
if areThereAnyCruds
then
genCrudOperations spec cruds
<++> genCrudServerOperations spec cruds
else return []
where
cruds = getCruds spec
areThereAnyCruds = not $ null cruds

genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved from WebAppGenerator

genCrudOperations spec cruds = return $ map genCrudOperation cruds
where
genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft
genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData)
where
tmplPath = [relfile|crud/_crud.ts|]
destPath = [reldir|crud|] </> fromJust (SP.parseRelFile (name ++ ".ts"))
tmplData = getCrudOperationJson name crud idField
idField = getIdFieldFromCrudEntity spec crud

genCrudServerOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft]
Copy link
Contributor Author

@infomiho infomiho Jan 23, 2024

Choose a reason for hiding this comment

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

Moved from ServerGenerator and adjusted to generated types for full-stack types

genCrudServerOperations spec cruds = return $ map genCrudOperation cruds
where
genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft
genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData)
where
tmplPath = [relfile|server/crud/_operations.ts|]
destPath = [reldir|server/crud|] </> getCrudFilePath name "ts"
tmplData =
object
[ "crud" .= getCrudOperationJson name crud idField,
"isAuthEnabled" .= isAuthEnabled spec,
"userEntityUpper" .= maybeUserEntity,
"overrides" .= object overrides,
"queryType" .= queryTsType,
"actionType" .= actionTsType
]
idField = getIdFieldFromCrudEntity spec crud
maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth
maybeAuth = AS.App.auth $ snd $ getApp spec

queryTsType :: String
queryTsType = if isAuthEnabled spec then "AuthenticatedQuery" else "Query"

actionTsType :: String
actionTsType = if isAuthEnabled spec then "AuthenticatedAction" else "Action"

overrides :: [Aeson.Types.Pair]
overrides = map operationToOverrideImport crudOperations

crudOperations = crudDeclarationToOperationsList crud

operationToOverrideImport :: (AS.Crud.CrudOperation, AS.Crud.CrudOperationOptions) -> Aeson.Types.Pair
operationToOverrideImport (operation, options) = makeCrudOperationKeyAndJsonPair operation importJson
where
importJson = GJI.jsImportToImportJson $ extImportToJsImport <$> AS.Crud.overrideFn options
27 changes: 12 additions & 15 deletions waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Wasp.AppSpec.Operation (getName)
import qualified Wasp.AppSpec.Operation as AS.Operation
import qualified Wasp.AppSpec.Query as AS.Query
import Wasp.AppSpec.Valid (isAuthEnabled)
import Wasp.Generator.Common (makeJsonWithEntityData)
import Wasp.Generator.Common (dropExtensionFromImportPath, makeJsonWithEntityData)
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.JsImport as GJI
import Wasp.Generator.Monad (Generator)
Expand Down Expand Up @@ -140,21 +140,18 @@ extImportToJsImport extImport@(EI.ExtImport extImportName extImportPath) =
_importAlias = Just $ EI.importIdentifier extImport ++ "_ext"
}
where
importPath = C.makeSdkImportPath $ extCodeDirP </> SP.castRel extImportPath
importPath = C.makeSdkImportPath $ extCodeDirP </> importPathWithoutExtension
-- We are dropping extensions here because of the way the "exports" field works in package.json.
-- If we left the extension, it would resolve the import e.g. ext-src/foo.js to ext-src/foo.js.js,
-- which is not what we want.
importPathWithoutExtension = dropExtensionFromImportPath (SP.castRel extImportPath)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please take at look at this piece and the comment.

extCodeDirP = fromJust $ SP.relDirToPosix C.extCodeDirInSdkRootDir
importName = GJI.extImportNameToJsImportName extImportName

-- extImportToImportJson :: EI.ExtImport -> Aeson.Value
-- extImportToImportJson extImport@(EI.ExtImport importName importPath) =
-- object
-- [ "isDefined" .= True,
-- "importStatement" .= Debug.trace jsImportStmt jsImportStmt,
-- "importIdentifier" .= importAlias
-- ]
-- extImportToImportJson ::
-- Path Posix (Rel importLocation) (Dir ServerSrcDir) ->
-- Maybe EI.ExtImport ->
-- Aeson.Value
-- extImportToImportJson pathFromImportLocationToSrcDir maybeExtImport = GJI.jsImportToImportJson jsImport
-- where
-- jsImportStmt = case importName of
-- EI.ExtImportModule n -> "import " ++ n ++ " from '" ++ importPathStr ++ "'"
-- EI.ExtImportField n -> "import { " ++ n ++ " as " ++ importAlias ++ " } from '" ++ importPathStr ++ "'"
-- importPathStr = C.makeSdkImportPath $ extCodeDirP </> SP.castRel importPath
-- extCodeDirP = fromJust $ SP.relDirToPosix C.extCodeDirInSdkRootDir
-- importAlias = EI.importIdentifier extImport ++ "User"
-- jsImport = extImportToJsImport pathFromImportLocationToSrcDir <$> maybeExtImport
Loading
Loading