From 81ffa6c27c3617675dea7f59094ada246112fa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Sodi=C4=87?= Date: Tue, 24 Jan 2023 14:45:35 +0100 Subject: [PATCH] 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 --- waspc/ChangeLog.md | 37 ++++++++++-- .../templates/server/src/actions/_action.js | 16 +++--- .../templates/server/src/core/auth.js | 4 ++ .../templates/server/src/entities/index.ts | 18 ++++++ .../templates/server/src/queries/_query.js | 18 +++--- .../server/src/routes/operations/index.js | 2 +- .../templates/server/src/types/index.ts | 57 +++++++++++++++++++ .../waspBuild-golden/files.manifest | 2 + .../waspBuild/.wasp/build/.waspchecksums | 14 +++++ .../.wasp/build/server/src/entities/index.ts | 8 +++ .../.wasp/build/server/src/types/index.ts | 26 +++++++++ .../waspCompile-golden/files.manifest | 2 + .../waspCompile/.wasp/out/.waspchecksums | 14 +++++ .../.wasp/out/server/src/entities/index.ts | 8 +++ .../.wasp/out/server/src/types/index.ts | 26 +++++++++ .../waspJob-golden/files.manifest | 2 + .../waspJob/.wasp/out/.waspchecksums | 14 +++++ .../.wasp/out/server/src/entities/index.ts | 8 +++ .../.wasp/out/server/src/types/index.ts | 26 +++++++++ .../waspMigrate-golden/files.manifest | 2 + .../waspMigrate/.wasp/out/.waspchecksums | 14 +++++ .../.wasp/out/server/src/entities/index.ts | 11 ++++ .../.wasp/out/server/src/types/index.ts | 29 ++++++++++ .../src/server/{actions.js => actions.ts} | 11 ++-- .../src/server/{queries.js => queries.ts} | 8 ++- waspc/src/Wasp/Generator/ServerGenerator.hs | 27 +++++++++ .../Wasp/Generator/ServerGenerator/Common.hs | 22 +++++++ .../Generator/ServerGenerator/JobGenerator.hs | 9 +-- .../Generator/ServerGenerator/OperationsG.hs | 8 +-- .../ServerGenerator/OperationsRoutesG.hs | 7 ++- waspc/waspc.cabal | 2 +- 31 files changed, 404 insertions(+), 48 deletions(-) create mode 100644 waspc/data/Generator/templates/server/src/entities/index.ts create mode 100644 waspc/data/Generator/templates/server/src/types/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/entities/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/types/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/entities/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/types/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/entities/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/types/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/entities/index.ts create mode 100644 waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/types/index.ts rename waspc/examples/todoApp/src/server/{actions.js => actions.ts} (76%) rename waspc/examples/todoApp/src/server/{queries.js => queries.ts} (74%) diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index af3420377..25fc4e0ec 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -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 diff --git a/waspc/data/Generator/templates/server/src/actions/_action.js b/waspc/data/Generator/templates/server/src/actions/_action.js index b2b6d0677..309069efd 100644 --- a/waspc/data/Generator/templates/server/src/actions/_action.js +++ b/waspc/data/Generator/templates/server/src/actions/_action.js @@ -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 =} + }, + }) } diff --git a/waspc/data/Generator/templates/server/src/core/auth.js b/waspc/data/Generator/templates/server/src/core/auth.js index bb70d47bf..c248a1d06 100644 --- a/waspc/data/Generator/templates/server/src/core/auth.js +++ b/waspc/data/Generator/templates/server/src/core/auth.js @@ -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 diff --git a/waspc/data/Generator/templates/server/src/entities/index.ts b/waspc/data/Generator/templates/server/src/entities/index.ts new file mode 100644 index 000000000..5b179aff2 --- /dev/null +++ b/waspc/data/Generator/templates/server/src/entities/index.ts @@ -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 diff --git a/waspc/data/Generator/templates/server/src/queries/_query.js b/waspc/data/Generator/templates/server/src/queries/_query.js index b2b6d0677..aa6c15a3a 100644 --- a/waspc/data/Generator/templates/server/src/queries/_query.js +++ b/waspc/data/Generator/templates/server/src/queries/_query.js @@ -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 =} + }, + }) } diff --git a/waspc/data/Generator/templates/server/src/routes/operations/index.js b/waspc/data/Generator/templates/server/src/routes/operations/index.js index cf5c4fe11..e32048b57 100644 --- a/waspc/data/Generator/templates/server/src/routes/operations/index.js +++ b/waspc/data/Generator/templates/server/src/routes/operations/index.js @@ -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 diff --git a/waspc/data/Generator/templates/server/src/types/index.ts b/waspc/data/Generator/templates/server/src/types/index.ts new file mode 100644 index 000000000..6c876a9f0 --- /dev/null +++ b/waspc/data/Generator/templates/server/src/types/index.ts @@ -0,0 +1,57 @@ +{{={= =}=}} +import prisma from "../dbClient.js" +import { + type WaspEntity, + {=# entities =} + type {= name =}, + {=/ entities =} + } from "../entities" + +export type Query = Operation + +export type Action = Operation + +{=# isAuthEnabled =} +export type AuthenticatedQuery = + AuthenticatedOperation + +export type AuthenticatedAction = + AuthenticatedOperation + +type AuthenticatedOperation = ( + args: any, + context: { + user: {= userViewName =}, + entities: EntityMap, + }, +) => Promise + +// 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 = ( + args: any, + context: { + entities: EntityMap, + }, +) => Promise + +type PrismaDelegateFor = + {=# entities =} + EntityName extends "{= name =}" ? typeof prisma.{= prismaIdentifier =} : + {=/ entities =} + never + +type WaspNameFor = + {=# entities =} + Entity extends {= name =} ? "{= name =}" : + {=/ entities =} + never + +type EntityMap = { + [EntityName in WaspNameFor]: PrismaDelegateFor +} diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/files.manifest b/waspc/e2e-test/test-outputs/waspBuild-golden/files.manifest index 201c13b7d..c54b2fe38 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/files.manifest +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/files.manifest @@ -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 diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums index 69b096daf..2f2ce9b56 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/.waspchecksums @@ -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", diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/entities/index.ts b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/entities/index.ts new file mode 100644 index 000000000..1a915ed15 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/entities/index.ts @@ -0,0 +1,8 @@ +import { +} from "@prisma/client" + +export { +} from "@prisma/client" + +export type WaspEntity = + | never diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/types/index.ts b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/types/index.ts new file mode 100644 index 000000000..6bec84654 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/.wasp/build/server/src/types/index.ts @@ -0,0 +1,26 @@ +import prisma from "../dbClient.js" +import { + type WaspEntity, + } from "../entities" + +export type Query = Operation + +export type Action = Operation + + +type Operation = ( + args: any, + context: { + entities: EntityMap, + }, +) => Promise + +type PrismaDelegateFor = + never + +type WaspNameFor = + never + +type EntityMap = { + [EntityName in WaspNameFor]: PrismaDelegateFor +} diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/files.manifest b/waspc/e2e-test/test-outputs/waspCompile-golden/files.manifest index cc537589f..4d414a28a 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/files.manifest +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/files.manifest @@ -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 diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums index 421950c56..9f09e2f13 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/.waspchecksums @@ -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", diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/entities/index.ts b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/entities/index.ts new file mode 100644 index 000000000..1a915ed15 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/entities/index.ts @@ -0,0 +1,8 @@ +import { +} from "@prisma/client" + +export { +} from "@prisma/client" + +export type WaspEntity = + | never diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/types/index.ts b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/types/index.ts new file mode 100644 index 000000000..6bec84654 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/.wasp/out/server/src/types/index.ts @@ -0,0 +1,26 @@ +import prisma from "../dbClient.js" +import { + type WaspEntity, + } from "../entities" + +export type Query = Operation + +export type Action = Operation + + +type Operation = ( + args: any, + context: { + entities: EntityMap, + }, +) => Promise + +type PrismaDelegateFor = + never + +type WaspNameFor = + never + +type EntityMap = { + [EntityName in WaspNameFor]: PrismaDelegateFor +} diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/files.manifest b/waspc/e2e-test/test-outputs/waspJob-golden/files.manifest index 09e1c0062..acec1b10b 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/files.manifest +++ b/waspc/e2e-test/test-outputs/waspJob-golden/files.manifest @@ -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 diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums index 3865b5f4e..68de64e50 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/.waspchecksums @@ -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", diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/entities/index.ts b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/entities/index.ts new file mode 100644 index 000000000..1a915ed15 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/entities/index.ts @@ -0,0 +1,8 @@ +import { +} from "@prisma/client" + +export { +} from "@prisma/client" + +export type WaspEntity = + | never diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/types/index.ts b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/types/index.ts new file mode 100644 index 000000000..6bec84654 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/.wasp/out/server/src/types/index.ts @@ -0,0 +1,26 @@ +import prisma from "../dbClient.js" +import { + type WaspEntity, + } from "../entities" + +export type Query = Operation + +export type Action = Operation + + +type Operation = ( + args: any, + context: { + entities: EntityMap, + }, +) => Promise + +type PrismaDelegateFor = + never + +type WaspNameFor = + never + +type EntityMap = { + [EntityName in WaspNameFor]: PrismaDelegateFor +} diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/files.manifest b/waspc/e2e-test/test-outputs/waspMigrate-golden/files.manifest index 6eaa29fe5..4aab48d67 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/files.manifest +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/files.manifest @@ -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 diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums index 07b118a50..13ed47c78 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/.waspchecksums @@ -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", diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/entities/index.ts b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/entities/index.ts new file mode 100644 index 000000000..d23cc4531 --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/entities/index.ts @@ -0,0 +1,11 @@ +import { + type Task, +} from "@prisma/client" + +export { + type Task, +} from "@prisma/client" + +export type WaspEntity = + | Task + | never diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/types/index.ts b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/types/index.ts new file mode 100644 index 000000000..a0739a68f --- /dev/null +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/.wasp/out/server/src/types/index.ts @@ -0,0 +1,29 @@ +import prisma from "../dbClient.js" +import { + type WaspEntity, + type Task, + } from "../entities" + +export type Query = Operation + +export type Action = Operation + + +type Operation = ( + args: any, + context: { + entities: EntityMap, + }, +) => Promise + +type PrismaDelegateFor = + EntityName extends "Task" ? typeof prisma.task : + never + +type WaspNameFor = + Entity extends Task ? "Task" : + never + +type EntityMap = { + [EntityName in WaspNameFor]: PrismaDelegateFor +} diff --git a/waspc/examples/todoApp/src/server/actions.js b/waspc/examples/todoApp/src/server/actions.ts similarity index 76% rename from waspc/examples/todoApp/src/server/actions.js rename to waspc/examples/todoApp/src/server/actions.ts index 1150ccfca..49d3a7c78 100644 --- a/waspc/examples/todoApp/src/server/actions.js +++ b/waspc/examples/todoApp/src/server/actions.ts @@ -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) } diff --git a/waspc/examples/todoApp/src/server/queries.js b/waspc/examples/todoApp/src/server/queries.ts similarity index 74% rename from waspc/examples/todoApp/src/server/queries.js rename to waspc/examples/todoApp/src/server/queries.ts index 5949beed2..d24b9b6cd 100644 --- a/waspc/examples/todoApp/src/server/queries.js +++ b/waspc/examples/todoApp/src/server/queries.ts @@ -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) } diff --git a/waspc/src/Wasp/Generator/ServerGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator.hs index d8b6c177c..d67028bc8 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator.hs @@ -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" diff --git a/waspc/src/Wasp/Generator/ServerGenerator/Common.hs b/waspc/src/Wasp/Generator/ServerGenerator/Common.hs index 67191f914..2919c6d8f 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/Common.hs @@ -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 diff --git a/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs index 8288ec812..f8a2b6ebf 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs @@ -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 diff --git a/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs b/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs index af9f1101c..24e7894ec 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/OperationsG.hs @@ -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 - ] diff --git a/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs b/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs index b4a9c07f6..41baa2281 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs @@ -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 diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 76c308acd..a39a53eed 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -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 homepage: https://github.com/wasp-lang/wasp/waspc#readme bug-reports: https://github.com/wasp-lang/wasp/issues