From 9bdc3395a695ffe3849f50e1c1b4234dc35f1ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0o=C5=A1i=C4=87?= Date: Tue, 30 Jan 2024 12:36:17 +0100 Subject: [PATCH 01/19] [New SDK]: 'wasp/server/api'. (#1679) --- waspc/data/Generator/templates/sdk/package.json | 6 +++++- .../sdk/server/{apis/types.ts => api/index.ts} | 4 ++-- waspc/examples/todo-typescript/src/api.ts | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 4 ++-- .../{ApiRoutesG.hs => ServerApiG.hs} | 16 ++++++++-------- waspc/waspc.cabal | 2 +- 6 files changed, 19 insertions(+), 15 deletions(-) rename waspc/data/Generator/templates/sdk/server/{apis/types.ts => api/index.ts} (89%) rename waspc/src/Wasp/Generator/SdkGenerator/{ApiRoutesG.hs => ServerApiG.hs} (84%) diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 363f3f11f4..d907cbab25 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -144,7 +144,11 @@ {=! Used by users, documented. =} "./webSocket": "./dist/webSocket/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} - "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx" + "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", + + {=! ================= NEW API HERE =================== =} + {=! Public: { type MyApiRoute1, type MyApiRoute2, ... } =} + "./server/api": "./dist/server/api/index.js" }, "license": "ISC", "include": [ diff --git a/waspc/data/Generator/templates/sdk/server/apis/types.ts b/waspc/data/Generator/templates/sdk/server/api/index.ts similarity index 89% rename from waspc/data/Generator/templates/sdk/server/apis/types.ts rename to waspc/data/Generator/templates/sdk/server/api/index.ts index ff3ac901e6..1d2fd78d57 100644 --- a/waspc/data/Generator/templates/sdk/server/apis/types.ts +++ b/waspc/data/Generator/templates/sdk/server/api/index.ts @@ -2,8 +2,6 @@ import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' -export type { ParamsDictionary as ExpressParams, Query as ExpressQuery } from 'express-serve-static-core' - import { {=# allEntities =} type {= internalTypeName =}, @@ -16,6 +14,8 @@ import { {=/ shouldImportAuthenticatedApi =} } from '../_types' +// PUBLIC API + {=# apiRoutes =} export type {= typeName =}< P extends ExpressParams = ExpressParams, diff --git a/waspc/examples/todo-typescript/src/api.ts b/waspc/examples/todo-typescript/src/api.ts index 5157cd7a59..169ff0ff02 100644 --- a/waspc/examples/todo-typescript/src/api.ts +++ b/waspc/examples/todo-typescript/src/api.ts @@ -1,4 +1,4 @@ -import type { FooBar } from "wasp/server/apis/types"; // This type is generated by Wasp based on the `api` declaration above. +import type { FooBar } from 'wasp/server/api'; // This type is generated by Wasp based on the `api` declaration above. export const fooBar: FooBar = (req, res, context) => { res.set("Access-Control-Allow-Origin", "*"); // Example of modifying headers to override Wasp default CORS middleware. diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index fe7ae1bbb4..1a975cc2d7 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -36,7 +36,6 @@ import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed) import Wasp.Generator.Job.Process (runNodeCommandAsJob) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N -import Wasp.Generator.SdkGenerator.ApiRoutesG (genApis) import Wasp.Generator.SdkGenerator.AuthG (genAuth) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) @@ -44,6 +43,7 @@ import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSe import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) +import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) import qualified Wasp.Generator.ServerGenerator.AuthG as ServerAuthG @@ -105,7 +105,7 @@ genSdkReal spec = <++> genEntitiesAndServerTypesDirs spec <++> genCrud spec <++> genJobTypes spec - <++> genApis spec + <++> genServerApi spec <++> genWebSockets spec <++> genRouter spec <++> genMiddleware spec diff --git a/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs b/waspc/src/Wasp/Generator/SdkGenerator/ServerApiG.hs similarity index 84% rename from waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/ServerApiG.hs index c5f3f048b4..def89d2252 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/ServerApiG.hs @@ -1,5 +1,5 @@ -module Wasp.Generator.SdkGenerator.ApiRoutesG - ( genApis, +module Wasp.Generator.SdkGenerator.ServerApiG + ( genServerApi, ) where @@ -19,19 +19,19 @@ import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.ServerGenerator.ApiRoutesG (getApiEntitiesObject, isAuthEnabledForApi) import Wasp.Util (toUpperFirst) -genApis :: AppSpec -> Generator [FileDraft] -genApis spec = +genServerApi :: AppSpec -> Generator [FileDraft] +genServerApi spec = if areThereAnyCustomApiRoutes then sequence - [ genApiTypes spec + [ genIndexTsWithApiRoutes spec ] else return [] where areThereAnyCustomApiRoutes = not . null $ getApis spec -genApiTypes :: AppSpec -> Generator FileDraft -genApiTypes spec = +genIndexTsWithApiRoutes :: AppSpec -> Generator FileDraft +genIndexTsWithApiRoutes spec = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) where namedApis = AS.getApis spec @@ -44,7 +44,7 @@ genApiTypes spec = "allEntities" .= nub (concatMap getApiEntitiesObject apis) ] usesAuth = fromMaybe (isAuthEnabledGlobally spec) . Api.auth - tmplFile = C.asTmplFile [relfile|server/apis/types.ts|] + tmplFile = C.asTmplFile [relfile|server/api/index.ts|] dstFile = SP.castRel tmplFile :: Path' (Rel C.SdkRootDir) File' getTmplData :: (String, Api.Api) -> Aeson.Value diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 6a8d58b139..d560ef184d 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -293,7 +293,7 @@ library Wasp.Generator.NpmInstall.Common Wasp.Generator.NpmInstall.InstalledNpmDepsLog Wasp.Generator.SdkGenerator - Wasp.Generator.SdkGenerator.ApiRoutesG + Wasp.Generator.SdkGenerator.ServerApiG Wasp.Generator.SdkGenerator.Auth.AuthFormsG Wasp.Generator.SdkGenerator.Auth.EmailAuthG Wasp.Generator.SdkGenerator.CrudG From 2258499ffc656b270fe225bb5f9625802a7bfe14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0o=C5=A1i=C4=87?= Date: Tue, 30 Jan 2024 12:45:54 +0100 Subject: [PATCH 02/19] [New SDK]: 'wasp/client/api'. (#1681) --- .../src/auth/pages/OAuthCodeExchange.jsx | 2 +- .../data/Generator/templates/sdk/api/index.ts | 10 +++++--- .../templates/sdk/auth/email/actions/login.ts | 2 +- .../sdk/auth/email/actions/passwordReset.ts | 2 +- .../sdk/auth/email/actions/signup.ts | 2 +- .../sdk/auth/email/actions/verifyEmail.ts | 2 +- .../templates/sdk/auth/helpers/user.ts | 2 +- .../Generator/templates/sdk/auth/login.ts | 2 +- .../Generator/templates/sdk/auth/logout.ts | 2 +- .../Generator/templates/sdk/auth/signup.ts | 2 +- .../Generator/templates/sdk/auth/useAuth.ts | 4 ++-- .../templates/sdk/operations/index.ts | 2 +- .../data/Generator/templates/sdk/package.json | 24 +++++++++++++++---- .../templates/sdk/server/api/index.ts | 5 ++-- .../sdk/webSocket/WebSocketProvider.tsx | 2 +- .../examples/todo-typescript/src/ChatPage.tsx | 2 +- 16 files changed, 42 insertions(+), 25 deletions(-) diff --git a/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx b/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx index 295cf5fa71..466fd90989 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx +++ b/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef } from 'react' import { useHistory } from 'react-router-dom' import config from 'wasp/core/config' -import api from 'wasp/api' +import { api } from 'wasp/client/api' import { initSession } from 'wasp/auth/helpers/user' // After a user authenticates via an Oauth 2.0 provider, this is the page that diff --git a/waspc/data/Generator/templates/sdk/api/index.ts b/waspc/data/Generator/templates/sdk/api/index.ts index 971f010160..d066bd5448 100644 --- a/waspc/data/Generator/templates/sdk/api/index.ts +++ b/waspc/data/Generator/templates/sdk/api/index.ts @@ -4,7 +4,8 @@ import config from 'wasp/core/config' import { storage } from 'wasp/core/storage' import { apiEventsEmitter } from './events.js' -const api = axios.create({ +// PUBLIC API +export const api = axios.create({ baseURL: config.apiUrl, }) @@ -12,22 +13,26 @@ const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' let waspAppAuthSessionId = storage.get(WASP_APP_AUTH_SESSION_ID_NAME) as string | undefined +// PRIVATE API (sdk) export function setSessionId(sessionId: string): void { waspAppAuthSessionId = sessionId storage.set(WASP_APP_AUTH_SESSION_ID_NAME, sessionId) apiEventsEmitter.emit('sessionId.set') } +// PRIVATE API (sdk) export function getSessionId(): string | undefined { return waspAppAuthSessionId } +// PRIVATE API (sdk) export function clearSessionId(): void { waspAppAuthSessionId = undefined storage.remove(WASP_APP_AUTH_SESSION_ID_NAME) apiEventsEmitter.emit('sessionId.clear') } +// PRIVATE API (sdk) export function removeLocalUserData(): void { waspAppAuthSessionId = undefined storage.clear() @@ -66,6 +71,7 @@ window.addEventListener('storage', (event) => { } }) +// PRIVATE API (sdk) /** * Takes an error returned by the app's API (as returned by axios), and transforms into a more * standard format to be further used by the client. It is also assumed that given API @@ -100,5 +106,3 @@ class WaspHttpError extends Error { this.data = data } } - -export default api diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts index 72194361aa..2f2cfb4777 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from 'wasp/api'; +import { api, handleApiError } from 'wasp/client/api'; import { initSession } from '../../helpers/user'; export async function login(data: { email: string; password: string }): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts index 34c773ce23..ac721f2102 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from 'wasp/api'; +import { api, handleApiError } from 'wasp/client/api'; export async function requestPasswordReset(data: { email: string; }): Promise<{ success: boolean }> { try { diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts index 60c0da2cff..cf882b2f71 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from 'wasp/api'; +import { api, handleApiError } from 'wasp/client/api'; export async function signup(data: { email: string; password: string }): Promise<{ success: boolean }> { try { diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts index 10fa4308c0..fa96569bb3 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from 'wasp/api' +import { api, handleApiError } from 'wasp/client/api' export async function verifyEmail(data: { token: string diff --git a/waspc/data/Generator/templates/sdk/auth/helpers/user.ts b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts index 498f2588a8..259a4c34b5 100644 --- a/waspc/data/Generator/templates/sdk/auth/helpers/user.ts +++ b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts @@ -1,4 +1,4 @@ -import { setSessionId } from 'wasp/api' +import { setSessionId } from 'wasp/client/api' import { invalidateAndRemoveQueries } from 'wasp/operations/resources' export async function initSession(sessionId: string): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/login.ts b/waspc/data/Generator/templates/sdk/auth/login.ts index b18a09ec16..5d084ad794 100644 --- a/waspc/data/Generator/templates/sdk/auth/login.ts +++ b/waspc/data/Generator/templates/sdk/auth/login.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from 'wasp/api' +import { api, handleApiError } from 'wasp/client/api' import { initSession } from './helpers/user' export default async function login(username: string, password: string): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/logout.ts b/waspc/data/Generator/templates/sdk/auth/logout.ts index cc41b6989c..7f40b0cbf6 100644 --- a/waspc/data/Generator/templates/sdk/auth/logout.ts +++ b/waspc/data/Generator/templates/sdk/auth/logout.ts @@ -1,4 +1,4 @@ -import api, { removeLocalUserData } from 'wasp/api' +import { api, removeLocalUserData } from 'wasp/client/api' import { invalidateAndRemoveQueries } from 'wasp/operations/resources' export default async function logout(): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/signup.ts b/waspc/data/Generator/templates/sdk/auth/signup.ts index 4fe93239cf..b0c0930f78 100644 --- a/waspc/data/Generator/templates/sdk/auth/signup.ts +++ b/waspc/data/Generator/templates/sdk/auth/signup.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from 'wasp/api' +import { api, handleApiError } from 'wasp/client/api' export default async function signup(userFields: { username: string; password: string }): Promise { try { diff --git a/waspc/data/Generator/templates/sdk/auth/useAuth.ts b/waspc/data/Generator/templates/sdk/auth/useAuth.ts index b2b7a5b06e..bf2d7e63e2 100644 --- a/waspc/data/Generator/templates/sdk/auth/useAuth.ts +++ b/waspc/data/Generator/templates/sdk/auth/useAuth.ts @@ -1,9 +1,9 @@ {{={= =}=}} import { deserialize as superjsonDeserialize } from 'superjson' import { useQuery } from 'wasp/rpc' -import api, { handleApiError } from 'wasp/api' +import { api, handleApiError } from 'wasp/client/api' import { HttpMethod } from 'wasp/types' -import type { User } from './types' +import type { User } from './types' import { addMetadataToQuery } from 'wasp/rpc/queries' export const getMe = createUserGetter() diff --git a/waspc/data/Generator/templates/sdk/operations/index.ts b/waspc/data/Generator/templates/sdk/operations/index.ts index 1da917cb2c..8ef076ee1f 100644 --- a/waspc/data/Generator/templates/sdk/operations/index.ts +++ b/waspc/data/Generator/templates/sdk/operations/index.ts @@ -1,4 +1,4 @@ -import api, { handleApiError } from 'wasp/api' +import { api, handleApiError } from 'wasp/client/api' import { HttpMethod } from 'wasp/types' import { serialize as superjsonSerialize, diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index d907cbab25..0e6077b4f2 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -83,8 +83,6 @@ "./auth/helpers/*": "./dist/auth/helpers/*.jsx", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", - {=! Used by users, documented. =} - "./api": "./dist/api/index.js", {=! Used by our framework code (Websockets), undocumented (but accessible) for users. =} "./api/events": "./dist/api/events.js", {=! Used by users, documented. =} @@ -148,12 +146,28 @@ {=! ================= NEW API HERE =================== =} {=! Public: { type MyApiRoute1, type MyApiRoute2, ... } =} - "./server/api": "./dist/server/api/index.js" + {=! Private: [] =} + "./server/api": "./dist/server/api/index.js", + {=! Public: { api } =} + {=! Private: [sdk] =} + "./client/api": "./dist/api/index.js" + }, + {=! + TypeScript doesn't care about the redirects we define above in "exports" field; those + are used only in runtime. TypeScript instead follows exact path as stated in an import when + trying to find the type declarations. Therefore, when "exports" redirect doesn't match the path + it redirects to, we need to also let TypeScript know about it, and that can be done with + `typesVersions` field below. + =} + "typesVersions": { + "*": { + "client/api": ["api/index.ts"] + } }, "license": "ISC", "include": [ "src/**/*" ], - {=& depsChunk =}, - {=& devDepsChunk =} + {=& depsChunk =}, + {=& devDepsChunk =} } diff --git a/waspc/data/Generator/templates/sdk/server/api/index.ts b/waspc/data/Generator/templates/sdk/server/api/index.ts index 1d2fd78d57..4f721fdcc2 100644 --- a/waspc/data/Generator/templates/sdk/server/api/index.ts +++ b/waspc/data/Generator/templates/sdk/server/api/index.ts @@ -14,8 +14,8 @@ import { {=/ shouldImportAuthenticatedApi =} } from '../_types' -// PUBLIC API +// PUBLIC API {=# apiRoutes =} export type {= typeName =}< P extends ExpressParams = ExpressParams, @@ -23,7 +23,7 @@ export type {= typeName =}< ReqBody = any, ReqQuery extends ExpressQuery = ExpressQuery, Locals extends Record = Record -> = +> = {=# usesAuth =} AuthenticatedApi< {=/ usesAuth =} @@ -41,5 +41,4 @@ export type {= typeName =}< ReqQuery, Locals > - {=/ apiRoutes =} diff --git a/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx b/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx index d650c1b80d..062aba19d8 100644 --- a/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx +++ b/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx @@ -2,7 +2,7 @@ import { createContext, useState, useEffect } from 'react' import { io, Socket } from 'socket.io-client' -import { getSessionId } from 'wasp/api' +import { getSessionId } from 'wasp/client/api' import { apiEventsEmitter } from 'wasp/api/events' import config from 'wasp/core/config' diff --git a/waspc/examples/todo-typescript/src/ChatPage.tsx b/waspc/examples/todo-typescript/src/ChatPage.tsx index c1d424e2c1..30f8c3191f 100644 --- a/waspc/examples/todo-typescript/src/ChatPage.tsx +++ b/waspc/examples/todo-typescript/src/ChatPage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react' -import api from 'wasp/api' +import { api } from 'wasp/client/api' import { useSocket, useSocketListener, From 3f980bdcfdd15e2ecd361ec766b9292cd85adada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0o=C5=A1i=C4=87?= Date: Tue, 30 Jan 2024 12:51:24 +0100 Subject: [PATCH 03/19] [New SDK]: 'wasp/server'. (#1682) --- .../data/Generator/templates/sdk/auth/jwt.ts | 2 +- .../Generator/templates/sdk/auth/lucia.ts | 2 +- .../Generator/templates/sdk/auth/session.ts | 2 +- .../Generator/templates/sdk/auth/utils.ts | 2 +- .../Generator/templates/sdk/dbSeed/types.ts | 2 +- .../Generator/templates/sdk/jobs/_jobTypes.ts | 2 +- .../data/Generator/templates/sdk/package.json | 14 ++++++------ .../templates/sdk/server/_types/index.ts | 2 +- .../templates/sdk/server/actions/index.ts | 2 +- .../templates/sdk/server/auth/email/utils.ts | 2 +- .../Generator/templates/sdk/server/config.ts | 1 + .../templates/sdk/server/dbClient.ts | 5 +++-- .../Generator/templates/sdk/server/index.ts | 7 ++++++ .../templates/sdk/server/queries/index.ts | 2 +- .../templates/sdk/server/types/index.ts | 5 ++--- .../templates/sdk/server/webSocket/index.ts | 2 +- .../templates/server/src/actions/_action.ts | 2 +- .../src/auth/providers/oauth/createRouter.ts | 3 +-- .../server/src/auth/providers/oauth/init.ts | 2 +- .../templates/server/src/auth/utils.ts | 2 +- .../templates/server/src/crud/_operations.ts | 2 +- .../Generator/templates/server/src/dbSeed.ts | 8 +++---- .../server/src/jobs/core/pgBoss/pgBoss.ts | 2 +- .../server/src/middleware/globalMiddleware.ts | 2 +- .../templates/server/src/queries/_query.ts | 2 +- .../templates/server/src/routes/apis/index.ts | 2 +- .../Generator/templates/server/src/server.ts | 5 +++-- .../server/src/webSocket/initialization.ts | 3 +-- .../todo-typescript/src/setup/serverSetup.ts | 8 +++---- .../src/user/customEmailSending.ts | 18 ++++++++------- waspc/src/Wasp/Generator/SdkGenerator.hs | 1 + web/docs/data-model/backends.md | 22 +++++++++---------- web/docs/data-model/entities.md | 8 +++---- web/docs/project/server-config.md | 3 ++- 34 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 waspc/data/Generator/templates/sdk/server/index.ts diff --git a/waspc/data/Generator/templates/sdk/auth/jwt.ts b/waspc/data/Generator/templates/sdk/auth/jwt.ts index b244990158..cb7f33725e 100644 --- a/waspc/data/Generator/templates/sdk/auth/jwt.ts +++ b/waspc/data/Generator/templates/sdk/auth/jwt.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken' import util from 'util' -import config from 'wasp/server/config' +import { config } from 'wasp/server' const jwtSign = util.promisify(jwt.sign) const jwtVerify = util.promisify(jwt.verify) diff --git a/waspc/data/Generator/templates/sdk/auth/lucia.ts b/waspc/data/Generator/templates/sdk/auth/lucia.ts index 9d53af0a1c..eabf63523e 100644 --- a/waspc/data/Generator/templates/sdk/auth/lucia.ts +++ b/waspc/data/Generator/templates/sdk/auth/lucia.ts @@ -1,7 +1,7 @@ {{={= =}=}} import { Lucia } from "lucia"; import { PrismaAdapter } from "@lucia-auth/adapter-prisma"; -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' import { type {= userEntityUpper =} } from "wasp/entities" const prismaAdapter = new PrismaAdapter( diff --git a/waspc/data/Generator/templates/sdk/auth/session.ts b/waspc/data/Generator/templates/sdk/auth/session.ts index b7ddebc3ea..487dca2046 100644 --- a/waspc/data/Generator/templates/sdk/auth/session.ts +++ b/waspc/data/Generator/templates/sdk/auth/session.ts @@ -11,7 +11,7 @@ import { deserializeAndSanitizeProviderData, } from "./utils.js"; -import prisma from 'wasp/server/dbClient'; +import { prisma } from 'wasp/server'; // Creates a new session for the `authId` in the database export async function createSession(authId: string): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/utils.ts b/waspc/data/Generator/templates/sdk/auth/utils.ts index 08b7798840..ca9f60628f 100644 --- a/waspc/data/Generator/templates/sdk/auth/utils.ts +++ b/waspc/data/Generator/templates/sdk/auth/utils.ts @@ -3,7 +3,7 @@ import { hashPassword } from './password.js' import { verify } from './jwt.js' import AuthError from 'wasp/core/AuthError' import HttpError from 'wasp/core/HttpError' -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' import { sleep } from 'wasp/server/utils' import { type {= userEntityUpper =}, diff --git a/waspc/data/Generator/templates/sdk/dbSeed/types.ts b/waspc/data/Generator/templates/sdk/dbSeed/types.ts index fe0d5396a4..7a72bca7ef 100644 --- a/waspc/data/Generator/templates/sdk/dbSeed/types.ts +++ b/waspc/data/Generator/templates/sdk/dbSeed/types.ts @@ -1,3 +1,3 @@ import type { PrismaClient } from '@prisma/client' -export type DbSeedFn = (prismaClient: PrismaClient) => Promise +export type DbSeedFn = (prisma: PrismaClient) => Promise diff --git a/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts b/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts index ce67cbfc9c..6c601a650e 100644 --- a/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts +++ b/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' import { type JobFn } from '{= jobExecutorTypesImportPath =}' diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 0e6077b4f2..8b56044e8a 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -100,12 +100,6 @@ "./universal/types": "./dist/universal/types.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./universal/validators": "./dist/universal/validators.js", - {=! Used by users and by our code, documented =} - "./server/dbClient": "./dist/server/dbClient.js", - {=! Used by users and by our code, documented. =} - "./server/config": "./dist/server/config.js", - {=! Used by users and by our code, documented. =} - "./server/types": "./dist/server/types/index.js", {=! Used by users and by our code, documented. =} "./server/middleware": "./dist/server/middleware/index.js", {=! Parts are used by users, documented. Parts are probably used by our code, undocumented (but accessible). =} @@ -144,7 +138,13 @@ {=! Used by our code, uncodumented (but accessible) for users. =} "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", + {=! Still needed, reconsider during refactoring =} + "./server/types": "./dist/server/types/index.js", + {=! ================= NEW API HERE =================== =} + {=! Public: { config, prisma, type ServerSetupFn } =} + {=! Private: [] =} + "./server": "./dist/server/index.js", {=! Public: { type MyApiRoute1, type MyApiRoute2, ... } =} {=! Private: [] =} "./server/api": "./dist/server/api/index.js", @@ -158,7 +158,7 @@ trying to find the type declarations. Therefore, when "exports" redirect doesn't match the path it redirects to, we need to also let TypeScript know about it, and that can be done with `typesVersions` field below. - =} + =} "typesVersions": { "*": { "client/api": ["api/index.ts"] diff --git a/waspc/data/Generator/templates/sdk/server/_types/index.ts b/waspc/data/Generator/templates/sdk/server/_types/index.ts index 24d5011c91..c00ecce15e 100644 --- a/waspc/data/Generator/templates/sdk/server/_types/index.ts +++ b/waspc/data/Generator/templates/sdk/server/_types/index.ts @@ -2,7 +2,7 @@ import { type Expand } from 'wasp/universal/types'; import { type Request, type Response } from 'express' import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' -import prisma from "wasp/server/dbClient" +import { prisma } from 'wasp/server' {=# isAuthEnabled =} import { type {= userEntityName =}, diff --git a/waspc/data/Generator/templates/sdk/server/actions/index.ts b/waspc/data/Generator/templates/sdk/server/actions/index.ts index c1832fd6d6..a00094e8fe 100644 --- a/waspc/data/Generator/templates/sdk/server/actions/index.ts +++ b/waspc/data/Generator/templates/sdk/server/actions/index.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' {=! 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. =} diff --git a/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts index 168ff61262..005adfdd67 100644 --- a/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts +++ b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts @@ -9,7 +9,7 @@ import { deserializeAndSanitizeProviderData, type EmailProviderData, } from 'wasp/auth/utils'; -import waspServerConfig from 'wasp/server/config'; +import { config as waspServerConfig } from 'wasp/server'; import { type {= userEntityUpper =}, type {= authEntityUpper =} } from 'wasp/entities' export async function createEmailVerificationLink( diff --git a/waspc/data/Generator/templates/sdk/server/config.ts b/waspc/data/Generator/templates/sdk/server/config.ts index 0ee4ca96f3..fcdc0b6667 100644 --- a/waspc/data/Generator/templates/sdk/server/config.ts +++ b/waspc/data/Generator/templates/sdk/server/config.ts @@ -54,6 +54,7 @@ const config: { } const resolvedConfig: Config = merge(config.all, config[env]) +// PUBLIC API export default resolvedConfig function getDevelopmentConfig(): EnvConfig { diff --git a/waspc/data/Generator/templates/sdk/server/dbClient.ts b/waspc/data/Generator/templates/sdk/server/dbClient.ts index 66e7801be3..cbd69d2f83 100644 --- a/waspc/data/Generator/templates/sdk/server/dbClient.ts +++ b/waspc/data/Generator/templates/sdk/server/dbClient.ts @@ -2,11 +2,12 @@ import Prisma from '@prisma/client' const createDbClient = () => { - const prismaClient = new Prisma.PrismaClient() + const prisma = new Prisma.PrismaClient() - return prismaClient + return prisma } const dbClient = createDbClient() +// PUBLIC API export default dbClient diff --git a/waspc/data/Generator/templates/sdk/server/index.ts b/waspc/data/Generator/templates/sdk/server/index.ts new file mode 100644 index 0000000000..8d527a331b --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/index.ts @@ -0,0 +1,7 @@ +// PUBLIC API +export { default as config } from './config.js' +// PUBLIC API +export { default as prisma } from './dbClient.js' +// PUBLIC API +export { type ServerSetupFn } from './types/index.js' + diff --git a/waspc/data/Generator/templates/sdk/server/queries/index.ts b/waspc/data/Generator/templates/sdk/server/queries/index.ts index 92cb7b7f4f..1c8cf33b05 100644 --- a/waspc/data/Generator/templates/sdk/server/queries/index.ts +++ b/waspc/data/Generator/templates/sdk/server/queries/index.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' {=! 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. =} diff --git a/waspc/data/Generator/templates/sdk/server/types/index.ts b/waspc/data/Generator/templates/sdk/server/types/index.ts index fda96a4528..1bbffc7555 100644 --- a/waspc/data/Generator/templates/sdk/server/types/index.ts +++ b/waspc/data/Generator/templates/sdk/server/types/index.ts @@ -1,12 +1,11 @@ import { type Application } from 'express' import { Server } from 'http' +// PUBLIC API export type ServerSetupFn = (context: ServerSetupFnContext) => Promise +// PRIVATE API (server) export type ServerSetupFnContext = { app: Application, server: Server, } - -export type { Application } from 'express' -export type { Server } from 'http' diff --git a/waspc/data/Generator/templates/sdk/server/webSocket/index.ts b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts index 1074eda872..f40a15d019 100644 --- a/waspc/data/Generator/templates/sdk/server/webSocket/index.ts +++ b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts @@ -3,7 +3,7 @@ import { Server } from 'socket.io' import { EventsMap, DefaultEventsMap } from '@socket.io/component-emitter' -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' {=# isAuthEnabled =} import { type SanitizedUser } from 'wasp/server/_types/index.js' {=/ isAuthEnabled =} diff --git a/waspc/data/Generator/templates/server/src/actions/_action.ts b/waspc/data/Generator/templates/server/src/actions/_action.ts index 11b092cb1a..54435171e0 100644 --- a/waspc/data/Generator/templates/server/src/actions/_action.ts +++ b/waspc/data/Generator/templates/server/src/actions/_action.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' {=& jsFn.importStatement =} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts index a8032b717d..38a783304a 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts @@ -3,8 +3,7 @@ import { Router } from "express" import passport from "passport" -import prisma from 'wasp/server/dbClient' -import waspServerConfig from 'wasp/server/config' +import { prisma, config as waspServerConfig } from 'wasp/server' import { type ProviderName, type ProviderId, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts index a104eac299..15cb5631fc 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts @@ -1,6 +1,6 @@ import passport from "passport"; -import waspServerConfig from 'wasp/server/config'; +import { config as waspServerConfig } from 'wasp/server'; import type { InitData, ProviderConfig, RequestWithWasp, UserSignupFields } from "wasp/auth/providers/types"; import type { OAuthConfig, UserDefinedConfigFn } from "./types.js"; diff --git a/waspc/data/Generator/templates/server/src/auth/utils.ts b/waspc/data/Generator/templates/server/src/auth/utils.ts index d3ede94ce6..887997b5cb 100644 --- a/waspc/data/Generator/templates/server/src/auth/utils.ts +++ b/waspc/data/Generator/templates/server/src/auth/utils.ts @@ -3,7 +3,7 @@ import { hashPassword } from 'wasp/auth/password' import { verify } from 'wasp/auth/jwt' import AuthError from 'wasp/core/AuthError' import HttpError from 'wasp/core/HttpError' -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' import { sleep } from 'wasp/server/utils' import { type {= userEntityUpper =}, diff --git a/waspc/data/Generator/templates/server/src/crud/_operations.ts b/waspc/data/Generator/templates/server/src/crud/_operations.ts index 31237b2dc9..14f9ddda2e 100644 --- a/waspc/data/Generator/templates/server/src/crud/_operations.ts +++ b/waspc/data/Generator/templates/server/src/crud/_operations.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from "wasp/server/dbClient"; +import { prisma } from 'wasp/server'; import type { Prisma } from "@prisma/client"; import type { diff --git a/waspc/data/Generator/templates/server/src/dbSeed.ts b/waspc/data/Generator/templates/server/src/dbSeed.ts index 204480f456..51378ed8f9 100644 --- a/waspc/data/Generator/templates/server/src/dbSeed.ts +++ b/waspc/data/Generator/templates/server/src/dbSeed.ts @@ -6,7 +6,7 @@ // TODO: Consider in the future moving it into a a separate project (maybe db/ ?), while still // maintaining access to logic from the server/ . -import prismaClient from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' import type { DbSeedFn } from 'wasp/dbSeed/types' {=# dbSeeds =} @@ -26,13 +26,13 @@ async function main() { } else { console.error('Name of the seed to run not specified!') } - await (seeds[nameOfSeedToRun] satisfies DbSeedFn)(prismaClient) + await (seeds[nameOfSeedToRun] satisfies DbSeedFn)(prisma) } main() - .then(async () => { await prismaClient.$disconnect() }) + .then(async () => { await prisma.$disconnect() }) .catch(async (e) => { console.error(e) - await prismaClient.$disconnect() + await prisma.$disconnect() process.exit(1) }) diff --git a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts index b0c1622f66..85c6c90e75 100644 --- a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts +++ b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts @@ -1,5 +1,5 @@ import PgBoss from 'pg-boss' -import config from 'wasp/server/config' +import { config } from 'wasp/server' const boss = createPgBoss() diff --git a/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts b/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts index 7f576b1353..49690a9fbe 100644 --- a/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts +++ b/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts @@ -5,7 +5,7 @@ import logger from 'morgan' import cors from 'cors' import helmet from 'helmet' -import config from 'wasp/server/config' +import { config } from 'wasp/server' import type { MiddlewareConfig, MiddlewareConfigFn } from 'wasp/server/middleware' export type { MiddlewareConfig, MiddlewareConfigFn } from 'wasp/server/middleware' diff --git a/waspc/data/Generator/templates/server/src/queries/_query.ts b/waspc/data/Generator/templates/server/src/queries/_query.ts index 24118fd432..0b60b99a42 100644 --- a/waspc/data/Generator/templates/server/src/queries/_query.ts +++ b/waspc/data/Generator/templates/server/src/queries/_query.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' {=& jsFn.importStatement =} diff --git a/waspc/data/Generator/templates/server/src/routes/apis/index.ts b/waspc/data/Generator/templates/server/src/routes/apis/index.ts index 3e87cd3643..6a1b1dcd00 100644 --- a/waspc/data/Generator/templates/server/src/routes/apis/index.ts +++ b/waspc/data/Generator/templates/server/src/routes/apis/index.ts @@ -1,6 +1,6 @@ {{={= =}=}} import express from 'express' -import prisma from 'wasp/server/dbClient' +import { prisma } from 'wasp/server' import { handleRejection } from 'wasp/server/utils' import { MiddlewareConfigFn, globalMiddlewareConfigForExpress } from '../../middleware/index.js' {=# isAuthEnabled =} diff --git a/waspc/data/Generator/templates/server/src/server.ts b/waspc/data/Generator/templates/server/src/server.ts index e4b16898b6..382be0c310 100644 --- a/waspc/data/Generator/templates/server/src/server.ts +++ b/waspc/data/Generator/templates/server/src/server.ts @@ -2,11 +2,12 @@ import http from 'http' import app from './app.js' -import config from 'wasp/server/config' +import { config } from 'wasp/server' {=# setupFn.isDefined =} {=& setupFn.importStatement =} -import { ServerSetupFn, ServerSetupFnContext } from 'wasp/server/types' +import { ServerSetupFn } from 'wasp/server' +import { ServerSetupFnContext } from 'wasp/server/types' {=/ setupFn.isDefined =} {=# isPgBossJobExecutorUsed =} diff --git a/waspc/data/Generator/templates/server/src/webSocket/initialization.ts b/waspc/data/Generator/templates/server/src/webSocket/initialization.ts index 28467910c1..8e87baa8e6 100644 --- a/waspc/data/Generator/templates/server/src/webSocket/initialization.ts +++ b/waspc/data/Generator/templates/server/src/webSocket/initialization.ts @@ -4,8 +4,7 @@ import http from 'http' import { Server, Socket } from 'socket.io' import type { ServerType } from 'wasp/server/webSocket' -import config from 'wasp/server/config' -import prisma from 'wasp/server/dbClient' +import { config, prisma } from 'wasp/server' {=# isAuthEnabled =} import { getSessionAndUserFromSessionId } from 'wasp/auth/session' diff --git a/waspc/examples/todo-typescript/src/setup/serverSetup.ts b/waspc/examples/todo-typescript/src/setup/serverSetup.ts index ddfc8ab036..1b49dcf081 100644 --- a/waspc/examples/todo-typescript/src/setup/serverSetup.ts +++ b/waspc/examples/todo-typescript/src/setup/serverSetup.ts @@ -1,9 +1,7 @@ -import express from 'express' +import express, { Application } from 'express' import cors from 'cors' import type { MiddlewareConfigFn } from 'wasp/server/middleware' -import config from 'wasp/server/config' -import type { Application, ServerSetupFn } from 'wasp/server/types' -import prismaClient from 'wasp/server/dbClient' +import { config, ServerSetupFn, prisma } from 'wasp/server' export const serverMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => { // Example of adding an extra domains to CORS. @@ -29,5 +27,5 @@ export const serverSetup: ServerSetupFn = async ({ app }: { app: Application}) = res.send('I am a custom route') }) console.log("I am a server setup function!"); - console.log("Executed raw prisma client call: ", await prismaClient.$executeRaw``); + console.log("Executed raw prisma client call: ", await prisma.$executeRaw``); } diff --git a/waspc/examples/todo-typescript/src/user/customEmailSending.ts b/waspc/examples/todo-typescript/src/user/customEmailSending.ts index ab6a729409..222031c34c 100644 --- a/waspc/examples/todo-typescript/src/user/customEmailSending.ts +++ b/waspc/examples/todo-typescript/src/user/customEmailSending.ts @@ -6,34 +6,36 @@ import { } from 'wasp/server/auth/email/utils' export async function send() { + const userEmail = 'mihovil@ilakovac.com' + const link = await createPasswordResetLink( - 'mihovil@ilakovac.com', + userEmail, '/password-reset' ) const secondLink = await createEmailVerificationLink( - 'mihovil@ilakovac.com', + userEmail, '/email-verify' ) // Send email verification email. - await sendEmailVerificationEmail('mihovil@ilakovac.com', { + await sendEmailVerificationEmail(userEmail, { from: { name: 'Wasp', - email: 'mihovil@ilakovac.com', + email: userEmail, }, - to: 'mihovil@ilakovac.com', + to: userEmail, subject: 'Email verification', text: 'Click on the link to verify your email. ' + secondLink, html: `Click here to verify your email.`, }) // Send password reset email. - await sendPasswordResetEmail('mihovil@ilakovac.com', { + await sendPasswordResetEmail(userEmail, { from: { name: 'Wasp', - email: 'mihovil@ilakovac.com', + email: userEmail, }, - to: 'mihovil@ilakovac.com', + to: userEmail, subject: 'Password reset', text: 'Click on the link to reset your password.' + link, html: `Click here to reset your password.`, diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 1a975cc2d7..363a1ad111 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -86,6 +86,7 @@ genSdkReal spec = genFileCopy [relfile|operations/index.ts|], -- Not migrated to TS yet genFileCopy [relfile|operations/updateHandlersMap.js|], + genFileCopy [relfile|server/index.ts|], genFileCopy [relfile|server/dbClient.ts|], genFileCopy [relfile|types/index.ts|], genFileCopy [relfile|dbSeed/types.ts|], diff --git a/web/docs/data-model/backends.md b/web/docs/data-model/backends.md index 672f2998cc..f450fa796a 100644 --- a/web/docs/data-model/backends.md +++ b/web/docs/data-model/backends.md @@ -144,7 +144,7 @@ app MyApp { -Each seed function must be an async function that takes one argument, `prismaClient`, which is a [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client/crud) instance used to interact with the database. +Each seed function must be an async function that takes one argument, `prisma`, which is a [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client/crud) instance used to interact with the database. This is the same Prisma Client instance that Wasp uses internally and thus includes all of the usual features (e.g., password hashing). Since a seed function falls under server-side code, it can import other server-side functions. This is convenient because you might want to seed the database using Actions. @@ -157,20 +157,20 @@ Here's an example of a seed function that imports an Action: ```js import { createTask } from "./actions.js"; -export const devSeedSimple = async (prismaClient) => { - const user = await createUser(prismaClient, { +export const devSeedSimple = async (prisma) => { + const user = await createUser(prisma, { username: "RiuTheDog", password: "bark1234", }); await createTask( { description: "Chase the cat" }, - { user, entities: { Task: prismaClient.task } } + { user, entities: { Task: prisma.task } } ); }; -async function createUser(prismaClient, data) { - const { password, ...newUser } = await prismaClient.user.create({ data }); +async function createUser(prisma, data) { + const { password, ...newUser } = await prisma.user.create({ data }); return newUser; } ``` @@ -185,23 +185,23 @@ import { PrismaClient } from "@prisma/client"; type SanitizedUser = Omit; -export const devSeedSimple = async (prismaClient: PrismaClient) => { - const user = await createUser(prismaClient, { +export const devSeedSimple = async (prisma: PrismaClient) => { + const user = await createUser(prisma, { username: "RiuTheDog", password: "bark1234", }); await createTask( { description: "Chase the cat", isDone: false }, - { user, entities: { Task: prismaClient.task } } + { user, entities: { Task: prisma.task } } ); }; async function createUser( - prismaClient: PrismaClient, + prisma: PrismaClient, data: Pick ): Promise { - const { password, ...newUser } = await prismaClient.user.create({ data }); + const { password, ...newUser } = await prisma.user.create({ data }); return newUser; } ``` diff --git a/web/docs/data-model/entities.md b/web/docs/data-model/entities.md index 23843da178..3af8063294 100644 --- a/web/docs/data-model/entities.md +++ b/web/docs/data-model/entities.md @@ -77,9 +77,9 @@ You can only use the Prisma Client in your Wasp server code. You can import it l ```js -import prismaClient from '@wasp/dbClient'` +import prisma from '@wasp/dbClient'` -prismaClient.task.create({ +prisma.task.create({ description: "Read the Entities doc", isDone: true // almost :) }) @@ -89,9 +89,9 @@ prismaClient.task.create({ ```ts -import prismaClient from '@wasp/dbClient'` +import prisma from '@wasp/dbClient'` -prismaClient.task.create({ +prisma.task.create({ description: "Read the Entities doc", isDone: true // almost :) }) diff --git a/web/docs/project/server-config.md b/web/docs/project/server-config.md index 10800d1f59..b690db4b34 100644 --- a/web/docs/project/server-config.md +++ b/web/docs/project/server-config.md @@ -72,7 +72,8 @@ function addCustomRoute(app) { ```ts title="src/server/myServerSetupCode.ts" -import { ServerSetupFn, Application } from '@wasp/types' +import { ServerSetupFn } from 'wasp/server' +import { Application } from 'express' export const mySetupFunction: ServerSetupFn = async ({ app }) => { addCustomRoute(app) From c2abee30fbfdba5760ef79a965b18d86aa3c3efa Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Tue, 30 Jan 2024 13:53:03 +0100 Subject: [PATCH 04/19] Implement new wasp/auth API (#1691) --- .../Generator/templates/sdk/auth/index.ts | 6 + .../Generator/templates/sdk/auth/session.ts | 8 +- .../Generator/templates/sdk/auth/types.ts | 2 +- .../Generator/templates/sdk/auth/useAuth.ts | 4 +- .../data/Generator/templates/sdk/auth/user.ts | 10 +- .../data/Generator/templates/sdk/package.json | 9 +- .../templates/sdk/server/_types/index.ts | 4 +- .../Generator/templates/sdk/server/utils.ts | 4 +- .../templates/sdk/server/webSocket/index.ts | 4 +- .../templates/server/src/routes/apis/index.ts | 4 +- .../Generator/templates/server/src/utils.ts | 4 +- .../.wasp/out/sdk/wasp/api/index.ts | 108 +++++++ .../.wasp/out/sdk/wasp/auth/helpers/user.ts | 14 + .../.wasp/out/sdk/wasp/auth/logout.ts | 17 + .../.wasp/out/sdk/wasp/auth/types.ts | 2 + .../.wasp/out/sdk/wasp/auth/useAuth.ts | 38 +++ .../.wasp/out/sdk/wasp/auth/user.ts | 23 ++ .../.wasp/out/sdk/wasp/auth/utils.ts | 302 ++++++++++++++++++ .../.wasp/out/sdk/wasp/operations/index.ts | 22 ++ .../.wasp/out/sdk/wasp/package.json | 116 +++++++ .../.wasp/out/sdk/wasp/server/_types/index.ts | 99 ++++++ .../out/sdk/wasp/server/actions/index.ts | 50 +++ .../out/sdk/wasp/server/queries/index.ts | 14 + .../.wasp/out/sdk/wasp/server/utils.ts | 67 ++++ .../examples/todo-typescript/src/MainPage.tsx | 5 +- .../todo-typescript/src/Todo.test.tsx | 4 +- .../todo-typescript/src/websocket/index.ts | 26 +- 27 files changed, 919 insertions(+), 47 deletions(-) create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts diff --git a/waspc/data/Generator/templates/sdk/auth/index.ts b/waspc/data/Generator/templates/sdk/auth/index.ts index 354fbe542a..56d03e4d89 100644 --- a/waspc/data/Generator/templates/sdk/auth/index.ts +++ b/waspc/data/Generator/templates/sdk/auth/index.ts @@ -1 +1,7 @@ export { defineUserSignupFields } from './providers/types.js'; + +// PUBLIC +export type { AuthUser } from '../server/_types' + +// PUBLIC +export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js' diff --git a/waspc/data/Generator/templates/sdk/auth/session.ts b/waspc/data/Generator/templates/sdk/auth/session.ts index 487dca2046..a077b41a05 100644 --- a/waspc/data/Generator/templates/sdk/auth/session.ts +++ b/waspc/data/Generator/templates/sdk/auth/session.ts @@ -2,7 +2,7 @@ import { Request as ExpressRequest } from "express"; import { type {= userEntityUpper =} } from "wasp/entities" -import { type SanitizedUser } from 'wasp/server/_types' +import { type AuthUser } from 'wasp/auth' import { auth } from "./lucia.js"; import type { Session } from "lucia"; @@ -19,7 +19,7 @@ export async function createSession(authId: string): Promise { } export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<{ - user: SanitizedUser | null, + user: AuthUser | null, session: Session | null, }> { const authorizationHeader = req.headers["authorization"]; @@ -43,7 +43,7 @@ export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Pro } export async function getSessionAndUserFromSessionId(sessionId: string): Promise<{ - user: SanitizedUser | null, + user: AuthUser | null, session: Session | null, }> { const { session, user: authEntity } = await auth.validateSession(sessionId); @@ -61,7 +61,7 @@ export async function getSessionAndUserFromSessionId(sessionId: string): Promise } } -async function getUser(userId: {= userEntityUpper =}['id']): Promise { +async function getUser(userId: {= userEntityUpper =}['id']): Promise { const user = await prisma.{= userEntityLower =} .findUnique({ where: { id: userId }, diff --git a/waspc/data/Generator/templates/sdk/auth/types.ts b/waspc/data/Generator/templates/sdk/auth/types.ts index f9f079a57a..03d33b5016 100644 --- a/waspc/data/Generator/templates/sdk/auth/types.ts +++ b/waspc/data/Generator/templates/sdk/auth/types.ts @@ -1,2 +1,2 @@ // todo(filip): turn into a proper import/path -export type { SanitizedUser as User, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types/' +export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types' diff --git a/waspc/data/Generator/templates/sdk/auth/useAuth.ts b/waspc/data/Generator/templates/sdk/auth/useAuth.ts index bf2d7e63e2..c7b02273cf 100644 --- a/waspc/data/Generator/templates/sdk/auth/useAuth.ts +++ b/waspc/data/Generator/templates/sdk/auth/useAuth.ts @@ -3,7 +3,7 @@ import { deserialize as superjsonDeserialize } from 'superjson' import { useQuery } from 'wasp/rpc' import { api, handleApiError } from 'wasp/client/api' import { HttpMethod } from 'wasp/types' -import type { User } from './types' +import type { AuthUser } from './types' import { addMetadataToQuery } from 'wasp/rpc/queries' export const getMe = createUserGetter() @@ -15,7 +15,7 @@ export default function useAuth(queryFnArgs?: unknown, config?: any) { function createUserGetter() { const getMeRelativePath = 'auth/me' const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` } - async function getMe(): Promise { + async function getMe(): Promise { try { const response = await api.get(getMeRoute.path) diff --git a/waspc/data/Generator/templates/sdk/auth/user.ts b/waspc/data/Generator/templates/sdk/auth/user.ts index a7ac86827e..0de50de6d0 100644 --- a/waspc/data/Generator/templates/sdk/auth/user.ts +++ b/waspc/data/Generator/templates/sdk/auth/user.ts @@ -1,14 +1,14 @@ -import type { User, ProviderName, DeserializedAuthIdentity } from './types' +import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types' -export function getEmail(user: User): string | null { +export function getEmail(user: AuthUser): string | null { return findUserIdentity(user, "email")?.providerUserId ?? null; } -export function getUsername(user: User): string | null { +export function getUsername(user: AuthUser): string | null { return findUserIdentity(user, "username")?.providerUserId ?? null; } -export function getFirstProviderUserId(user?: User): string | null { +export function getFirstProviderUserId(user?: AuthUser): string | null { if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { return null; } @@ -16,7 +16,7 @@ export function getFirstProviderUserId(user?: User): string | null { return user.auth.identities[0].providerUserId ?? null; } -export function findUserIdentity(user: User, providerName: ProviderName): DeserializedAuthIdentity | undefined { +export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined { return user.auth.identities.find( (identity) => identity.providerName === providerName ); diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 8b56044e8a..847d6a1b8b 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -37,10 +37,6 @@ "./rpc/queryClient": "./dist/rpc/queryClient.js", {=! Used by users, documented. =} "./types": "./dist/types/index.js", - {=! Used by user, documented. =} - "./auth": "./dist/auth/index.js", - {=! Used by users, documented. =} - "./auth/types": "./dist/auth/types.js", {=! Used by users, documented. =} "./auth/login": "./dist/auth/login.js", {=! Used by users, documented. =} @@ -50,8 +46,6 @@ {=! Used by users, documented. =} "./auth/useAuth": "./dist/auth/useAuth.js", {=! Used by users, documented. =} - "./auth/user": "./dist/auth/user.js", - {=! Used by users, documented. =} "./auth/email": "./dist/auth/email/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/helpers/user": "./dist/auth/helpers/user.js", @@ -150,7 +144,8 @@ "./server/api": "./dist/server/api/index.js", {=! Public: { api } =} {=! Private: [sdk] =} - "./client/api": "./dist/api/index.js" + "./client/api": "./dist/api/index.js", + "./auth": "./dist/auth/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/data/Generator/templates/sdk/server/_types/index.ts b/waspc/data/Generator/templates/sdk/server/_types/index.ts index c00ecce15e..a1b438e2af 100644 --- a/waspc/data/Generator/templates/sdk/server/_types/index.ts +++ b/waspc/data/Generator/templates/sdk/server/_types/index.ts @@ -86,7 +86,7 @@ type Context = Expand<{ }> {=# isAuthEnabled =} -type ContextWithUser = Expand & { user?: SanitizedUser }> +type ContextWithUser = Expand & { user?: AuthUser }> // TODO: This type must match the logic in auth/session.js (if we remove the // password field from the object there, we must do the same here). Ideally, @@ -97,7 +97,7 @@ export type DeserializedAuthIdentity = Expand | Omit | OAuthProviderData }> -export type SanitizedUser = {= userEntityName =} & { +export type AuthUser = {= userEntityName =} & { {= authFieldOnUserEntityName =}: {= authEntityName =} & { {= identitiesFieldOnAuthEntityName =}: DeserializedAuthIdentity[] } | null diff --git a/waspc/data/Generator/templates/sdk/server/utils.ts b/waspc/data/Generator/templates/sdk/server/utils.ts index 6ca262decd..9bae69db17 100644 --- a/waspc/data/Generator/templates/sdk/server/utils.ts +++ b/waspc/data/Generator/templates/sdk/server/utils.ts @@ -7,12 +7,12 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' {=# isAuthEnabled =} -import { type SanitizedUser } from 'wasp/server/_types/index.js' +import { type AuthUser } from 'wasp/auth' {=/ isAuthEnabled =} type RequestWithExtraFields = Request & { {=# isAuthEnabled =} - user?: SanitizedUser; + user?: AuthUser; sessionId?: string; {=/ isAuthEnabled =} } diff --git a/waspc/data/Generator/templates/sdk/server/webSocket/index.ts b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts index f40a15d019..82a044185b 100644 --- a/waspc/data/Generator/templates/sdk/server/webSocket/index.ts +++ b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts @@ -5,7 +5,7 @@ import { EventsMap, DefaultEventsMap } from '@socket.io/component-emitter' import { prisma } from 'wasp/server' {=# isAuthEnabled =} -import { type SanitizedUser } from 'wasp/server/_types/index.js' +import { type AuthUser } from 'wasp/auth' {=/ isAuthEnabled =} {=& userWebSocketFn.importStatement =} @@ -33,7 +33,7 @@ export type WebSocketDefinition< export interface WaspSocketData { {=# isAuthEnabled =} - user?: SanitizedUser + user?: AuthUser {=/ isAuthEnabled =} } diff --git a/waspc/data/Generator/templates/server/src/routes/apis/index.ts b/waspc/data/Generator/templates/server/src/routes/apis/index.ts index 6a1b1dcd00..1e6939c621 100644 --- a/waspc/data/Generator/templates/server/src/routes/apis/index.ts +++ b/waspc/data/Generator/templates/server/src/routes/apis/index.ts @@ -5,7 +5,7 @@ import { handleRejection } from 'wasp/server/utils' import { MiddlewareConfigFn, globalMiddlewareConfigForExpress } from '../../middleware/index.js' {=# isAuthEnabled =} import auth from 'wasp/core/auth' -import { type SanitizedUser } from 'wasp/server/_types' +import { type AuthUser } from 'wasp/auth' {=/ isAuthEnabled =} {=# apiNamespaces =} @@ -45,7 +45,7 @@ router.{= routeMethod =}( {=/ usesAuth =} handleRejection( ( - req: Parameters[0]{=# usesAuth =} & { user: SanitizedUser }{=/ usesAuth =}, + req: Parameters[0]{=# usesAuth =} & { user: AuthUser }{=/ usesAuth =}, res: Parameters[1], ) => { const context = { diff --git a/waspc/data/Generator/templates/server/src/utils.ts b/waspc/data/Generator/templates/server/src/utils.ts index 6ca262decd..9bae69db17 100644 --- a/waspc/data/Generator/templates/server/src/utils.ts +++ b/waspc/data/Generator/templates/server/src/utils.ts @@ -7,12 +7,12 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' {=# isAuthEnabled =} -import { type SanitizedUser } from 'wasp/server/_types/index.js' +import { type AuthUser } from 'wasp/auth' {=/ isAuthEnabled =} type RequestWithExtraFields = Request & { {=# isAuthEnabled =} - user?: SanitizedUser; + user?: AuthUser; sessionId?: string; {=/ isAuthEnabled =} } diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts new file mode 100644 index 0000000000..d066bd5448 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts @@ -0,0 +1,108 @@ +import axios, { type AxiosError } from 'axios' + +import config from 'wasp/core/config' +import { storage } from 'wasp/core/storage' +import { apiEventsEmitter } from './events.js' + +// PUBLIC API +export const api = axios.create({ + baseURL: config.apiUrl, +}) + +const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' + +let waspAppAuthSessionId = storage.get(WASP_APP_AUTH_SESSION_ID_NAME) as string | undefined + +// PRIVATE API (sdk) +export function setSessionId(sessionId: string): void { + waspAppAuthSessionId = sessionId + storage.set(WASP_APP_AUTH_SESSION_ID_NAME, sessionId) + apiEventsEmitter.emit('sessionId.set') +} + +// PRIVATE API (sdk) +export function getSessionId(): string | undefined { + return waspAppAuthSessionId +} + +// PRIVATE API (sdk) +export function clearSessionId(): void { + waspAppAuthSessionId = undefined + storage.remove(WASP_APP_AUTH_SESSION_ID_NAME) + apiEventsEmitter.emit('sessionId.clear') +} + +// PRIVATE API (sdk) +export function removeLocalUserData(): void { + waspAppAuthSessionId = undefined + storage.clear() + apiEventsEmitter.emit('sessionId.clear') +} + +api.interceptors.request.use((request) => { + const sessionId = getSessionId() + if (sessionId) { + request.headers['Authorization'] = `Bearer ${sessionId}` + } + return request +}) + +api.interceptors.response.use(undefined, (error) => { + if (error.response?.status === 401) { + clearSessionId() + } + return Promise.reject(error) +}) + +// This handler will run on other tabs (not the active one calling API functions), +// and will ensure they know about auth session ID changes. +// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event +// "Note: This won't work on the same page that is making the changes — it is really a way +// for other pages on the domain using the storage to sync any changes that are made." +window.addEventListener('storage', (event) => { + if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) { + if (!!event.newValue) { + waspAppAuthSessionId = event.newValue + apiEventsEmitter.emit('sessionId.set') + } else { + waspAppAuthSessionId = undefined + apiEventsEmitter.emit('sessionId.clear') + } + } +}) + +// PRIVATE API (sdk) +/** + * Takes an error returned by the app's API (as returned by axios), and transforms into a more + * standard format to be further used by the client. It is also assumed that given API + * error has been formatted as implemented by HttpError on the server. + */ +export function handleApiError(error: AxiosError<{ message?: string, data?: unknown }>): void { + if (error?.response) { + // If error came from HTTP response, we capture most informative message + // and also add .statusCode information to it. + // If error had JSON response, we assume it is of format { message, data } and + // add that info to the error. + // TODO: We might want to use HttpError here instead of just Error, since + // HttpError is also used on server to throw errors like these. + // That would require copying HttpError code to web-app also and using it here. + const responseJson = error.response?.data + const responseStatusCode = error.response.status + throw new WaspHttpError(responseStatusCode, responseJson?.message ?? error.message, responseJson) + } else { + // If any other error, we just propagate it. + throw error + } +} + +class WaspHttpError extends Error { + statusCode: number + + data: unknown + + constructor (statusCode: number, message: string, data: unknown) { + super(message) + this.statusCode = statusCode + this.data = data + } +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts new file mode 100644 index 0000000000..259a4c34b5 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts @@ -0,0 +1,14 @@ +import { setSessionId } from 'wasp/client/api' +import { invalidateAndRemoveQueries } from 'wasp/operations/resources' + +export async function initSession(sessionId: string): Promise { + setSessionId(sessionId) + // We need to invalidate queries after login in order to get the correct user + // data in the React components (using `useAuth`). + // Redirects after login won't work properly without this. + + // TODO(filip): We are currently removing all the queries, but we should + // remove only non-public, user-dependent queries - public queries are + // expected not to change in respect to the currently logged in user. + await invalidateAndRemoveQueries() +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts new file mode 100644 index 0000000000..7f40b0cbf6 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts @@ -0,0 +1,17 @@ +import { api, removeLocalUserData } from 'wasp/client/api' +import { invalidateAndRemoveQueries } from 'wasp/operations/resources' + +export default async function logout(): Promise { + try { + await api.post('/auth/logout') + } finally { + // Even if the logout request fails, we still want to remove the local user data + // in case the logout failed because of a network error and the user walked away + // from the computer. + removeLocalUserData() + + // TODO(filip): We are currently invalidating and removing all the queries, but + // we should remove only the non-public, user-dependent ones. + await invalidateAndRemoveQueries() + } +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts new file mode 100644 index 0000000000..03d33b5016 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts @@ -0,0 +1,2 @@ +// todo(filip): turn into a proper import/path +export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts new file mode 100644 index 0000000000..47a6293879 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts @@ -0,0 +1,38 @@ +import { deserialize as superjsonDeserialize } from 'superjson' +import { useQuery } from 'wasp/rpc' +import { api, handleApiError } from 'wasp/client/api' +import { HttpMethod } from 'wasp/types' +import type { AuthUser } from './types' +import { addMetadataToQuery } from 'wasp/rpc/queries' + +export const getMe = createUserGetter() + +export default function useAuth(queryFnArgs?: unknown, config?: any) { + return useQuery(getMe, queryFnArgs, config) +} + +function createUserGetter() { + const getMeRelativePath = 'auth/me' + const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` } + async function getMe(): Promise { + try { + const response = await api.get(getMeRoute.path) + + return superjsonDeserialize(response.data) + } catch (error) { + if (error.response?.status === 401) { + return null + } else { + handleApiError(error) + } + } + } + + addMetadataToQuery(getMe, { + relativeQueryPath: getMeRelativePath, + queryRoute: getMeRoute, + entitiesUsed: ['User'], + }) + + return getMe +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts new file mode 100644 index 0000000000..0de50de6d0 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts @@ -0,0 +1,23 @@ +import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types' + +export function getEmail(user: AuthUser): string | null { + return findUserIdentity(user, "email")?.providerUserId ?? null; +} + +export function getUsername(user: AuthUser): string | null { + return findUserIdentity(user, "username")?.providerUserId ?? null; +} + +export function getFirstProviderUserId(user?: AuthUser): string | null { + if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { + return null; + } + + return user.auth.identities[0].providerUserId ?? null; +} + +export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined { + return user.auth.identities.find( + (identity) => identity.providerName === providerName + ); +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts new file mode 100644 index 0000000000..4f08b8d552 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts @@ -0,0 +1,302 @@ +import { hashPassword } from './password.js' +import { verify } from './jwt.js' +import AuthError from 'wasp/core/AuthError' +import HttpError from 'wasp/core/HttpError' +import { prisma } from 'wasp/server' +import { sleep } from 'wasp/server/utils' +import { + type User, + type Auth, + type AuthIdentity, +} from 'wasp/entities' +import { Prisma } from '@prisma/client'; + +import { throwValidationError } from './validation.js' + +import { type UserSignupFields, type PossibleUserFields } from './providers/types.js' + +export type EmailProviderData = { + hashedPassword: string; + isEmailVerified: boolean; + emailVerificationSentAt: string | null; + passwordResetSentAt: string | null; +} + +export type UsernameProviderData = { + hashedPassword: string; +} + +export type OAuthProviderData = {} + +/** + * This type is used for type-level programming e.g. to enumerate + * all possible provider data types. + * + * The keys of this type are the names of the providers and the values + * are the types of the provider data. + */ +export type PossibleProviderData = { + email: EmailProviderData; + username: UsernameProviderData; + google: OAuthProviderData; + github: OAuthProviderData; +} + +export type ProviderName = keyof PossibleProviderData + +export const contextWithUserEntity = { + entities: { + User: prisma.user + } +} + +export const authConfig = { + failureRedirectPath: "/login", + successRedirectPath: "/", +} + +/** + * ProviderId uniquely identifies an auth identity e.g. + * "email" provider with user id "test@test.com" or + * "google" provider with user id "1234567890". + * + * We use this type to avoid passing the providerName and providerUserId + * separately. Also, we can normalize the providerUserId to make sure it's + * consistent across different DB operations. + */ +export type ProviderId = { + providerName: ProviderName; + providerUserId: string; +} + +export function createProviderId(providerName: ProviderName, providerUserId: string): ProviderId { + return { + providerName, + providerUserId: providerUserId.toLowerCase(), + } +} + +export async function findAuthIdentity(providerId: ProviderId): Promise { + return prisma.authIdentity.findUnique({ + where: { + providerName_providerUserId: providerId, + } + }); +} + +/** + * Updates the provider data for the given auth identity. + * + * This function performs data sanitization and serialization. + * Sanitization is done by hashing the password, so this function + * expects the password received in the `providerDataUpdates` + * **not to be hashed**. + */ +export async function updateAuthIdentityProviderData( + providerId: ProviderId, + existingProviderData: PossibleProviderData[PN], + providerDataUpdates: Partial, +): Promise { + // We are doing the sanitization here only on updates to avoid + // hashing the password multiple times. + const sanitizedProviderDataUpdates = await sanitizeProviderData(providerDataUpdates); + const newProviderData = { + ...existingProviderData, + ...sanitizedProviderDataUpdates, + } + const serializedProviderData = await serializeProviderData(newProviderData); + return prisma.authIdentity.update({ + where: { + providerName_providerUserId: providerId, + }, + data: { providerData: serializedProviderData }, + }); +} + +type FindAuthWithUserResult = Auth & { + user: User +} + +export async function findAuthWithUserBy( + where: Prisma.AuthWhereInput +): Promise { + return prisma.auth.findFirst({ where, include: { user: true }}); +} + +export async function createUser( + providerId: ProviderId, + serializedProviderData?: string, + userFields?: PossibleUserFields, +): Promise { + return prisma.user.create({ + data: { + // Using any here to prevent type errors when userFields are not + // defined. We want Prisma to throw an error in that case. + ...(userFields ?? {} as any), + auth: { + create: { + identities: { + create: { + providerName: providerId.providerName, + providerUserId: providerId.providerUserId, + providerData: serializedProviderData, + }, + }, + } + }, + }, + // We need to include the Auth entity here because we need `authId` + // to be able to create a session. + include: { + auth: true, + }, + }) +} + +export async function deleteUserByAuthId(authId: string): Promise<{ count: number }> { + return prisma.user.deleteMany({ where: { auth: { + id: authId, + } } }) +} + +export async function verifyToken(token: string): Promise { + return verify(token); +} + +// If an user exists, we don't want to leak information +// about it. Pretending that we're doing some work +// will make it harder for an attacker to determine +// if a user exists or not. +// NOTE: Attacker measuring time to response can still determine +// if a user exists or not. We'll be able to avoid it when +// we implement e-mail sending via jobs. +export async function doFakeWork(): Promise { + const timeToWork = Math.floor(Math.random() * 1000) + 1000; + return sleep(timeToWork); +} + +export function rethrowPossibleAuthError(e: unknown): void { + if (e instanceof AuthError) { + throwValidationError(e.message); + } + + // Prisma code P2002 is for unique constraint violations. + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') { + throw new HttpError(422, 'Save failed', { + message: `user with the same identity already exists`, + }) + } + + if (e instanceof Prisma.PrismaClientValidationError) { + // NOTE: Logging the error since this usually means that there are + // required fields missing in the request, we want the developer + // to know about it. + console.error(e) + throw new HttpError(422, 'Save failed', { + message: 'there was a database error' + }) + } + + // Prisma code P2021 is for missing table errors. + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2021') { + // NOTE: Logging the error since this usually means that the database + // migrations weren't run, we want the developer to know about it. + console.error(e) + console.info('🐝 This error can happen if you did\'t run the database migrations.') + throw new HttpError(500, 'Save failed', { + message: `there was a database error`, + }) + } + + // Prisma code P2003 is for foreign key constraint failure + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2003') { + console.error(e) + console.info(`🐝 This error can happen if you have some relation on your User entity + but you didn't specify the "onDelete" behaviour to either "Cascade" or "SetNull". + Read more at: https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/referential-actions`) + throw new HttpError(500, 'Save failed', { + message: `there was a database error`, + }) + } + + throw e +} + +export async function validateAndGetUserFields( + data: { + [key: string]: unknown + }, + userSignupFields?: UserSignupFields, +): Promise> { + const { + password: _password, + ...sanitizedData + } = data; + const result: Record = {}; + + if (!userSignupFields) { + return result; + } + + for (const [field, getFieldValue] of Object.entries(userSignupFields)) { + try { + const value = await getFieldValue(sanitizedData) + result[field] = value + } catch (e) { + throwValidationError(e.message) + } + } + return result; +} + +export function deserializeAndSanitizeProviderData( + providerData: string, + { shouldRemovePasswordField = false }: { shouldRemovePasswordField?: boolean } = {}, +): PossibleProviderData[PN] { + // NOTE: We are letting JSON.parse throw an error if the providerData is not valid JSON. + let data = JSON.parse(providerData) as PossibleProviderData[PN]; + + if (providerDataHasPasswordField(data) && shouldRemovePasswordField) { + delete data.hashedPassword; + } + + return data; +} + +export async function sanitizeAndSerializeProviderData( + providerData: PossibleProviderData[PN], +): Promise { + return serializeProviderData( + await sanitizeProviderData(providerData) + ); +} + +function serializeProviderData(providerData: PossibleProviderData[PN]): string { + return JSON.stringify(providerData); +} + +async function sanitizeProviderData( + providerData: PossibleProviderData[PN], +): Promise { + const data = { + ...providerData, + }; + if (providerDataHasPasswordField(data)) { + data.hashedPassword = await hashPassword(data.hashedPassword); + } + + return data; +} + + +function providerDataHasPasswordField( + providerData: PossibleProviderData[keyof PossibleProviderData], +): providerData is { hashedPassword: string } { + return 'hashedPassword' in providerData; +} + +export function throwInvalidCredentialsError(message?: string): void { + throw new HttpError(401, 'Invalid credentials', { message }) +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts new file mode 100644 index 0000000000..8ef076ee1f --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts @@ -0,0 +1,22 @@ +import { api, handleApiError } from 'wasp/client/api' +import { HttpMethod } from 'wasp/types' +import { + serialize as superjsonSerialize, + deserialize as superjsonDeserialize, + } from 'superjson' + +export type OperationRoute = { method: HttpMethod, path: string } + +export async function callOperation(operationRoute: OperationRoute & { method: HttpMethod.Post }, args: any) { + try { + const superjsonArgs = superjsonSerialize(args) + const response = await api.post(operationRoute.path, superjsonArgs) + return superjsonDeserialize(response.data) + } catch (error) { + handleApiError(error) + } +} + +export function makeOperationRoute(relativeOperationRoute: string): OperationRoute { + return { method: HttpMethod.Post, path: `/${relativeOperationRoute}` } +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json new file mode 100644 index 0000000000..8e954a8490 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -0,0 +1,116 @@ +{ + "name": "wasp", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "types": "tsc --declaration --emitDeclarationOnly --stripInternal --declarationDir dist" + }, + "exports": { + "./core/HttpError": "./dist/core/HttpError.js", + "./core/AuthError": "./dist/core/AuthError.js", + "./core/config": "./dist/core/config.js", + "./core/stitches.config": "./dist/core/stitches.config.js", + "./core/storage": "./dist/core/storage.js", + "./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", + "./auth/logout": "./dist/auth/logout.js", + "./auth/signup": "./dist/auth/signup.js", + "./auth/useAuth": "./dist/auth/useAuth.js", + "./auth/email": "./dist/auth/email/index.js", + "./auth/helpers/user": "./dist/auth/helpers/user.js", + "./auth/session": "./dist/auth/session.js", + "./auth/providers/types": "./dist/auth/providers/types.js", + "./auth/utils": "./dist/auth/utils.js", + "./auth/password": "./dist/auth/password.js", + "./auth/jwt": "./dist/auth/jwt.js", + "./auth/validation": "./dist/auth/validation.js", + "./auth/forms/Login": "./dist/auth/forms/Login.jsx", + "./auth/forms/Signup": "./dist/auth/forms/Signup.jsx", + "./auth/forms/VerifyEmail": "./dist/auth/forms/VerifyEmail.jsx", + "./auth/forms/ForgotPassword": "./dist/auth/forms/ForgotPassword.jsx", + "./auth/forms/ResetPassword": "./dist/auth/forms/ResetPassword.jsx", + "./auth/forms/internal/Form": "./dist/auth/forms/internal/Form.jsx", + "./auth/helpers/*": "./dist/auth/helpers/*.jsx", + "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", + "./api/events": "./dist/api/events.js", + "./operations": "./dist/operations/index.js", + "./ext-src/*": "./dist/ext-src/*.js", + "./operations/*": "./dist/operations/*", + "./universal/url": "./dist/universal/url.js", + "./universal/types": "./dist/universal/types.js", + "./universal/validators": "./dist/universal/validators.js", + "./server/middleware": "./dist/server/middleware/index.js", + "./server/utils": "./dist/server/utils.js", + "./server/actions": "./dist/server/actions/index.js", + "./server/queries": "./dist/server/queries/index.js", + "./server/auth/email": "./dist/server/auth/email/index.js", + "./dbSeed/types": "./dist/dbSeed/types.js", + "./test": "./dist/test/index.js", + "./test/*": "./dist/test/*.js", + "./crud/*": "./dist/crud/*.js", + "./server/crud/*": "./dist/server/crud/*", + "./email": "./dist/email/index.js", + "./email/core/types": "./dist/email/core/types.js", + "./server/auth/email/utils": "./dist/server/auth/email/utils.js", + "./jobs/*": "./dist/jobs/*.js", + "./jobs/pgBoss/types": "./dist/jobs/pgBoss/types.js", + "./router": "./dist/router/index.js", + "./server/webSocket": "./dist/server/webSocket/index.js", + "./webSocket": "./dist/webSocket/index.js", + "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", + + "./server/types": "./dist/server/types/index.js", + + "./server": "./dist/server/index.js", + "./server/api": "./dist/server/api/index.js", + "./client/api": "./dist/api/index.js", + "./auth": "./dist/auth/index.js" + }, + "typesVersions": { + "*": { + "client/api": ["api/index.ts"] + } + }, + "license": "ISC", + "include": [ + "src/**/*" + ], + "dependencies": {"@prisma/client": "4.16.2", + "prisma": "4.16.2", + "@tanstack/react-query": "^4.29.0", + "axios": "^1.4.0", + "express": "~4.18.1", + "jsonwebtoken": "^8.5.1", + "mitt": "3.0.0", + "react": "^18.2.0", + "lodash.merge": "^4.6.2", + "react-router-dom": "^5.3.3", + "react-hook-form": "^7.45.4", + "secure-password": "^4.0.0", + "superjson": "^1.12.2", + "@types/express-serve-static-core": "^4.17.13", + "@stitches/react": "^1.2.8", + "lucia": "^3.0.0-beta.14", + "@lucia-auth/adapter-prisma": "^4.0.0-beta.9", + "socket.io": "^4.6.1", + "socket.io-client": "^4.6.1", + "@socket.io/component-emitter": "^4.0.0", + "vitest": "^1.2.1", + "@vitest/ui": "^1.2.1", + "jsdom": "^21.1.1", + "@testing-library/react": "^14.1.2", + "@testing-library/jest-dom": "^6.3.0", + "msw": "^1.1.0" +}, + "devDependencies": {"@tsconfig/node18": "latest" +} +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts new file mode 100644 index 0000000000..fa27d07d00 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts @@ -0,0 +1,99 @@ +import { type Expand } from 'wasp/universal/types'; +import { type Request, type Response } from 'express' +import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' +import { prisma } from 'wasp/server' +import { + type User, + type Auth, + type AuthIdentity, +} from "wasp/entities" +import { + type EmailProviderData, + type UsernameProviderData, + type OAuthProviderData, +} from 'wasp/auth/utils' +import { type _Entity } from "./taggedEntities" +import { type Payload } from "./serialization"; + +export * from "./taggedEntities" +export * from "./serialization" + +export type Query = + Operation + +export type Action = + Operation + +export type AuthenticatedQuery = + AuthenticatedOperation + +export type AuthenticatedAction = + AuthenticatedOperation + +type AuthenticatedOperation = ( + args: Input, + context: ContextWithUser, +) => Output | Promise + +export type AuthenticatedApi< + Entities extends _Entity[], + Params extends ExpressParams, + ResBody, + ReqBody, + ReqQuery extends ExpressQuery, + Locals extends Record +> = ( + req: Request, + res: Response, + context: ContextWithUser, +) => void + +type Operation = ( + args: Input, + context: Context, +) => Output | Promise + +export type Api< + Entities extends _Entity[], + Params extends ExpressParams, + ResBody, + ReqBody, + ReqQuery extends ExpressQuery, + Locals extends Record +> = ( + req: Request, + res: Response, + context: Context, +) => void + +type EntityMap = { + [EntityName in Entities[number]["_entityName"]]: PrismaDelegate[EntityName] +} + +export type PrismaDelegate = { + "User": typeof prisma.user, + "Task": typeof prisma.task, +} + +type Context = Expand<{ + entities: Expand> +}> + +type ContextWithUser = Expand & { user?: AuthUser }> + +// TODO: This type must match the logic in auth/session.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 + +export type DeserializedAuthIdentity = Expand & { + providerData: Omit | Omit | OAuthProviderData +}> + +export type AuthUser = User & { + auth: Auth & { + identities: DeserializedAuthIdentity[] + } | null +} + +export type { ProviderName } from 'wasp/auth/utils' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts new file mode 100644 index 0000000000..54c224ba2e --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts @@ -0,0 +1,50 @@ +import { prisma } from 'wasp/server' + +import { createTask as createTask_ext } from 'wasp/ext-src/task/actions.js' +import { updateTask as updateTask_ext } from 'wasp/ext-src/task/actions.js' +import { deleteTasks as deleteTasks_ext } from 'wasp/ext-src/task/actions.js' +import { send as send_ext } from 'wasp/ext-src/user/customEmailSending.js' + +export type CreateTask = typeof createTask_ext + +export const createTask = async (args, context) => { + return (createTask_ext as any)(args, { + ...context, + entities: { + Task: prisma.task, + }, + }) +} + +export type UpdateTask = typeof updateTask_ext + +export const updateTask = async (args, context) => { + return (updateTask_ext as any)(args, { + ...context, + entities: { + Task: prisma.task, + }, + }) +} + +export type DeleteTasks = typeof deleteTasks_ext + +export const deleteTasks = async (args, context) => { + return (deleteTasks_ext as any)(args, { + ...context, + entities: { + Task: prisma.task, + }, + }) +} + +export type CustomEmailSending = typeof send_ext + +export const customEmailSending = async (args, context) => { + return (send_ext as any)(args, { + ...context, + entities: { + User: prisma.user, + }, + }) +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts new file mode 100644 index 0000000000..cbfb76d351 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts @@ -0,0 +1,14 @@ +import { prisma } from 'wasp/server' + +import { getTasks as getTasks_ext } from 'wasp/ext-src/task/queries.js' + +export type GetTasks = typeof getTasks_ext + +export const getTasks = async (args, context) => { + return (getTasks_ext as any)(args, { + ...context, + entities: { + Task: prisma.task, + }, + }) +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts new file mode 100644 index 0000000000..d7fe314996 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts @@ -0,0 +1,67 @@ +import crypto from 'crypto' +import { Request, Response, NextFunction } from 'express' + +import { readdir } from 'fs' +import { dirname } from 'path' +import { fileURLToPath } from 'url' + +import { type AuthUser } from 'wasp/auth' + +type RequestWithExtraFields = Request & { + user?: AuthUser; + sessionId?: string; +} + +/** + * Decorator for async express middleware that handles promise rejections. + * @param {Func} middleware - Express middleware function. + * @returns Express middleware that is exactly the same as the given middleware but, + * if given middleware returns promise, reject of that promise will be correctly handled, + * meaning that error will be forwarded to next(). + */ +export const handleRejection = ( + middleware: ( + req: RequestWithExtraFields, + res: Response, + next: NextFunction + ) => any +) => +async (req: RequestWithExtraFields, res: Response, next: NextFunction) => { + try { + await middleware(req, res, next) + } catch (error) { + next(error) + } +} + +export const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)) + +export function getDirPathFromFileUrl(fileUrl: string): string { + return fileURLToPath(dirname(fileUrl)) +} + +export async function importJsFilesFromDir( + pathToDir: string, + whitelistedFileNames: string[] | null = null +): Promise { + return new Promise((resolve, reject) => { + readdir(pathToDir, async (err, files) => { + if (err) { + return reject(err) + } + const importPromises = files + .filter((file) => file.endsWith('.js') && isWhitelistedFileName(file)) + .map((file) => import(`${pathToDir}/${file}`)) + resolve(Promise.all(importPromises)) + }) + }) + + function isWhitelistedFileName(fileName: string) { + // No whitelist means all files are whitelisted + if (!Array.isArray(whitelistedFileNames)) { + return true + } + + return whitelistedFileNames.some((whitelistedFileName) => fileName === whitelistedFileName) + } +} diff --git a/waspc/examples/todo-typescript/src/MainPage.tsx b/waspc/examples/todo-typescript/src/MainPage.tsx index 582eb2527d..9e89cbf449 100644 --- a/waspc/examples/todo-typescript/src/MainPage.tsx +++ b/waspc/examples/todo-typescript/src/MainPage.tsx @@ -11,8 +11,7 @@ import { } from 'wasp/rpc/actions' 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 { AuthUser, getFirstProviderUserId } from 'wasp/auth' import { Link } from 'react-router-dom' import { Tasks } from 'wasp/crud/Tasks' // import login from 'wasp/auth/login' @@ -20,7 +19,7 @@ import { Tasks } from 'wasp/crud/Tasks' import useAuth from 'wasp/auth/useAuth' import { Todo } from './Todo' -export const MainPage = ({ user }: { user: User }) => { +export const MainPage = ({ user }: { user: AuthUser }) => { const { data: tasks, isLoading, error } = useQuery(getTasks) const { data: userAgain } = useAuth() diff --git a/waspc/examples/todo-typescript/src/Todo.test.tsx b/waspc/examples/todo-typescript/src/Todo.test.tsx index 47dee23f7b..151554bce5 100644 --- a/waspc/examples/todo-typescript/src/Todo.test.tsx +++ b/waspc/examples/todo-typescript/src/Todo.test.tsx @@ -5,7 +5,7 @@ import { mockServer, renderInContext } from 'wasp/test' import { getTasks } from 'wasp/rpc/queries' import { Todo, areThereAnyTasks } from './Todo' import { MainPage } from './MainPage' -import type { User } from 'wasp/auth/types' +import type { AuthUser } from 'wasp/auth' import { getMe } from 'wasp/auth/useAuth' import { Tasks } from 'wasp/crud/Tasks' @@ -54,7 +54,7 @@ const mockUser = { ], }, address: '', -} satisfies User +} satisfies AuthUser test('handles mock data', async () => { mockQuery(getTasks, mockTasks) diff --git a/waspc/examples/todo-typescript/src/websocket/index.ts b/waspc/examples/todo-typescript/src/websocket/index.ts index 502856a824..8640fd3dc9 100644 --- a/waspc/examples/todo-typescript/src/websocket/index.ts +++ b/waspc/examples/todo-typescript/src/websocket/index.ts @@ -1,26 +1,26 @@ -import { WebSocketDefinition } from "wasp/server/webSocket"; -import { getFirstProviderUserId } from "wasp/auth/user"; +import { WebSocketDefinition } from 'wasp/server/webSocket' +import { getFirstProviderUserId } from 'wasp/auth' export const webSocketFn: WebSocketDefinition< ClientToServerEvents, ServerToClientEvents, InterServerEvents > = (io, context) => { - io.on("connection", (socket) => { - const username = getFirstProviderUserId(socket.data.user) ?? "Unknown"; - console.log("a user connected: ", username); + io.on('connection', (socket) => { + const username = getFirstProviderUserId(socket.data.user) ?? 'Unknown' + console.log('a user connected: ', username) - socket.on("chatMessage", async (msg) => { - console.log("message: ", msg); - io.emit("chatMessage", { id: "random", username, text: msg }); - }); - }); -}; + socket.on('chatMessage', async (msg) => { + console.log('message: ', msg) + io.emit('chatMessage', { id: 'random', username, text: msg }) + }) + }) +} interface ServerToClientEvents { - chatMessage: (msg: { id: string; username: string; text: string }) => void; + chatMessage: (msg: { id: string; username: string; text: string }) => void } interface ClientToServerEvents { - chatMessage: (msg: string) => void; + chatMessage: (msg: string) => void } interface InterServerEvents {} From 101b13b11316de33619917cd3bf9d483c6990bd9 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Mon, 29 Jan 2024 16:14:05 +0100 Subject: [PATCH 05/19] Migrate wasp/client/auth API --- .../src/auth/pages/createAuthRequiredPage.jsx | 2 +- .../templates/sdk/auth/email/actions/login.ts | 1 + .../sdk/auth/email/actions/passwordReset.ts | 2 + .../sdk/auth/email/actions/signup.ts | 1 + .../sdk/auth/email/actions/verifyEmail.ts | 1 + .../templates/sdk/auth/forms/Auth.tsx | 2 + .../sdk/auth/forms/ForgotPassword.tsx | 1 + .../templates/sdk/auth/forms/Login.tsx | 1 + .../sdk/auth/forms/ResetPassword.tsx | 1 + .../templates/sdk/auth/forms/Signup.tsx | 1 + .../templates/sdk/auth/forms/VerifyEmail.tsx | 1 + .../sdk/auth/forms/internal/Form.tsx | 7 ++ .../sdk/auth/forms/internal/Message.tsx | 3 + .../forms/internal/common/LoginSignupForm.tsx | 2 + .../internal/email/ForgotPasswordForm.tsx | 1 + .../internal/email/ResetPasswordForm.tsx | 1 + .../forms/internal/email/VerifyEmailForm.tsx | 1 + .../sdk/auth/forms/internal/email/useEmail.ts | 1 + .../forms/internal/social/SocialButton.tsx | 1 + .../forms/internal/social/SocialIcons.tsx | 2 + .../useUsernameAndPassword.ts | 1 + .../templates/sdk/auth/forms/types.ts | 7 ++ .../templates/sdk/auth/helpers/Generic.tsx | 2 + .../templates/sdk/auth/helpers/user.ts | 1 + .../Generator/templates/sdk/auth/index.ts | 2 - .../data/Generator/templates/sdk/auth/jwt.ts | 2 + .../Generator/templates/sdk/auth/logout.ts | 1 + .../Generator/templates/sdk/auth/lucia.ts | 1 + .../Generator/templates/sdk/auth/useAuth.ts | 2 + .../data/Generator/templates/sdk/auth/user.ts | 4 + .../templates/sdk/client/auth/email.ts | 5 ++ .../templates/sdk/client/auth/github.ts | 2 + .../templates/sdk/client/auth/google.ts | 2 + .../templates/sdk/client/auth/index.ts | 20 +++++ .../Generator/templates/sdk/client/auth/ui.ts | 23 +++++ .../templates/sdk/client/auth/username.ts | 2 + .../data/Generator/templates/sdk/package.json | 27 +----- .../.wasp/out/sdk/wasp/package.json | 15 +--- .../examples/todo-typescript/src/MainPage.tsx | 3 +- .../todo-typescript/src/Todo.test.tsx | 2 +- .../todo-typescript/src/user/auth.tsx | 31 +++---- waspc/src/Wasp/Generator/SdkGenerator.hs | 3 + .../Generator/SdkGenerator/Client/AuthG.hs | 87 +++++++++++++++++++ waspc/waspc.cabal | 1 + 44 files changed, 220 insertions(+), 59 deletions(-) create mode 100644 waspc/data/Generator/templates/sdk/client/auth/email.ts create mode 100644 waspc/data/Generator/templates/sdk/client/auth/github.ts create mode 100644 waspc/data/Generator/templates/sdk/client/auth/google.ts create mode 100644 waspc/data/Generator/templates/sdk/client/auth/index.ts create mode 100644 waspc/data/Generator/templates/sdk/client/auth/ui.ts create mode 100644 waspc/data/Generator/templates/sdk/client/auth/username.ts create mode 100644 waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs diff --git a/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx b/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx index 8725da1835..7458dd470f 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx +++ b/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx @@ -2,7 +2,7 @@ import React from 'react' import { Redirect } from 'react-router-dom' -import useAuth from 'wasp/auth/useAuth' +import { useAuth } from 'wasp/client/auth' const createAuthRequiredPage = (Page) => { diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts index 2f2cfb4777..6a2cbbad1b 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts @@ -2,6 +2,7 @@ import { api, handleApiError } from 'wasp/client/api'; import { initSession } from '../../helpers/user'; +// PUBLIC API export async function login(data: { email: string; password: string }): Promise { try { const response = await api.post('{= loginPath =}', data); diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts index ac721f2102..ca48bbf98d 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts @@ -1,6 +1,7 @@ {{={= =}=}} import { api, handleApiError } from 'wasp/client/api'; +// PUBLIC API export async function requestPasswordReset(data: { email: string; }): Promise<{ success: boolean }> { try { const response = await api.post('{= requestPasswordResetPath =}', data); @@ -10,6 +11,7 @@ export async function requestPasswordReset(data: { email: string; }): Promise<{ } } +// PUBLIC API export async function resetPassword(data: { token: string; password: string; }): Promise<{ success: boolean }> { try { const response = await api.post('{= resetPasswordPath =}', data); diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts index cf882b2f71..797af04a26 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts @@ -1,6 +1,7 @@ {{={= =}=}} import { api, handleApiError } from 'wasp/client/api'; +// PUBLIC API export async function signup(data: { email: string; password: string }): Promise<{ success: boolean }> { try { const response = await api.post('{= signupPath =}', data); diff --git a/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts index fa96569bb3..80c7c4ceba 100644 --- a/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts @@ -1,6 +1,7 @@ {{={= =}=}} import { api, handleApiError } from 'wasp/client/api' +// PUBLIC API export async function verifyEmail(data: { token: string }): Promise<{ success: boolean; reason?: string }> { diff --git a/waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx b/waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx index 99568c40da..e14ebab308 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx @@ -33,6 +33,7 @@ const HeaderText = styled('h2', { }) +// PRIVATE API export const AuthContext = createContext({ isLoading: false, setIsLoading: (isLoading: boolean) => {}, @@ -98,4 +99,5 @@ function Auth ({ state, appearance, logo, socialLayout = 'horizontal', additiona ) } +// PRIVATE API export default Auth; diff --git a/waspc/data/Generator/templates/sdk/auth/forms/ForgotPassword.tsx b/waspc/data/Generator/templates/sdk/auth/forms/ForgotPassword.tsx index f0bb2623a1..e0893bebad 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/ForgotPassword.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/ForgotPassword.tsx @@ -1,6 +1,7 @@ import Auth from './Auth' import { type CustomizationOptions, State } from './types' +// PUBLIC API export function ForgotPasswordForm({ appearance, logo, diff --git a/waspc/data/Generator/templates/sdk/auth/forms/Login.tsx b/waspc/data/Generator/templates/sdk/auth/forms/Login.tsx index 2ea532d9c5..f8fca66082 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/Login.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/Login.tsx @@ -1,6 +1,7 @@ import Auth from './Auth' import { type CustomizationOptions, State } from './types' +// PUBLIC API export function LoginForm({ appearance, logo, diff --git a/waspc/data/Generator/templates/sdk/auth/forms/ResetPassword.tsx b/waspc/data/Generator/templates/sdk/auth/forms/ResetPassword.tsx index daa14ebae1..8457b238f1 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/ResetPassword.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/ResetPassword.tsx @@ -1,6 +1,7 @@ import Auth from './Auth' import { type CustomizationOptions, State } from './types' +// PUBLIC API export function ResetPasswordForm({ appearance, logo, diff --git a/waspc/data/Generator/templates/sdk/auth/forms/Signup.tsx b/waspc/data/Generator/templates/sdk/auth/forms/Signup.tsx index 66ffab4503..32c7afc38a 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/Signup.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/Signup.tsx @@ -5,6 +5,7 @@ import { State, } from './types' +// PUBLIC API export function SignupForm({ appearance, logo, diff --git a/waspc/data/Generator/templates/sdk/auth/forms/VerifyEmail.tsx b/waspc/data/Generator/templates/sdk/auth/forms/VerifyEmail.tsx index 29dae07082..1c81e0e244 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/VerifyEmail.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/VerifyEmail.tsx @@ -1,6 +1,7 @@ import Auth from './Auth' import { type CustomizationOptions, State } from './types' +// PUBLIC API export function VerifyEmailForm({ appearance, logo, diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/Form.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/Form.tsx index 781c75a0ae..1634307427 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/Form.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/Form.tsx @@ -1,15 +1,18 @@ import { styled } from 'wasp/core/stitches.config' +// PRIVATE API export const Form = styled('form', { marginTop: '1.5rem', }) +// PUBLIC API export const FormItemGroup = styled('div', { '& + div': { marginTop: '1.5rem', }, }) +// PUBLIC API export const FormLabel = styled('label', { display: 'block', fontSize: '$sm', @@ -48,10 +51,13 @@ const commonInputStyles = { margin: 0, } +// PUBLIC API export const FormInput = styled('input', commonInputStyles) +// PUBLIC API export const FormTextarea = styled('textarea', commonInputStyles) +// PUBLIC API export const FormError = styled('div', { display: 'block', fontSize: '$sm', @@ -60,6 +66,7 @@ export const FormError = styled('div', { marginTop: '0.5rem', }) +// PRIVATE API export const SubmitButton = styled('button', { display: 'flex', justifyContent: 'center', diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/Message.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/Message.tsx index 7279ed2525..362ff9dfda 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/Message.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/Message.tsx @@ -1,5 +1,6 @@ import { styled } from 'wasp/core/stitches.config' +// PRIVATE API export const Message = styled('div', { padding: '0.5rem 0.75rem', borderRadius: '0.375rem', @@ -7,11 +8,13 @@ export const Message = styled('div', { background: '$gray400', }) +// PRIVATE API export const MessageError = styled(Message, { background: '$errorBackground', color: '$errorText', }) +// PRIVATE API export const MessageSuccess = styled(Message, { background: '$successBackground', color: '$successText', diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx index e76e56bb7c..8c96121dd9 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx @@ -117,10 +117,12 @@ const gitHubSignInUrl = `${config.apiUrl}{= gitHubSignInPath =}` // know the exact shape of the form values. We are assuming that the form values // will be a flat object with string values. =} +// PRIVATE API export type LoginSignupFormFields = { [key: string]: string; } +// PRIVATE API export const LoginSignupForm = ({ state, socialButtonsDirection = 'horizontal', diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ForgotPasswordForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ForgotPasswordForm.tsx index e20328a4b9..b2ad9ae7d2 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ForgotPasswordForm.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ForgotPasswordForm.tsx @@ -4,6 +4,7 @@ import { requestPasswordReset } from '../../../email/actions/passwordReset.js' import { Form, FormItemGroup, FormLabel, FormInput, SubmitButton, FormError } from '../Form' import { AuthContext } from '../../Auth' +// PRIVATE API export const ForgotPasswordForm = () => { const { register, handleSubmit, reset, formState: { errors } } = useForm<{ email: string }>() const { isLoading, setErrorMessage, setSuccessMessage, setIsLoading } = useContext(AuthContext) diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ResetPasswordForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ResetPasswordForm.tsx index a3535d57d3..ba3cd2f0ca 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ResetPasswordForm.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ResetPasswordForm.tsx @@ -5,6 +5,7 @@ import { useLocation } from 'react-router-dom' import { Form, FormItemGroup, FormLabel, FormInput, SubmitButton, FormError } from '../Form' import { AuthContext } from '../../Auth' +// PRIVATE API export const ResetPasswordForm = () => { const { register, handleSubmit, reset, formState: { errors } } = useForm<{ password: string; passwordConfirmation: string }>() const { isLoading, setErrorMessage, setSuccessMessage, setIsLoading } = useContext(AuthContext) diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/VerifyEmailForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/VerifyEmailForm.tsx index 3d8444985d..dd5b5d6777 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/VerifyEmailForm.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/VerifyEmailForm.tsx @@ -4,6 +4,7 @@ import { verifyEmail } from '../../../email/actions/verifyEmail.js' import { Message } from '../Message' import { AuthContext } from '../../Auth' +// PRIVATE API export const VerifyEmailForm = () => { const { isLoading, setErrorMessage, setSuccessMessage, setIsLoading } = useContext(AuthContext) const location = useLocation() diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/useEmail.ts b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/useEmail.ts index 4d8b792ba0..3cd191b91d 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/email/useEmail.ts +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/useEmail.ts @@ -1,6 +1,7 @@ import { signup } from '../../../email/actions/signup' import { login } from '../../../email/actions/login' +// PRIVATE API export function useEmail({ onError, showEmailVerificationPending, diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx index 47044884bc..cd2cf48853 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx @@ -1,5 +1,6 @@ import { styled } from 'wasp/core/stitches.config' +// PRIVATE API export const SocialButton = styled('a', { display: 'flex', justifyContent: 'center', diff --git a/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialIcons.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialIcons.tsx index aac1f53ff1..7192e457b1 100644 --- a/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialIcons.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialIcons.tsx @@ -5,6 +5,7 @@ const defaultStyles = css({ height: '1.25rem', }) +// PRIVATE API export const Google = () => ( ( ) +// PRIVATE API export const GitHub = () => ( [0] } +// PRIVATE API export type ErrorMessage = { title: string description?: string } +// PRIVATE API export type FormState = { isLoading: boolean } +// PRIVATE API export type AdditionalSignupFieldRenderFn = ( hookForm: UseFormReturn, formState: FormState ) => React.ReactNode +// PRIVATE API export type AdditionalSignupField = { name: string label: string @@ -40,6 +46,7 @@ export type AdditionalSignupField = { validations?: RegisterOptions } +// PRIVATE API export type AdditionalSignupFields = | (AdditionalSignupField | AdditionalSignupFieldRenderFn)[] | AdditionalSignupFieldRenderFn diff --git a/waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx b/waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx index b54464da18..21922f31be 100644 --- a/waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx +++ b/waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx @@ -4,8 +4,10 @@ import config from 'wasp/core/config' import { SocialButton } from '../forms/internal/social/SocialButton' import * as SocialIcons from '../forms/internal/social/SocialIcons' +// PUBLIC API export const signInUrl = `${config.apiUrl}{= signInPath =}` +// PUBLIC API export function SignInButton() { return ( diff --git a/waspc/data/Generator/templates/sdk/auth/helpers/user.ts b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts index 259a4c34b5..050dd3f32a 100644 --- a/waspc/data/Generator/templates/sdk/auth/helpers/user.ts +++ b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts @@ -1,6 +1,7 @@ import { setSessionId } from 'wasp/client/api' import { invalidateAndRemoveQueries } from 'wasp/operations/resources' +// PRIVATE API export async function initSession(sessionId: string): Promise { setSessionId(sessionId) // We need to invalidate queries after login in order to get the correct user diff --git a/waspc/data/Generator/templates/sdk/auth/index.ts b/waspc/data/Generator/templates/sdk/auth/index.ts index 56d03e4d89..83cfcc27c3 100644 --- a/waspc/data/Generator/templates/sdk/auth/index.ts +++ b/waspc/data/Generator/templates/sdk/auth/index.ts @@ -1,7 +1,5 @@ export { defineUserSignupFields } from './providers/types.js'; -// PUBLIC export type { AuthUser } from '../server/_types' -// PUBLIC export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js' diff --git a/waspc/data/Generator/templates/sdk/auth/jwt.ts b/waspc/data/Generator/templates/sdk/auth/jwt.ts index cb7f33725e..8ed702e4c0 100644 --- a/waspc/data/Generator/templates/sdk/auth/jwt.ts +++ b/waspc/data/Generator/templates/sdk/auth/jwt.ts @@ -8,5 +8,7 @@ const jwtVerify = util.promisify(jwt.verify) const JWT_SECRET = config.auth.jwtSecret +// PRIVATE API export const signData = (data, options) => jwtSign(data, JWT_SECRET, options) +// PRIVATE API export const verify = (token) => jwtVerify(token, JWT_SECRET) diff --git a/waspc/data/Generator/templates/sdk/auth/logout.ts b/waspc/data/Generator/templates/sdk/auth/logout.ts index 7f40b0cbf6..4a51817560 100644 --- a/waspc/data/Generator/templates/sdk/auth/logout.ts +++ b/waspc/data/Generator/templates/sdk/auth/logout.ts @@ -1,6 +1,7 @@ import { api, removeLocalUserData } from 'wasp/client/api' import { invalidateAndRemoveQueries } from 'wasp/operations/resources' +// PUBLIC API export default async function logout(): Promise { try { await api.post('/auth/logout') diff --git a/waspc/data/Generator/templates/sdk/auth/lucia.ts b/waspc/data/Generator/templates/sdk/auth/lucia.ts index eabf63523e..3a91263514 100644 --- a/waspc/data/Generator/templates/sdk/auth/lucia.ts +++ b/waspc/data/Generator/templates/sdk/auth/lucia.ts @@ -12,6 +12,7 @@ const prismaAdapter = new PrismaAdapter( prisma.{= authEntityLower =} as any ); +// PRIVATE API /** * We are using Lucia for session management. * diff --git a/waspc/data/Generator/templates/sdk/auth/useAuth.ts b/waspc/data/Generator/templates/sdk/auth/useAuth.ts index c7b02273cf..5038c40d77 100644 --- a/waspc/data/Generator/templates/sdk/auth/useAuth.ts +++ b/waspc/data/Generator/templates/sdk/auth/useAuth.ts @@ -6,8 +6,10 @@ import { HttpMethod } from 'wasp/types' import type { AuthUser } from './types' import { addMetadataToQuery } from 'wasp/rpc/queries' +// PUBLIC API export const getMe = createUserGetter() +// PUBLIC API export default function useAuth(queryFnArgs?: unknown, config?: any) { return useQuery(getMe, queryFnArgs, config) } diff --git a/waspc/data/Generator/templates/sdk/auth/user.ts b/waspc/data/Generator/templates/sdk/auth/user.ts index 0de50de6d0..f9bc6d39a3 100644 --- a/waspc/data/Generator/templates/sdk/auth/user.ts +++ b/waspc/data/Generator/templates/sdk/auth/user.ts @@ -1,13 +1,16 @@ import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types' +// PUBLIC API export function getEmail(user: AuthUser): string | null { return findUserIdentity(user, "email")?.providerUserId ?? null; } +// PUBLIC API export function getUsername(user: AuthUser): string | null { return findUserIdentity(user, "username")?.providerUserId ?? null; } +// PUBLIC API export function getFirstProviderUserId(user?: AuthUser): string | null { if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { return null; @@ -16,6 +19,7 @@ export function getFirstProviderUserId(user?: AuthUser): string | null { return user.auth.identities[0].providerUserId ?? null; } +// PUBLIC API export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined { return user.auth.identities.find( (identity) => identity.providerName === providerName diff --git a/waspc/data/Generator/templates/sdk/client/auth/email.ts b/waspc/data/Generator/templates/sdk/client/auth/email.ts new file mode 100644 index 0000000000..244e4a5e26 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/auth/email.ts @@ -0,0 +1,5 @@ +// PUBLIC API +export { login } from '../../auth/email/actions/login' +export { signup } from '../../auth/email/actions/signup' +export { requestPasswordReset, resetPassword } from '../../auth/email/actions/passwordReset' +export { verifyEmail } from '../../auth/email/actions/verifyEmail' diff --git a/waspc/data/Generator/templates/sdk/client/auth/github.ts b/waspc/data/Generator/templates/sdk/client/auth/github.ts new file mode 100644 index 0000000000..a749c0f788 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/auth/github.ts @@ -0,0 +1,2 @@ +// PUBLIC API +export { signInUrl as githubSignInUrl } from '../../auth/helpers/Github' diff --git a/waspc/data/Generator/templates/sdk/client/auth/google.ts b/waspc/data/Generator/templates/sdk/client/auth/google.ts new file mode 100644 index 0000000000..a3f0a98ac5 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/auth/google.ts @@ -0,0 +1,2 @@ +// PUBLIC API +export { signInUrl as googleSignInUrl } from '../../auth/helpers/Google' diff --git a/waspc/data/Generator/templates/sdk/client/auth/index.ts b/waspc/data/Generator/templates/sdk/client/auth/index.ts new file mode 100644 index 0000000000..14b2ea392f --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/auth/index.ts @@ -0,0 +1,20 @@ +{{={= =}=}} +export * from './ui' +{=# isEmailAuthEnabled =} +export * from './email' +{=/ isEmailAuthEnabled =} +{=# isUsernameAndPasswordAuthEnabled =} +export * from './username' +{=/ isUsernameAndPasswordAuthEnabled =} +{=# isGoogleAuthEnabled =} +export * from './google' +{=/ isGoogleAuthEnabled =} +{=# isGithubAuthEnabled =} +export * from './github' +{=/ isGithubAuthEnabled =} +export { + default as useAuth, + getMe, +} from '../../auth/useAuth' + +export { default as logout } from '../../auth/logout' diff --git a/waspc/data/Generator/templates/sdk/client/auth/ui.ts b/waspc/data/Generator/templates/sdk/client/auth/ui.ts new file mode 100644 index 0000000000..d1567ec582 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/auth/ui.ts @@ -0,0 +1,23 @@ +{{={= =}=}} +// PUBLIC API +export { LoginForm } from '../../auth/forms/Login' +export { SignupForm } from '../../auth/forms/Signup' +{=# isEmailAuthEnabled =} +export { ForgotPasswordForm } from '../../auth/forms/ForgotPassword' +export { VerifyEmailForm } from '../../auth/forms/VerifyEmail' +export { ResetPasswordForm } from '../../auth/forms/ResetPassword' +{=/ isEmailAuthEnabled =} +export type { CustomizationOptions } from '../../auth/forms/types' +{=# isGoogleAuthEnabled =} +export { SignInButton as GoogleSignInButton } from '../../auth/helpers/Google' +{=/ isGoogleAuthEnabled =} +{=# isGithubAuthEnabled =} +export { SignInButton as GithubSignInButton } from '../../auth/helpers/Github' +{=/ isGithubAuthEnabled =} +export { + FormError, + FormInput, + FormTextarea, + FormItemGroup, + FormLabel, +} from '../../auth/forms/internal/Form' diff --git a/waspc/data/Generator/templates/sdk/client/auth/username.ts b/waspc/data/Generator/templates/sdk/client/auth/username.ts new file mode 100644 index 0000000000..f83e89683d --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/auth/username.ts @@ -0,0 +1,2 @@ +export { default as login } from '../../auth/login' +export { default as signup } from '../../auth/signup' diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 847d6a1b8b..f5c02c6afe 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -37,16 +37,6 @@ "./rpc/queryClient": "./dist/rpc/queryClient.js", {=! Used by users, documented. =} "./types": "./dist/types/index.js", - {=! Used by users, documented. =} - "./auth/login": "./dist/auth/login.js", - {=! Used by users, documented. =} - "./auth/logout": "./dist/auth/logout.js", - {=! Used by users, documented. =} - "./auth/signup": "./dist/auth/signup.js", - {=! Used by users, documented. =} - "./auth/useAuth": "./dist/auth/useAuth.js", - {=! Used by users, documented. =} - "./auth/email": "./dist/auth/email/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/helpers/user": "./dist/auth/helpers/user.js", {=! Used by our code, uncodumented (but accessible) for users. =} @@ -61,20 +51,6 @@ "./auth/jwt": "./dist/auth/jwt.js", {=! Used by user, documented. =} "./auth/validation": "./dist/auth/validation.js", - {=! Used by users, documented. =} - "./auth/forms/Login": "./dist/auth/forms/Login.jsx", - {=! Used by users, documented. =} - "./auth/forms/Signup": "./dist/auth/forms/Signup.jsx", - {=! Used by users, documented. =} - "./auth/forms/VerifyEmail": "./dist/auth/forms/VerifyEmail.jsx", - {=! Used by users, documented. =} - "./auth/forms/ForgotPassword": "./dist/auth/forms/ForgotPassword.jsx", - {=! Used by users, documented. =} - "./auth/forms/ResetPassword": "./dist/auth/forms/ResetPassword.jsx", - {=! Used by users, documented. =} - "./auth/forms/internal/Form": "./dist/auth/forms/internal/Form.jsx", - {=! Used by users, documented. =} - "./auth/helpers/*": "./dist/auth/helpers/*.jsx", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", {=! Used by our framework code (Websockets), undocumented (but accessible) for users. =} @@ -145,7 +121,8 @@ {=! Public: { api } =} {=! Private: [sdk] =} "./client/api": "./dist/api/index.js", - "./auth": "./dist/auth/index.js" + "./auth": "./dist/auth/index.js", + "./client/auth": "./dist/client/auth/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json index 8e954a8490..3545ebabf7 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -21,11 +21,6 @@ "./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", - "./auth/logout": "./dist/auth/logout.js", - "./auth/signup": "./dist/auth/signup.js", - "./auth/useAuth": "./dist/auth/useAuth.js", - "./auth/email": "./dist/auth/email/index.js", "./auth/helpers/user": "./dist/auth/helpers/user.js", "./auth/session": "./dist/auth/session.js", "./auth/providers/types": "./dist/auth/providers/types.js", @@ -33,13 +28,6 @@ "./auth/password": "./dist/auth/password.js", "./auth/jwt": "./dist/auth/jwt.js", "./auth/validation": "./dist/auth/validation.js", - "./auth/forms/Login": "./dist/auth/forms/Login.jsx", - "./auth/forms/Signup": "./dist/auth/forms/Signup.jsx", - "./auth/forms/VerifyEmail": "./dist/auth/forms/VerifyEmail.jsx", - "./auth/forms/ForgotPassword": "./dist/auth/forms/ForgotPassword.jsx", - "./auth/forms/ResetPassword": "./dist/auth/forms/ResetPassword.jsx", - "./auth/forms/internal/Form": "./dist/auth/forms/internal/Form.jsx", - "./auth/helpers/*": "./dist/auth/helpers/*.jsx", "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", "./api/events": "./dist/api/events.js", "./operations": "./dist/operations/index.js", @@ -73,7 +61,8 @@ "./server": "./dist/server/index.js", "./server/api": "./dist/server/api/index.js", "./client/api": "./dist/api/index.js", - "./auth": "./dist/auth/index.js" + "./auth": "./dist/auth/index.js", + "./client/auth": "./dist/client/auth/index.js" }, "typesVersions": { "*": { diff --git a/waspc/examples/todo-typescript/src/MainPage.tsx b/waspc/examples/todo-typescript/src/MainPage.tsx index 9e89cbf449..cc400853bd 100644 --- a/waspc/examples/todo-typescript/src/MainPage.tsx +++ b/waspc/examples/todo-typescript/src/MainPage.tsx @@ -1,6 +1,5 @@ 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 import { getTasks } from 'wasp/rpc/queries' import { @@ -16,8 +15,8 @@ import { Link } from 'react-router-dom' import { Tasks } from 'wasp/crud/Tasks' // import login from 'wasp/auth/login' // import signup from 'wasp/auth/signup' -import useAuth from 'wasp/auth/useAuth' import { Todo } from './Todo' +import { logout, useAuth } from 'wasp/client/auth' export const MainPage = ({ user }: { user: AuthUser }) => { const { data: tasks, isLoading, error } = useQuery(getTasks) diff --git a/waspc/examples/todo-typescript/src/Todo.test.tsx b/waspc/examples/todo-typescript/src/Todo.test.tsx index 151554bce5..b515530f0e 100644 --- a/waspc/examples/todo-typescript/src/Todo.test.tsx +++ b/waspc/examples/todo-typescript/src/Todo.test.tsx @@ -6,7 +6,7 @@ import { getTasks } from 'wasp/rpc/queries' import { Todo, areThereAnyTasks } from './Todo' import { MainPage } from './MainPage' import type { AuthUser } from 'wasp/auth' -import { getMe } from 'wasp/auth/useAuth' +import { getMe } from 'wasp/client/auth' import { Tasks } from 'wasp/crud/Tasks' const mockTasks = [ diff --git a/waspc/examples/todo-typescript/src/user/auth.tsx b/waspc/examples/todo-typescript/src/user/auth.tsx index 2d6b341aa0..9a15825461 100644 --- a/waspc/examples/todo-typescript/src/user/auth.tsx +++ b/waspc/examples/todo-typescript/src/user/auth.tsx @@ -1,13 +1,15 @@ -import { ResetPasswordForm } from "wasp/auth/forms/ResetPassword"; -import { LoginForm } from "wasp/auth/forms/Login"; -import { VerifyEmailForm } from "wasp/auth/forms/VerifyEmail"; -import { SignupForm } from "wasp/auth/forms/Signup"; import { + LoginForm, + SignupForm, + VerifyEmailForm, + ResetPasswordForm, + ForgotPasswordForm, FormError, FormInput, FormItemGroup, FormLabel, -} from "wasp/auth/forms/internal/Form"; +} from 'wasp/client/auth' + // import { // SignInButton as GitHubSignInButton, // signInUrl as gitHubSignInUrl, @@ -17,8 +19,7 @@ import { // signInUrl as googleSignInUrl, // } from "wasp/auth/helpers/Google"; -import { ForgotPasswordForm } from "wasp/auth/forms/ForgotPassword"; -import { Link, routes } from "wasp/router"; +import { Link, routes } from 'wasp/router' export function SignupPage() { return ( @@ -32,15 +33,15 @@ export function SignupPage() { Address {errors.address && ( {errors.address.message} )} - ); + ) }} /> {/*
@@ -55,7 +56,7 @@ export function SignupPage() { The link to the login page is {routes.LoginRoute.build()}. - ); + ) } export function LoginPage() { @@ -70,17 +71,17 @@ export function LoginPage() { I don't have an account yet (go to signup). - ); + ) } export function RequestPasswordResetPage() { - return ; + return } export function PasswordResetPage() { - return ; + return } export function EmailVerificationPage() { - return ; + return } diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 363a1ad111..e3f168a9db 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -37,6 +37,7 @@ import Wasp.Generator.Job.Process (runNodeCommandAsJob) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.SdkGenerator.AuthG (genAuth) +import Wasp.Generator.SdkGenerator.Client.AuthG (genNewClientAuth) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSender) @@ -112,6 +113,8 @@ genSdkReal spec = <++> genMiddleware spec <++> genExportedTypesDir spec <++> genEmailSender spec + -- New API + <++> genNewClientAuth spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs new file mode 100644 index 0000000000..12fe44a5af --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs @@ -0,0 +1,87 @@ +module Wasp.Generator.SdkGenerator.Client.AuthG + ( genNewClientAuth, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import StrongPath (File', Path', Rel, relfile) +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.Auth +import Wasp.AppSpec.Valid (getApp) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Util ((<++>)) + +genNewClientAuth :: AppSpec -> Generator [FileDraft] +genNewClientAuth spec = + case maybeAuth of + Nothing -> return [] + Just auth -> + sequence + [ genAuthIndex auth, + genAuthUI auth + ] + <++> genAuthEmail auth + <++> genAuthUsername auth + <++> genAuthGoogle auth + <++> genAuthGitHub auth + where + maybeAuth = AS.App.auth $ snd $ getApp spec + +genAuthIndex :: AS.Auth.Auth -> Generator FileDraft +genAuthIndex auth = + return $ + C.mkTmplFdWithData + [relfile|client/auth/index.ts|] + tmplData + where + tmplData = getAuthProvidersJson auth + +genAuthUI :: AS.Auth.Auth -> Generator FileDraft +genAuthUI auth = + return $ + C.mkTmplFdWithData + [relfile|client/auth/ui.ts|] + tmplData + where + tmplData = getAuthProvidersJson auth + +genAuthEmail :: AS.Auth.Auth -> Generator [FileDraft] +genAuthEmail auth = + if AS.Auth.isEmailAuthEnabled auth + then sequence [genFileCopy [relfile|client/auth/email.ts|]] + else return [] + +genAuthUsername :: AS.Auth.Auth -> Generator [FileDraft] +genAuthUsername auth = + if AS.Auth.isUsernameAndPasswordAuthEnabled auth + then sequence [genFileCopy [relfile|client/auth/username.ts|]] + else return [] + +genAuthGoogle :: AS.Auth.Auth -> Generator [FileDraft] +genAuthGoogle auth = + if AS.Auth.isGoogleAuthEnabled auth + then sequence [genFileCopy [relfile|client/auth/google.ts|]] + else return [] + +genAuthGitHub :: AS.Auth.Auth -> Generator [FileDraft] +genAuthGitHub auth = + if AS.Auth.isGitHubAuthEnabled auth + then sequence [genFileCopy [relfile|client/auth/github.ts|]] + else return [] + +getAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value +getAuthProvidersJson auth = + object + [ "isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth, + "isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth, + "isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth, + "isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth + ] + +genFileCopy :: Path' (Rel SdkTemplatesDir) File' -> Generator FileDraft +genFileCopy = return . C.mkTmplFd diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index d560ef184d..c617e90d6b 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -308,6 +308,7 @@ library Wasp.Generator.SdkGenerator.EmailSender.Providers Wasp.Generator.SdkGenerator.WebSocketGenerator Wasp.Generator.SdkGenerator.RouterGenerator + Wasp.Generator.SdkGenerator.Client.AuthG Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG From 7a21dd740ecb4b48bac45d93d188377ff79e48df Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Tue, 30 Jan 2024 15:34:37 +0100 Subject: [PATCH 06/19] Adds wasp/server/auth API (#1694) --- .../Generator/templates/sdk/auth/index.ts | 3 +- .../Generator/templates/sdk/auth/password.ts | 2 + .../templates/sdk/auth/providers/types.ts | 14 +++- .../Generator/templates/sdk/auth/session.ts | 4 + .../Generator/templates/sdk/auth/utils.ts | 21 +++++ .../templates/sdk/auth/validation.ts | 6 ++ .../data/Generator/templates/sdk/package.json | 9 +- .../templates/sdk/server/auth/email/index.ts | 11 +++ .../templates/sdk/server/auth/email/utils.ts | 5 ++ .../templates/sdk/server/auth/index.ts | 27 ++++++ .../templates/sdk/server/auth/username.ts | 1 + .../.wasp/out/sdk/wasp/auth/helpers/user.ts | 1 + .../.wasp/out/sdk/wasp/auth/logout.ts | 1 + .../out/sdk/wasp/auth/providers/types.ts | 46 ++++++++++ .../.wasp/out/sdk/wasp/auth/useAuth.ts | 2 + .../.wasp/out/sdk/wasp/auth/user.ts | 4 + .../.wasp/out/sdk/wasp/auth/utils.ts | 21 +++++ .../.wasp/out/sdk/wasp/auth/validation.ts | 83 +++++++++++++++++++ .../.wasp/out/sdk/wasp/package.json | 3 +- .../todo-typescript/src/task/queries.ts | 9 +- .../src/user/customEmailSending.ts | 2 +- .../src/user/userSignupFields.ts | 18 ++-- waspc/src/Wasp/Generator/SdkGenerator.hs | 2 + .../Generator/SdkGenerator/Auth/EmailAuthG.hs | 7 -- .../Generator/SdkGenerator/Server/AuthG.hs | 63 ++++++++++++++ waspc/waspc.cabal | 1 + 26 files changed, 333 insertions(+), 33 deletions(-) create mode 100644 waspc/data/Generator/templates/sdk/server/auth/index.ts create mode 100644 waspc/data/Generator/templates/sdk/server/auth/username.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts create mode 100644 waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs diff --git a/waspc/data/Generator/templates/sdk/auth/index.ts b/waspc/data/Generator/templates/sdk/auth/index.ts index 83cfcc27c3..4b79c6ad5e 100644 --- a/waspc/data/Generator/templates/sdk/auth/index.ts +++ b/waspc/data/Generator/templates/sdk/auth/index.ts @@ -1,5 +1,4 @@ -export { defineUserSignupFields } from './providers/types.js'; - +// PUBLIC export type { AuthUser } from '../server/_types' export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js' diff --git a/waspc/data/Generator/templates/sdk/auth/password.ts b/waspc/data/Generator/templates/sdk/auth/password.ts index a359892b5e..9bccc80ad7 100644 --- a/waspc/data/Generator/templates/sdk/auth/password.ts +++ b/waspc/data/Generator/templates/sdk/auth/password.ts @@ -2,11 +2,13 @@ import SecurePassword from 'secure-password' const SP = new SecurePassword() +// PRIVATE API export const hashPassword = async (password: string): Promise => { const hashedPwdBuffer = await SP.hash(Buffer.from(password)) return hashedPwdBuffer.toString("base64") } +// PRIVATE API export const verifyPassword = async (hashedPassword: string, password: string): Promise => { const result = await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64")) if (result !== SecurePassword.VALID) { diff --git a/waspc/data/Generator/templates/sdk/auth/providers/types.ts b/waspc/data/Generator/templates/sdk/auth/providers/types.ts index f040c1c490..7e1ca77667 100644 --- a/waspc/data/Generator/templates/sdk/auth/providers/types.ts +++ b/waspc/data/Generator/templates/sdk/auth/providers/types.ts @@ -4,8 +4,14 @@ import type { Prisma } from '@prisma/client' import type { Expand } from 'wasp/universal/types' import type { ProviderName } from '../utils' +// PUBLIC API +export function defineUserSignupFields(fields: UserSignupFields) { + return fields +} + type UserEntityCreateInput = Prisma.{= userEntityUpper =}CreateInput +// PRIVATE API export type ProviderConfig = { // Unique provider identifier, used as part of URL paths id: ProviderName; @@ -18,14 +24,18 @@ export type ProviderConfig = { createRouter(provider: ProviderConfig, initData: InitData): Router; }; +// PRIVATE API export type InitData = { [key: string]: any; } +// PRIVATE API export type RequestWithWasp = Request & { wasp?: { [key: string]: any } } +// PRIVATE API export type PossibleUserFields = Expand> +// PRIVATE API export type UserSignupFields = { [key in keyof PossibleUserFields]: FieldGetter< PossibleUserFields[key] @@ -35,7 +45,3 @@ export type UserSignupFields = { type FieldGetter = ( data: { [key: string]: unknown } ) => Promise | T | undefined - -export function defineUserSignupFields(fields: UserSignupFields) { - return fields -} diff --git a/waspc/data/Generator/templates/sdk/auth/session.ts b/waspc/data/Generator/templates/sdk/auth/session.ts index a077b41a05..8c2d5e242d 100644 --- a/waspc/data/Generator/templates/sdk/auth/session.ts +++ b/waspc/data/Generator/templates/sdk/auth/session.ts @@ -13,11 +13,13 @@ import { import { prisma } from 'wasp/server'; +// PRIVATE API // Creates a new session for the `authId` in the database export async function createSession(authId: string): Promise { return auth.createSession(authId, {}); } +// PRIVATE API export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<{ user: AuthUser | null, session: Session | null, @@ -42,6 +44,7 @@ export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Pro return getSessionAndUserFromSessionId(sessionId); } +// PRIVATE API export async function getSessionAndUserFromSessionId(sessionId: string): Promise<{ user: AuthUser | null, session: Session | null, @@ -103,6 +106,7 @@ async function getUser(userId: {= userEntityUpper =}['id']): Promise { } } +// PRIVATE API export function invalidateSession(sessionId: string): Promise { return auth.invalidateSession(sessionId); } diff --git a/waspc/data/Generator/templates/sdk/auth/utils.ts b/waspc/data/Generator/templates/sdk/auth/utils.ts index ca9f60628f..fd3c87feeb 100644 --- a/waspc/data/Generator/templates/sdk/auth/utils.ts +++ b/waspc/data/Generator/templates/sdk/auth/utils.ts @@ -16,6 +16,7 @@ import { throwValidationError } from './validation.js' import { type UserSignupFields, type PossibleUserFields } from './providers/types.js' +// PRIVATE API export type EmailProviderData = { hashedPassword: string; isEmailVerified: boolean; @@ -23,12 +24,15 @@ export type EmailProviderData = { passwordResetSentAt: string | null; } +// PRIVATE API export type UsernameProviderData = { hashedPassword: string; } +// PRIVATE API export type OAuthProviderData = {} +// PRIVATE API /** * This type is used for type-level programming e.g. to enumerate * all possible provider data types. @@ -43,19 +47,23 @@ export type PossibleProviderData = { github: OAuthProviderData; } +// PRIVATE API export type ProviderName = keyof PossibleProviderData +// PRIVATE API export const contextWithUserEntity = { entities: { {= userEntityUpper =}: prisma.{= userEntityLower =} } } +// PRIVATE API export const authConfig = { failureRedirectPath: "{= failureRedirectPath =}", successRedirectPath: "{= successRedirectPath =}", } +// PRIVATE API /** * ProviderId uniquely identifies an auth identity e.g. * "email" provider with user id "test@test.com" or @@ -70,6 +78,7 @@ export type ProviderId = { providerUserId: string; } +// PUBLIC API export function createProviderId(providerName: ProviderName, providerUserId: string): ProviderId { return { providerName, @@ -77,6 +86,7 @@ export function createProviderId(providerName: ProviderName, providerUserId: str } } +// PUBLIC API export async function findAuthIdentity(providerId: ProviderId): Promise<{= authIdentityEntityUpper =} | null> { return prisma.{= authIdentityEntityLower =}.findUnique({ where: { @@ -85,6 +95,7 @@ export async function findAuthIdentity(providerId: ProviderId): Promise<{= authI }); } +// PUBLIC API /** * Updates the provider data for the given auth identity. * @@ -118,12 +129,14 @@ type FindAuthWithUserResult = {= authEntityUpper =} & { {= userFieldOnAuthEntityName =}: {= userEntityUpper =} } +// PRIVATE API export async function findAuthWithUserBy( where: Prisma.{= authEntityUpper =}WhereInput ): Promise { return prisma.{= authEntityLower =}.findFirst({ where, include: { {= userFieldOnAuthEntityName =}: true }}); } +// PUBLIC API export async function createUser( providerId: ProviderId, serializedProviderData?: string, @@ -156,16 +169,19 @@ export async function createUser( }) } +// PRIVATE API export async function deleteUserByAuthId(authId: string): Promise<{ count: number }> { return prisma.{= userEntityLower =}.deleteMany({ where: { auth: { id: authId, } } }) } +// PRIVATE API export async function verifyToken(token: string): Promise { return verify(token); } +// PRIVATE API // If an user exists, we don't want to leak information // about it. Pretending that we're doing some work // will make it harder for an attacker to determine @@ -178,6 +194,7 @@ export async function doFakeWork(): Promise { return sleep(timeToWork); } +// PRIVATE API export function rethrowPossibleAuthError(e: unknown): void { if (e instanceof AuthError) { throwValidationError(e.message); @@ -225,6 +242,7 @@ export function rethrowPossibleAuthError(e: unknown): void { throw e } +// PRIVATE API export async function validateAndGetUserFields( data: { [key: string]: unknown @@ -252,6 +270,7 @@ export async function validateAndGetUserFields( return result; } +// PUBLIC API export function deserializeAndSanitizeProviderData( providerData: string, { shouldRemovePasswordField = false }: { shouldRemovePasswordField?: boolean } = {}, @@ -266,6 +285,7 @@ export function deserializeAndSanitizeProviderData( return data; } +// PUBLIC API export async function sanitizeAndSerializeProviderData( providerData: PossibleProviderData[PN], ): Promise { @@ -298,6 +318,7 @@ function providerDataHasPasswordField( return 'hashedPassword' in providerData; } +// PRIVATE API export function throwInvalidCredentialsError(message?: string): void { throw new HttpError(401, 'Invalid credentials', { message }) } diff --git a/waspc/data/Generator/templates/sdk/auth/validation.ts b/waspc/data/Generator/templates/sdk/auth/validation.ts index 73bac13e21..d7cda4e29f 100644 --- a/waspc/data/Generator/templates/sdk/auth/validation.ts +++ b/waspc/data/Generator/templates/sdk/auth/validation.ts @@ -5,6 +5,7 @@ const USERNAME_FIELD = 'username'; const EMAIL_FIELD = 'email'; const TOKEN_FIELD = 'token'; +// PUBLIC API export function ensureValidEmail(args: unknown): void { validate(args, [ { validates: EMAIL_FIELD, message: 'email must be present', validator: email => !!email }, @@ -12,18 +13,21 @@ export function ensureValidEmail(args: unknown): void { ]); } +// PUBLIC API export function ensureValidUsername(args: unknown): void { validate(args, [ { validates: USERNAME_FIELD, message: 'username must be present', validator: username => !!username } ]); } +// PUBLIC API export function ensurePasswordIsPresent(args: unknown): void { validate(args, [ { validates: PASSWORD_FIELD, message: 'password must be present', validator: password => !!password }, ]); } +// PUBLIC API export function ensureValidPassword(args: unknown): void { validate(args, [ { validates: PASSWORD_FIELD, message: 'password must be at least 8 characters', validator: password => isMinLength(password, 8) }, @@ -31,12 +35,14 @@ export function ensureValidPassword(args: unknown): void { ]); } +// PUBLIC API export function ensureTokenIsPresent(args: unknown): void { validate(args, [ { validates: TOKEN_FIELD, message: 'token must be present', validator: token => !!token }, ]); } +// PRIVATE API export function throwValidationError(message: string): void { throw new HttpError(422, 'Validation failed', { message }) } diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index f5c02c6afe..fb9c10b74c 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -43,7 +43,7 @@ "./auth/session": "./dist/auth/session.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/providers/types": "./dist/auth/providers/types.js", - {=! Used by user, documented. =} + {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/utils": "./dist/auth/utils.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/password": "./dist/auth/password.js", @@ -78,7 +78,7 @@ "./server/actions": "./dist/server/actions/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/queries": "./dist/server/queries/index.js", - {=! Used by users and also by our code, documented. =} + {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email": "./dist/server/auth/email/index.js", {=! Used by users, documented. =} "./dbSeed/types": "./dist/dbSeed/types.js", @@ -92,7 +92,7 @@ "./email": "./dist/email/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./email/core/types": "./dist/email/core/types.js", - {=! Used by users, documented. =} + {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email/utils": "./dist/server/auth/email/utils.js", {=! Parts are used by users and documented (types), other parts are used by the framework code (entities). =} "./jobs/*": "./dist/jobs/*.js", @@ -122,7 +122,8 @@ {=! Private: [sdk] =} "./client/api": "./dist/api/index.js", "./auth": "./dist/auth/index.js", - "./client/auth": "./dist/client/auth/index.js" + "./client/auth": "./dist/client/auth/index.js", + "./server/auth": "./dist/server/auth/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/data/Generator/templates/sdk/server/auth/email/index.ts b/waspc/data/Generator/templates/sdk/server/auth/email/index.ts index a37dca08d4..da974e91b5 100644 --- a/waspc/data/Generator/templates/sdk/server/auth/email/index.ts +++ b/waspc/data/Generator/templates/sdk/server/auth/email/index.ts @@ -1,5 +1,7 @@ +// PUBLIC API export type GetVerificationEmailContentFn = (params: { verificationLink: string }) => EmailContent; +// PUBLIC API export type GetPasswordResetEmailContentFn = (params: { passwordResetLink: string }) => EmailContent; type EmailContent = { @@ -7,3 +9,12 @@ type EmailContent = { html: string; text: string; } + +export { + createEmailVerificationLink, + sendEmailVerificationEmail, + createPasswordResetLink, + sendPasswordResetEmail, + isEmailResendAllowed, +} from './utils.js' +export { ensureValidEmail } from '../../../auth/validation.js' diff --git a/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts index 005adfdd67..1270589940 100644 --- a/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts +++ b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts @@ -12,6 +12,7 @@ import { import { config as waspServerConfig } from 'wasp/server'; import { type {= userEntityUpper =}, type {= authEntityUpper =} } from 'wasp/entities' +// PUBLIC API export async function createEmailVerificationLink( email: string, clientRoute: string, @@ -20,6 +21,7 @@ export async function createEmailVerificationLink( return `${waspServerConfig.frontendUrl}${clientRoute}?token=${jwtToken}`; } +// PUBLIC API export async function createPasswordResetLink( email: string, clientRoute: string, @@ -33,6 +35,7 @@ async function createEmailJwtToken(email: string): Promise<{ jwtToken: string; } return { jwtToken }; } +// PUBLIC API export async function sendPasswordResetEmail( email: string, content: Email, @@ -42,6 +45,7 @@ export async function sendPasswordResetEmail( }); } +// PUBLIC API export async function sendEmailVerificationEmail( email: string, content: Email, @@ -68,6 +72,7 @@ async function sendEmailAndSaveMetadata( }); } +// PUBLIC API export function isEmailResendAllowed( fields: { [field in Field]: string | null diff --git a/waspc/data/Generator/templates/sdk/server/auth/index.ts b/waspc/data/Generator/templates/sdk/server/auth/index.ts new file mode 100644 index 0000000000..6f7d96138e --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/auth/index.ts @@ -0,0 +1,27 @@ +{{={= =}=}} +export { + defineUserSignupFields, +} from '../../auth/providers/types.js' + +export { + createProviderId, + sanitizeAndSerializeProviderData, + updateAuthIdentityProviderData, + deserializeAndSanitizeProviderData, + findAuthIdentity, + createUser, +} from '../../auth/utils.js' + +export { + ensurePasswordIsPresent, + ensureValidPassword, + ensureTokenIsPresent, +} from '../../auth/validation.js' + +{=# isEmailAuthEnabled =} +export * from './email/index.js' +{=/ isEmailAuthEnabled =} + +{=# isUsernameAndPasswordAuthEnabled =} +export * from './username.js' +{=/ isUsernameAndPasswordAuthEnabled =} diff --git a/waspc/data/Generator/templates/sdk/server/auth/username.ts b/waspc/data/Generator/templates/sdk/server/auth/username.ts new file mode 100644 index 0000000000..d39feb89bf --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/auth/username.ts @@ -0,0 +1 @@ +export { ensureValidUsername } from '../../auth/validation.js' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts index 259a4c34b5..050dd3f32a 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts @@ -1,6 +1,7 @@ import { setSessionId } from 'wasp/client/api' import { invalidateAndRemoveQueries } from 'wasp/operations/resources' +// PRIVATE API export async function initSession(sessionId: string): Promise { setSessionId(sessionId) // We need to invalidate queries after login in order to get the correct user diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts index 7f40b0cbf6..4a51817560 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts @@ -1,6 +1,7 @@ import { api, removeLocalUserData } from 'wasp/client/api' import { invalidateAndRemoveQueries } from 'wasp/operations/resources' +// PUBLIC API export default async function logout(): Promise { try { await api.post('/auth/logout') diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts new file mode 100644 index 0000000000..8cd06b8afc --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts @@ -0,0 +1,46 @@ +import type { Router, Request } from 'express' +import type { Prisma } from '@prisma/client' +import type { Expand } from 'wasp/universal/types' +import type { ProviderName } from '../utils' + +// PUBLIC API +export function defineUserSignupFields(fields: UserSignupFields) { + return fields +} + +type UserEntityCreateInput = Prisma.UserCreateInput + +// PRIVATE API +export type ProviderConfig = { + // Unique provider identifier, used as part of URL paths + id: ProviderName; + displayName: string; + // Each provider config can have an init method which is ran on setup time + // e.g. for oAuth providers this is the time when the Passport strategy is registered. + init?(provider: ProviderConfig): Promise; + // Every provider must have a setupRouter method which returns the Express router. + // In this function we are flexibile to do what ever is necessary to make the provider work. + createRouter(provider: ProviderConfig, initData: InitData): Router; +}; + +// PRIVATE API +export type InitData = { + [key: string]: any; +} + +// PRIVATE API +export type RequestWithWasp = Request & { wasp?: { [key: string]: any } } + +// PRIVATE API +export type PossibleUserFields = Expand> + +// PRIVATE API +export type UserSignupFields = { + [key in keyof PossibleUserFields]: FieldGetter< + PossibleUserFields[key] + > +} + +type FieldGetter = ( + data: { [key: string]: unknown } +) => Promise | T | undefined diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts index 47a6293879..cd7630ade6 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts @@ -5,8 +5,10 @@ import { HttpMethod } from 'wasp/types' import type { AuthUser } from './types' import { addMetadataToQuery } from 'wasp/rpc/queries' +// PUBLIC API export const getMe = createUserGetter() +// PUBLIC API export default function useAuth(queryFnArgs?: unknown, config?: any) { return useQuery(getMe, queryFnArgs, config) } diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts index 0de50de6d0..f9bc6d39a3 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts @@ -1,13 +1,16 @@ import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types' +// PUBLIC API export function getEmail(user: AuthUser): string | null { return findUserIdentity(user, "email")?.providerUserId ?? null; } +// PUBLIC API export function getUsername(user: AuthUser): string | null { return findUserIdentity(user, "username")?.providerUserId ?? null; } +// PUBLIC API export function getFirstProviderUserId(user?: AuthUser): string | null { if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { return null; @@ -16,6 +19,7 @@ export function getFirstProviderUserId(user?: AuthUser): string | null { return user.auth.identities[0].providerUserId ?? null; } +// PUBLIC API export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined { return user.auth.identities.find( (identity) => identity.providerName === providerName diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts index 4f08b8d552..fc6847cb59 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts @@ -15,6 +15,7 @@ import { throwValidationError } from './validation.js' import { type UserSignupFields, type PossibleUserFields } from './providers/types.js' +// PRIVATE API export type EmailProviderData = { hashedPassword: string; isEmailVerified: boolean; @@ -22,12 +23,15 @@ export type EmailProviderData = { passwordResetSentAt: string | null; } +// PRIVATE API export type UsernameProviderData = { hashedPassword: string; } +// PRIVATE API export type OAuthProviderData = {} +// PRIVATE API /** * This type is used for type-level programming e.g. to enumerate * all possible provider data types. @@ -42,19 +46,23 @@ export type PossibleProviderData = { github: OAuthProviderData; } +// PRIVATE API export type ProviderName = keyof PossibleProviderData +// PRIVATE API export const contextWithUserEntity = { entities: { User: prisma.user } } +// PRIVATE API export const authConfig = { failureRedirectPath: "/login", successRedirectPath: "/", } +// PRIVATE API /** * ProviderId uniquely identifies an auth identity e.g. * "email" provider with user id "test@test.com" or @@ -69,6 +77,7 @@ export type ProviderId = { providerUserId: string; } +// PUBLIC API export function createProviderId(providerName: ProviderName, providerUserId: string): ProviderId { return { providerName, @@ -76,6 +85,7 @@ export function createProviderId(providerName: ProviderName, providerUserId: str } } +// PUBLIC API export async function findAuthIdentity(providerId: ProviderId): Promise { return prisma.authIdentity.findUnique({ where: { @@ -84,6 +94,7 @@ export async function findAuthIdentity(providerId: ProviderId): Promise { return prisma.auth.findFirst({ where, include: { user: true }}); } +// PUBLIC API export async function createUser( providerId: ProviderId, serializedProviderData?: string, @@ -155,16 +168,19 @@ export async function createUser( }) } +// PRIVATE API export async function deleteUserByAuthId(authId: string): Promise<{ count: number }> { return prisma.user.deleteMany({ where: { auth: { id: authId, } } }) } +// PRIVATE API export async function verifyToken(token: string): Promise { return verify(token); } +// PRIVATE API // If an user exists, we don't want to leak information // about it. Pretending that we're doing some work // will make it harder for an attacker to determine @@ -177,6 +193,7 @@ export async function doFakeWork(): Promise { return sleep(timeToWork); } +// PRIVATE API export function rethrowPossibleAuthError(e: unknown): void { if (e instanceof AuthError) { throwValidationError(e.message); @@ -224,6 +241,7 @@ export function rethrowPossibleAuthError(e: unknown): void { throw e } +// PRIVATE API export async function validateAndGetUserFields( data: { [key: string]: unknown @@ -251,6 +269,7 @@ export async function validateAndGetUserFields( return result; } +// PUBLIC API export function deserializeAndSanitizeProviderData( providerData: string, { shouldRemovePasswordField = false }: { shouldRemovePasswordField?: boolean } = {}, @@ -265,6 +284,7 @@ export function deserializeAndSanitizeProviderData( return data; } +// PUBLIC API export async function sanitizeAndSerializeProviderData( providerData: PossibleProviderData[PN], ): Promise { @@ -297,6 +317,7 @@ function providerDataHasPasswordField( return 'hashedPassword' in providerData; } +// PRIVATE API export function throwInvalidCredentialsError(message?: string): void { throw new HttpError(401, 'Invalid credentials', { message }) } diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts new file mode 100644 index 0000000000..d7cda4e29f --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts @@ -0,0 +1,83 @@ +import HttpError from 'wasp/core/HttpError'; + +export const PASSWORD_FIELD = 'password'; +const USERNAME_FIELD = 'username'; +const EMAIL_FIELD = 'email'; +const TOKEN_FIELD = 'token'; + +// PUBLIC API +export function ensureValidEmail(args: unknown): void { + validate(args, [ + { validates: EMAIL_FIELD, message: 'email must be present', validator: email => !!email }, + { validates: EMAIL_FIELD, message: 'email must be a valid email', validator: email => isValidEmail(email) }, + ]); +} + +// PUBLIC API +export function ensureValidUsername(args: unknown): void { + validate(args, [ + { validates: USERNAME_FIELD, message: 'username must be present', validator: username => !!username } + ]); +} + +// PUBLIC API +export function ensurePasswordIsPresent(args: unknown): void { + validate(args, [ + { validates: PASSWORD_FIELD, message: 'password must be present', validator: password => !!password }, + ]); +} + +// PUBLIC API +export function ensureValidPassword(args: unknown): void { + validate(args, [ + { validates: PASSWORD_FIELD, message: 'password must be at least 8 characters', validator: password => isMinLength(password, 8) }, + { validates: PASSWORD_FIELD, message: 'password must contain a number', validator: password => containsNumber(password) }, + ]); +} + +// PUBLIC API +export function ensureTokenIsPresent(args: unknown): void { + validate(args, [ + { validates: TOKEN_FIELD, message: 'token must be present', validator: token => !!token }, + ]); +} + +// PRIVATE API +export function throwValidationError(message: string): void { + throw new HttpError(422, 'Validation failed', { message }) +} + +function validate(args: unknown, validators: { validates: string, message: string, validator: (value: unknown) => boolean }[]): void { + for (const { validates, message, validator } of validators) { + if (!validator(args[validates])) { + throwValidationError(message); + } + } +} + +// NOTE(miho): it would be good to replace our custom validations with e.g. Zod + +const validEmailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ +function isValidEmail(input: unknown): boolean { + if (typeof input !== 'string') { + return false + } + + return input.match(validEmailRegex) !== null +} + +function isMinLength(input: unknown, minLength: number): boolean { + if (typeof input !== 'string') { + return false + } + + return input.length >= minLength +} + +function containsNumber(input: unknown): boolean { + if (typeof input !== 'string') { + return false + } + + return /\d/.test(input) +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json index 3545ebabf7..5d085ed28a 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -62,7 +62,8 @@ "./server/api": "./dist/server/api/index.js", "./client/api": "./dist/api/index.js", "./auth": "./dist/auth/index.js", - "./client/auth": "./dist/client/auth/index.js" + "./client/auth": "./dist/client/auth/index.js", + "./server/auth": "./dist/server/auth/index.js" }, "typesVersions": { "*": { diff --git a/waspc/examples/todo-typescript/src/task/queries.ts b/waspc/examples/todo-typescript/src/task/queries.ts index e14f410f7b..fbc2e858bd 100644 --- a/waspc/examples/todo-typescript/src/task/queries.ts +++ b/waspc/examples/todo-typescript/src/task/queries.ts @@ -2,8 +2,7 @@ import HttpError from 'wasp/core/HttpError' import AuthError from 'wasp/core/AuthError' import type { GetTasks } from 'wasp/server/queries/types' import type { Task } from 'wasp/entities' -import { ensureValidEmail } from 'wasp/auth/validation' -import { createProviderId } from 'wasp/auth/utils' +import { ensureValidEmail, createProviderId } from 'wasp/server/auth' //Using TypeScript's new 'satisfies' keyword, it will infer the types of the arguments and return value export const getTasks = ((_args, context) => { @@ -11,10 +10,10 @@ export const getTasks = ((_args, context) => { throw new HttpError(401) } - console.log(AuthError); + console.log(AuthError) - console.log(createProviderId); - ensureValidEmail({ email: "wasp@gmail.com"}); + console.log(createProviderId) + ensureValidEmail({ email: 'wasp@gmail.com' }) return context.entities.Task.findMany({ where: { user: { id: context.user.id } }, diff --git a/waspc/examples/todo-typescript/src/user/customEmailSending.ts b/waspc/examples/todo-typescript/src/user/customEmailSending.ts index 222031c34c..f4908636fc 100644 --- a/waspc/examples/todo-typescript/src/user/customEmailSending.ts +++ b/waspc/examples/todo-typescript/src/user/customEmailSending.ts @@ -3,7 +3,7 @@ import { createEmailVerificationLink, sendEmailVerificationEmail, sendPasswordResetEmail, -} from 'wasp/server/auth/email/utils' +} from 'wasp/server/auth' export async function send() { const userEmail = 'mihovil@ilakovac.com' diff --git a/waspc/examples/todo-typescript/src/user/userSignupFields.ts b/waspc/examples/todo-typescript/src/user/userSignupFields.ts index 7c40562cc5..5f2b6402d4 100644 --- a/waspc/examples/todo-typescript/src/user/userSignupFields.ts +++ b/waspc/examples/todo-typescript/src/user/userSignupFields.ts @@ -1,18 +1,18 @@ -import { defineUserSignupFields } from "wasp/auth"; +import { defineUserSignupFields } from 'wasp/server/auth' export const googleUserSignupFields = defineUserSignupFields({ - address: () => "Placeholder address", -}); + address: () => 'Placeholder address', +}) export const githubUserSignupFields = defineUserSignupFields({ - address: () => "Placeholder address", -}); + address: () => 'Placeholder address', +}) export const userSignupFields = defineUserSignupFields({ address: (data) => { - if (typeof data.address !== "string") { - throw new Error("Address must be provided on signup."); + if (typeof data.address !== 'string') { + throw new Error('Address must be provided on signup.') } - return data.address; + return data.address }, -}); +}) diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index e3f168a9db..ebd436b496 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -44,6 +44,7 @@ import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSe import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) +import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) @@ -115,6 +116,7 @@ genSdkReal spec = <++> genEmailSender spec -- New API <++> genNewClientAuth spec + <++> genNewServerApi spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs index 9e205cf22b..70c09a1e3f 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs @@ -30,7 +30,6 @@ genEmailAuth auth genServerUtils auth ] <++> genActions - <++> genServer | otherwise = return [] genIndex :: Generator FileDraft @@ -45,12 +44,6 @@ genActions = genVerifyEmailAction ] -genServer :: Generator [FileDraft] -genServer = - return - [ C.mkTmplFd [relfile|server/auth/email/index.ts|] - ] - genLoginAction :: Generator FileDraft genLoginAction = return $ diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs new file mode 100644 index 0000000000..42556a964a --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/AuthG.hs @@ -0,0 +1,63 @@ +module Wasp.Generator.SdkGenerator.Server.AuthG + ( genNewServerApi, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import StrongPath (File', Path', Rel, relfile) +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.Auth +import Wasp.AppSpec.Valid (getApp) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Util ((<++>)) + +genNewServerApi :: AppSpec -> Generator [FileDraft] +genNewServerApi spec = + case maybeAuth of + Nothing -> return [] + Just auth -> + sequence + [ genAuthIndex auth + ] + <++> genAuthEmail auth + <++> genAuthUsername auth + where + maybeAuth = AS.App.auth $ snd $ getApp spec + +genAuthIndex :: AS.Auth.Auth -> Generator FileDraft +genAuthIndex auth = + return $ + C.mkTmplFdWithData + [relfile|server/auth/index.ts|] + tmplData + where + tmplData = getAuthProvidersJson auth + +genAuthEmail :: AS.Auth.Auth -> Generator [FileDraft] +genAuthEmail auth = + if AS.Auth.isEmailAuthEnabled auth + then sequence [genFileCopy [relfile|server/auth/email/index.ts|]] + else return [] + +genAuthUsername :: AS.Auth.Auth -> Generator [FileDraft] +genAuthUsername auth = + if AS.Auth.isUsernameAndPasswordAuthEnabled auth + then sequence [genFileCopy [relfile|server/auth/username.ts|]] + else return [] + +getAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value +getAuthProvidersJson auth = + object + [ "isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth, + "isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth, + "isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth, + "isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth + ] + +genFileCopy :: Path' (Rel SdkTemplatesDir) File' -> Generator FileDraft +genFileCopy = return . C.mkTmplFd diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index c617e90d6b..5054e433f7 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -309,6 +309,7 @@ library Wasp.Generator.SdkGenerator.WebSocketGenerator Wasp.Generator.SdkGenerator.RouterGenerator Wasp.Generator.SdkGenerator.Client.AuthG + Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG From c03444d6522a4201b9eed32d99956e0c11166222 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Tue, 30 Jan 2024 15:37:41 +0100 Subject: [PATCH 07/19] Implement the new `wasp/server/crud` API (#1695) --- .../data/Generator/templates/sdk/package.json | 3 +- .../sdk/server/crud/_operationTypes.ts | 51 +++-- .../templates/sdk/server/crud/index.ts | 5 + .../.wasp/out/sdk/wasp/auth/forms/Auth.tsx | 96 +++++++++ .../.wasp/out/sdk/wasp/auth/forms/Login.tsx | 18 ++ .../.wasp/out/sdk/wasp/auth/forms/Signup.tsx | 24 +++ .../out/sdk/wasp/auth/forms/internal/Form.tsx | 102 ++++++++++ .../sdk/wasp/auth/forms/internal/Message.tsx | 21 ++ .../forms/internal/common/LoginSignupForm.tsx | 184 ++++++++++++++++++ .../.wasp/out/sdk/wasp/auth/forms/types.ts | 49 +++++ .../.wasp/out/sdk/wasp/package.json | 3 +- .../examples/todo-typescript/src/task/crud.ts | 8 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 2 + .../Generator/SdkGenerator/Server/CrudG.hs | 33 ++++ waspc/waspc.cabal | 1 + 15 files changed, 579 insertions(+), 21 deletions(-) create mode 100644 waspc/data/Generator/templates/sdk/server/crud/index.ts create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx create mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts create mode 100644 waspc/src/Wasp/Generator/SdkGenerator/Server/CrudG.hs diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index fb9c10b74c..e785457aba 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -123,7 +123,8 @@ "./client/api": "./dist/api/index.js", "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", - "./server/auth": "./dist/server/auth/index.js" + "./server/auth": "./dist/server/auth/index.js", + "./server/crud": "./dist/server/crud/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts b/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts index 6fd34aea45..a5be3247ab 100644 --- a/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts +++ b/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts @@ -34,13 +34,42 @@ import type { type _WaspEntityTagged = _{= crud.entityUpper =} type _WaspEntity = {= crud.entityUpper =} +/** + * PUBLIC API + */ +export namespace {= crud.name =} { + {=# crud.operations.GetAll =} + export type GetAllQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> + {=/ crud.operations.GetAll =} + + {=# crud.operations.Get =} + export type GetQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> + {=/ crud.operations.Get =} + + {=# crud.operations.Create =} + export type CreateAction= {= actionType =}<[_WaspEntityTagged], Input, Output> + {=/ crud.operations.Create =} + + {=# crud.operations.Update =} + export type UpdateAction = {= actionType =}<[_WaspEntityTagged], Input, Output> + {=/ crud.operations.Update =} + + {=# crud.operations.Delete =} + export type DeleteAction = {= actionType =}<[_WaspEntityTagged], Input, Output> + {=/ crud.operations.Delete =} +} + +/** + * PRIVATE API + * + * The types with the `Resolved` suffix are the types that are used internally by the Wasp client + * to implement full-stack type safety. + */ {=# crud.operations.GetAll =} -// Get All query -export type GetAllQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.GetAll.isDefined =} type GetAllInput = {} type GetAllOutput = _WaspEntity[] -export type GetAllQueryResolved = GetAllQuery +export type GetAllQueryResolved = {= crud.name =}.GetAllQuery {=/ overrides.GetAll.isDefined =} {=# overrides.GetAll.isDefined =} const _waspGetAllQuery = {= overrides.GetAll.importIdentifier =} @@ -49,12 +78,10 @@ export type GetAllQueryResolved = typeof _waspGetAllQuery {=/ crud.operations.GetAll =} {=# crud.operations.Get =} -// Get query -export type GetQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Get.isDefined =} type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type GetOutput = _WaspEntity | null -export type GetQueryResolved = GetQuery +export type GetQueryResolved = {= crud.name =}.GetQuery {=/ overrides.Get.isDefined =} {=# overrides.Get.isDefined =} const _waspGetQuery = {= overrides.Get.importIdentifier =} @@ -63,12 +90,10 @@ export type GetQueryResolved = typeof _waspGetQuery {=/ crud.operations.Get =} {=# crud.operations.Create =} -// Create action -export type CreateAction= {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Create.isDefined =} type CreateInput = Prisma.{= crud.entityUpper =}CreateInput type CreateOutput = _WaspEntity -export type CreateActionResolved = CreateAction +export type CreateActionResolved = {= crud.name =}.CreateAction {=/ overrides.Create.isDefined =} {=# overrides.Create.isDefined =} const _waspCreateAction = {= overrides.Create.importIdentifier =} @@ -77,12 +102,10 @@ export type CreateActionResolved = typeof _waspCreateAction {=/ crud.operations.Create =} {=# crud.operations.Update =} -// Update action -export type UpdateAction = {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Update.isDefined =} type UpdateInput = Prisma.{= crud.entityUpper =}UpdateInput & Prisma.{= crud.entityUpper =}WhereUniqueInput type UpdateOutput = _WaspEntity -export type UpdateActionResolved = UpdateAction +export type UpdateActionResolved = {= crud.name =}.UpdateAction {=/ overrides.Update.isDefined =} {=# overrides.Update.isDefined =} const _waspUpdateAction = {= overrides.Update.importIdentifier =} @@ -91,12 +114,10 @@ export type UpdateActionResolved = typeof _waspUpdateAction {=/ crud.operations.Update =} {=# crud.operations.Delete =} -// Delete action -export type DeleteAction = {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Delete.isDefined =} type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type DeleteOutput = _WaspEntity -export type DeleteActionResolved = DeleteAction +export type DeleteActionResolved = {= crud.name =}.DeleteAction {=/ overrides.Delete.isDefined =} {=# overrides.Delete.isDefined =} const _waspDeleteAction = {= overrides.Delete.importIdentifier =} diff --git a/waspc/data/Generator/templates/sdk/server/crud/index.ts b/waspc/data/Generator/templates/sdk/server/crud/index.ts new file mode 100644 index 0000000000..4404d90d2a --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/crud/index.ts @@ -0,0 +1,5 @@ +{{={= =}=}} + +{=# cruds =} + export type { {= name =} } from './{= name =}'; +{=/ cruds =} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx new file mode 100644 index 0000000000..c612e33865 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx @@ -0,0 +1,96 @@ +import { useState, createContext } from 'react' +import { createTheme } from '@stitches/react' +import { styled } from 'wasp/core/stitches.config' + +import { + type State, + type CustomizationOptions, + type ErrorMessage, + type AdditionalSignupFields, +} from './types' +import { LoginSignupForm } from './internal/common/LoginSignupForm' +import { MessageError, MessageSuccess } from './internal/Message' +import { ForgotPasswordForm } from './internal/email/ForgotPasswordForm' +import { ResetPasswordForm } from './internal/email/ResetPasswordForm' +import { VerifyEmailForm } from './internal/email/VerifyEmailForm' + +const logoStyle = { + height: '3rem' +} + +const Container = styled('div', { + display: 'flex', + flexDirection: 'column', +}) + +const HeaderText = styled('h2', { + fontSize: '1.875rem', + fontWeight: '700', + marginTop: '1.5rem' +}) + + +// PRIVATE API +export const AuthContext = createContext({ + isLoading: false, + setIsLoading: (isLoading: boolean) => {}, + setErrorMessage: (errorMessage: ErrorMessage | null) => {}, + setSuccessMessage: (successMessage: string | null) => {}, +}) + +function Auth ({ state, appearance, logo, socialLayout = 'horizontal', additionalSignupFields }: { + state: State; +} & CustomizationOptions & { + additionalSignupFields?: AdditionalSignupFields; +}) { + const [errorMessage, setErrorMessage] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + // TODO(matija): this is called on every render, is it a problem? + // If we do it in useEffect(), then there is a glitch between the default color and the + // user provided one. + const customTheme = createTheme(appearance ?? {}) + + const titles: Record = { + login: 'Log in to your account', + signup: 'Create a new account', + "forgot-password": "Forgot your password?", + "reset-password": "Reset your password", + "verify-email": "Email verification", + } + const title = titles[state] + + const socialButtonsDirection = socialLayout === 'vertical' ? 'vertical' : 'horizontal' + + return ( + +
+ {logo && (Your Company)} + {title} +
+ + {errorMessage && ( + + {errorMessage.title}{errorMessage.description && ': '}{errorMessage.description} + + )} + {successMessage && {successMessage}} + + {(state === 'login' || state === 'signup') && ( + + )} + {state === 'forgot-password' && ()} + {state === 'reset-password' && ()} + {state === 'verify-email' && ()} + +
+ ) +} + +// PRIVATE API +export default Auth; diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx new file mode 100644 index 0000000000..f8fca66082 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx @@ -0,0 +1,18 @@ +import Auth from './Auth' +import { type CustomizationOptions, State } from './types' + +// PUBLIC API +export function LoginForm({ + appearance, + logo, + socialLayout, +}: CustomizationOptions) { + return ( + + ) +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx new file mode 100644 index 0000000000..32c7afc38a --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx @@ -0,0 +1,24 @@ +import Auth from './Auth' +import { + type CustomizationOptions, + type AdditionalSignupFields, + State, +} from './types' + +// PUBLIC API +export function SignupForm({ + appearance, + logo, + socialLayout, + additionalFields, +}: CustomizationOptions & { additionalFields?: AdditionalSignupFields; }) { + return ( + + ) +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx new file mode 100644 index 0000000000..1634307427 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx @@ -0,0 +1,102 @@ +import { styled } from 'wasp/core/stitches.config' + +// PRIVATE API +export const Form = styled('form', { + marginTop: '1.5rem', +}) + +// PUBLIC API +export const FormItemGroup = styled('div', { + '& + div': { + marginTop: '1.5rem', + }, +}) + +// PUBLIC API +export const FormLabel = styled('label', { + display: 'block', + fontSize: '$sm', + fontWeight: '500', + marginBottom: '0.5rem', +}) + +const commonInputStyles = { + display: 'block', + lineHeight: '1.5rem', + fontSize: '$sm', + borderWidth: '1px', + borderColor: '$gray600', + backgroundColor: '#f8f4ff', + boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + '&:focus': { + borderWidth: '1px', + borderColor: '$gray700', + boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + }, + '&:disabled': { + opacity: 0.5, + cursor: 'not-allowed', + backgroundColor: '$gray400', + borderColor: '$gray400', + color: '$gray500', + }, + + borderRadius: '0.375rem', + width: '100%', + + paddingTop: '0.375rem', + paddingBottom: '0.375rem', + paddingLeft: '0.75rem', + paddingRight: '0.75rem', + margin: 0, +} + +// PUBLIC API +export const FormInput = styled('input', commonInputStyles) + +// PUBLIC API +export const FormTextarea = styled('textarea', commonInputStyles) + +// PUBLIC API +export const FormError = styled('div', { + display: 'block', + fontSize: '$sm', + fontWeight: '500', + color: '$formErrorText', + marginTop: '0.5rem', +}) + +// PRIVATE API +export const SubmitButton = styled('button', { + display: 'flex', + justifyContent: 'center', + + width: '100%', + borderWidth: '1px', + borderColor: '$brand', + backgroundColor: '$brand', + color: '$submitButtonText', + + padding: '0.5rem 0.75rem', + boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + + fontWeight: '600', + fontSize: '$sm', + lineHeight: '1.25rem', + borderRadius: '0.375rem', + + // TODO(matija): extract this into separate BaseButton component and then inherit it. + '&:hover': { + backgroundColor: '$brandAccent', + borderColor: '$brandAccent', + }, + '&:disabled': { + opacity: 0.5, + cursor: 'not-allowed', + backgroundColor: '$gray400', + borderColor: '$gray400', + color: '$gray500', + }, + transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', + transitionDuration: '100ms', +}) diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx new file mode 100644 index 0000000000..362ff9dfda --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx @@ -0,0 +1,21 @@ +import { styled } from 'wasp/core/stitches.config' + +// PRIVATE API +export const Message = styled('div', { + padding: '0.5rem 0.75rem', + borderRadius: '0.375rem', + marginTop: '1rem', + background: '$gray400', +}) + +// PRIVATE API +export const MessageError = styled(Message, { + background: '$errorBackground', + color: '$errorText', +}) + +// PRIVATE API +export const MessageSuccess = styled(Message, { + background: '$successBackground', + color: '$successText', +}) diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx new file mode 100644 index 0000000000..db7519fceb --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx @@ -0,0 +1,184 @@ +import { useContext } from 'react' +import { useForm, UseFormReturn } from 'react-hook-form' +import { styled } from 'wasp/core/stitches.config' +import config from 'wasp/core/config' + +import { AuthContext } from '../../Auth' +import { + Form, + FormInput, + FormItemGroup, + FormLabel, + FormError, + FormTextarea, + SubmitButton, +} from '../Form' +import type { + AdditionalSignupFields, + AdditionalSignupField, + AdditionalSignupFieldRenderFn, + FormState, +} from '../../types' +import { useHistory } from 'react-router-dom' +import { useEmail } from '../email/useEmail' + + +// PRIVATE API +export type LoginSignupFormFields = { + [key: string]: string; +} + +// PRIVATE API +export const LoginSignupForm = ({ + state, + socialButtonsDirection = 'horizontal', + additionalSignupFields, +}: { + state: 'login' | 'signup' + socialButtonsDirection?: 'horizontal' | 'vertical' + additionalSignupFields?: AdditionalSignupFields +}) => { + const { + isLoading, + setErrorMessage, + setSuccessMessage, + setIsLoading, + } = useContext(AuthContext) + const isLogin = state === 'login' + const cta = isLogin ? 'Log in' : 'Sign up'; + const history = useHistory(); + const onErrorHandler = (error) => { + setErrorMessage({ title: error.message, description: error.data?.data?.message }) + }; + const hookForm = useForm() + const { register, formState: { errors }, handleSubmit: hookFormHandleSubmit } = hookForm + const { handleSubmit } = useEmail({ + isLogin, + onError: onErrorHandler, + showEmailVerificationPending() { + hookForm.reset() + setSuccessMessage(`You've signed up successfully! Check your email for the confirmation link.`) + }, + onLoginSuccess() { + history.push('/') + }, + }); + async function onSubmit (data) { + setIsLoading(true); + setErrorMessage(null); + setSuccessMessage(null); + try { + await handleSubmit(data); + } finally { + setIsLoading(false); + } + } + + return (<> +
+ + E-mail + + {errors.email && {errors.email.message}} + + + Password + + {errors.password && {errors.password.message}} + + + + {cta} + + + ) +} + +function AdditionalFormFields({ + hookForm, + formState: { isLoading }, + additionalSignupFields, +}: { + hookForm: UseFormReturn; + formState: FormState; + additionalSignupFields: AdditionalSignupFields; +}) { + const { + register, + formState: { errors }, + } = hookForm; + + function renderField>( + field: AdditionalSignupField, + // Ideally we would use ComponentType here, but it doesn't work with react-hook-form + Component: any, + props?: React.ComponentProps + ) { + return ( + + {field.label} + + {errors[field.name] && ( + {errors[field.name].message} + )} + + ); + } + + if (areAdditionalFieldsRenderFn(additionalSignupFields)) { + return additionalSignupFields(hookForm, { isLoading }) + } + + return ( + additionalSignupFields && + additionalSignupFields.map((field) => { + if (isFieldRenderFn(field)) { + return field(hookForm, { isLoading }) + } + switch (field.type) { + case 'input': + return renderField(field, FormInput, { + type: 'text', + }) + case 'textarea': + return renderField(field, FormTextarea) + default: + throw new Error( + `Unsupported additional signup field type: ${field.type}` + ) + } + }) + ) +} + +function isFieldRenderFn( + additionalSignupField: AdditionalSignupField | AdditionalSignupFieldRenderFn +): additionalSignupField is AdditionalSignupFieldRenderFn { + return typeof additionalSignupField === 'function' +} + +function areAdditionalFieldsRenderFn( + additionalSignupFields: AdditionalSignupFields +): additionalSignupFields is AdditionalSignupFieldRenderFn { + return typeof additionalSignupFields === 'function' +} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts new file mode 100644 index 0000000000..5156472689 --- /dev/null +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts @@ -0,0 +1,49 @@ +import { createTheme } from '@stitches/react' +import { UseFormReturn, RegisterOptions } from 'react-hook-form' +import type { LoginSignupFormFields } from './internal/common/LoginSignupForm' + +// PRIVATE API +export enum State { + Login = 'login', + Signup = 'signup', + ForgotPassword = 'forgot-password', + ResetPassword = 'reset-password', + VerifyEmail = 'verify-email', +} + +// PUBLIC API +export type CustomizationOptions = { + logo?: string + socialLayout?: 'horizontal' | 'vertical' + appearance?: Parameters[0] +} + +// PRIVATE API +export type ErrorMessage = { + title: string + description?: string +} + +// PRIVATE API +export type FormState = { + isLoading: boolean +} + +// PRIVATE API +export type AdditionalSignupFieldRenderFn = ( + hookForm: UseFormReturn, + formState: FormState +) => React.ReactNode + +// PRIVATE API +export type AdditionalSignupField = { + name: string + label: string + type: 'input' | 'textarea' + validations?: RegisterOptions +} + +// PRIVATE API +export type AdditionalSignupFields = + | (AdditionalSignupField | AdditionalSignupFieldRenderFn)[] + | AdditionalSignupFieldRenderFn diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json index 5d085ed28a..9455246888 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -63,7 +63,8 @@ "./client/api": "./dist/api/index.js", "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", - "./server/auth": "./dist/server/auth/index.js" + "./server/auth": "./dist/server/auth/index.js", + "./server/crud": "./dist/server/crud/index.js" }, "typesVersions": { "*": { diff --git a/waspc/examples/todo-typescript/src/task/crud.ts b/waspc/examples/todo-typescript/src/task/crud.ts index 042cdf7563..215cfee70a 100644 --- a/waspc/examples/todo-typescript/src/task/crud.ts +++ b/waspc/examples/todo-typescript/src/task/crud.ts @@ -1,6 +1,6 @@ -import { Task } from "wasp/entities"; -import { GetAllQuery } from "wasp/server/crud/Tasks"; +import { Task } from 'wasp/entities' +import { Tasks } from 'wasp/server/crud' export const getAllQuery = ((args, context) => { - return context.entities.Task.findMany({}); -}) satisfies GetAllQuery<{}, Task[]>; + return context.entities.Task.findMany({}) +}) satisfies Tasks.GetAllQuery<{}, Task[]> diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index ebd436b496..ddc7b97495 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -45,6 +45,7 @@ import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) +import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) @@ -117,6 +118,7 @@ genSdkReal spec = -- New API <++> genNewClientAuth spec <++> genNewServerApi spec + <++> genNewServerCrudApi spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Server/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/CrudG.hs new file mode 100644 index 0000000000..cca3f2258b --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/CrudG.hs @@ -0,0 +1,33 @@ +module Wasp.Generator.SdkGenerator.Server.CrudG + ( genNewServerCrudApi, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import StrongPath (relfile) +import Wasp.AppSpec (AppSpec, getCruds) +import qualified Wasp.AppSpec.Crud as AS.Crud +import Wasp.AppSpec.Valid (getIdFieldFromCrudEntity) +import Wasp.Generator.Crud (getCrudOperationJson) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import qualified Wasp.Generator.SdkGenerator.Common as C + +genNewServerCrudApi :: AppSpec -> Generator [FileDraft] +genNewServerCrudApi spec = + if areThereAnyCruds + then sequence [genCrudIndex spec cruds] + else return [] + where + cruds = getCruds spec + areThereAnyCruds = not $ null cruds + +genCrudIndex :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator FileDraft +genCrudIndex spec cruds = return $ C.mkTmplFdWithData [relfile|server/crud/index.ts|] tmplData + where + tmplData = object ["cruds" .= map getCrudOperationJsonFromCrud cruds] + getCrudOperationJsonFromCrud :: (String, AS.Crud.Crud) -> Aeson.Value + getCrudOperationJsonFromCrud (name, crud) = getCrudOperationJson name crud idField + where + idField = getIdFieldFromCrudEntity spec crud diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 5054e433f7..560d20398d 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -310,6 +310,7 @@ library Wasp.Generator.SdkGenerator.RouterGenerator Wasp.Generator.SdkGenerator.Client.AuthG Wasp.Generator.SdkGenerator.Server.AuthG + Wasp.Generator.SdkGenerator.Server.CrudG Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG From 427a1a9ab77ee5d7ac5dd7f2bf00168bcf1ff9bd Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Tue, 30 Jan 2024 20:07:53 +0100 Subject: [PATCH 08/19] Fixes CRUD server import --- .../templates/server/src/crud/_operations.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/waspc/data/Generator/templates/server/src/crud/_operations.ts b/waspc/data/Generator/templates/server/src/crud/_operations.ts index 14f9ddda2e..0be7517584 100644 --- a/waspc/data/Generator/templates/server/src/crud/_operations.ts +++ b/waspc/data/Generator/templates/server/src/crud/_operations.ts @@ -8,7 +8,7 @@ import type { {=# isAuthEnabled =} import { throwInvalidCredentialsError } from 'wasp/auth/utils' {=/ isAuthEnabled =} -import type { GetAllQuery, GetQuery, CreateAction, UpdateAction, DeleteAction } from "{= crudTypesImportPath =}"; +import type { {= crud.name =} } from "wasp/server/crud"; {=# overrides.GetAll.isDefined =} {=& overrides.GetAll.importStatement =} {=/ overrides.GetAll.isDefined =} @@ -41,7 +41,7 @@ const entities = { {=^ overrides.GetAll.isDefined =} type GetAllInput = {} type GetAllOutput = _WaspEntity[] -const _waspGetAllQuery: GetAllQuery = ((args, context) => { +const _waspGetAllQuery: {= crud.name =}.GetAllQuery = ((args, context) => { {=^ crud.operations.GetAll.isPublic =} throwIfNotAuthenticated(context) {=/ crud.operations.GetAll.isPublic =} @@ -75,7 +75,7 @@ export async function getAllFn(args, context) { {=^ overrides.Get.isDefined =} type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type GetOutput = _WaspEntity | null -const _waspGetQuery: GetQuery = ((args, context) => { +const _waspGetQuery: {= crud.name =}.GetQuery = ((args, context) => { {=^ crud.operations.Get.isPublic =} throwIfNotAuthenticated(context) {=/ crud.operations.Get.isPublic =} @@ -99,7 +99,7 @@ export async function getFn(args, context) { {=^ overrides.Create.isDefined =} type CreateInput = Prisma.{= crud.entityUpper =}CreateInput type CreateOutput = _WaspEntity -const _waspCreateAction: CreateAction = ((args, context) => { +const _waspCreateAction: {= crud.name =}.CreateAction = ((args, context) => { {=^ crud.operations.Create.isPublic =} throwIfNotAuthenticated(context) {=/ crud.operations.Create.isPublic =} @@ -123,7 +123,7 @@ export async function createFn(args, context) { {=^ overrides.Update.isDefined =} type UpdateInput = Prisma.{= crud.entityUpper =}UpdateInput & Prisma.{= crud.entityUpper =}WhereUniqueInput type UpdateOutput = _WaspEntity -const _waspUpdateAction: UpdateAction = ((args, context) => { +const _waspUpdateAction: {= crud.name =}.UpdateAction = ((args, context) => { {=^ crud.operations.Update.isPublic =} throwIfNotAuthenticated(context) {=/ crud.operations.Update.isPublic =} @@ -151,7 +151,7 @@ export async function updateFn(args, context) { {=^ overrides.Delete.isDefined =} type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type DeleteOutput = _WaspEntity -const _waspDeleteAction: DeleteAction = ((args, context) => { +const _waspDeleteAction: {= crud.name =}.DeleteAction = ((args, context) => { {=^ crud.operations.Delete.isPublic =} throwIfNotAuthenticated(context) {=/ crud.operations.Delete.isPublic =} @@ -180,4 +180,4 @@ function throwIfNotAuthenticated (context) { {=^ isAuthEnabled =} // Auth is not enabled {=/ isAuthEnabled =} -} +} \ No newline at end of file From 197c906df183bd1cade2fc0ea73a7b3e577a2cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0o=C5=A1i=C4=87?= Date: Tue, 30 Jan 2024 21:03:26 +0100 Subject: [PATCH 09/19] [New SDK]: { HttpError, AuthError, DbSeedFn, MiddlewareConfigFn } from 'wasp/server'. (#1699) --- waspc/data/Generator/templates/sdk/auth/utils.ts | 4 +--- .../Generator/templates/sdk/auth/validation.ts | 2 +- waspc/data/Generator/templates/sdk/dbSeed/types.ts | 3 --- waspc/data/Generator/templates/sdk/package.json | 14 ++++---------- .../templates/sdk/{core => server}/AuthError.ts | 6 ++---- .../templates/sdk/{core => server}/HttpError.ts | 6 ++---- waspc/data/Generator/templates/sdk/server/index.ts | 11 +++++++++++ .../sdk/server/middleware/globalMiddleware.ts | 6 ++++-- waspc/data/Generator/templates/server/src/app.js | 2 +- .../auth/providers/email/requestPasswordReset.ts | 2 +- .../src/auth/providers/email/resetPassword.ts | 2 +- .../server/src/auth/providers/email/signup.ts | 2 +- .../server/src/auth/providers/email/verifyEmail.ts | 2 +- .../Generator/templates/server/src/auth/utils.ts | 4 +--- .../data/Generator/templates/server/src/dbSeed.ts | 3 +-- waspc/examples/todo-typescript/src/db/seeds.ts | 2 +- .../todo-typescript/src/setup/serverSetup.ts | 2 +- waspc/examples/todo-typescript/src/task/actions.ts | 2 +- waspc/examples/todo-typescript/src/task/queries.ts | 3 +-- waspc/src/Wasp/Generator/SdkGenerator.hs | 5 ++--- 20 files changed, 38 insertions(+), 45 deletions(-) delete mode 100644 waspc/data/Generator/templates/sdk/dbSeed/types.ts rename waspc/data/Generator/templates/sdk/{core => server}/AuthError.ts (64%) rename waspc/data/Generator/templates/sdk/{core => server}/HttpError.ts (90%) diff --git a/waspc/data/Generator/templates/sdk/auth/utils.ts b/waspc/data/Generator/templates/sdk/auth/utils.ts index fd3c87feeb..2f4524edca 100644 --- a/waspc/data/Generator/templates/sdk/auth/utils.ts +++ b/waspc/data/Generator/templates/sdk/auth/utils.ts @@ -1,9 +1,7 @@ {{={= =}=}} import { hashPassword } from './password.js' import { verify } from './jwt.js' -import AuthError from 'wasp/core/AuthError' -import HttpError from 'wasp/core/HttpError' -import { prisma } from 'wasp/server' +import { prisma, HttpError, AuthError } from 'wasp/server' import { sleep } from 'wasp/server/utils' import { type {= userEntityUpper =}, diff --git a/waspc/data/Generator/templates/sdk/auth/validation.ts b/waspc/data/Generator/templates/sdk/auth/validation.ts index d7cda4e29f..637f4203fd 100644 --- a/waspc/data/Generator/templates/sdk/auth/validation.ts +++ b/waspc/data/Generator/templates/sdk/auth/validation.ts @@ -1,4 +1,4 @@ -import HttpError from 'wasp/core/HttpError'; +import { HttpError } from 'wasp/server'; export const PASSWORD_FIELD = 'password'; const USERNAME_FIELD = 'username'; diff --git a/waspc/data/Generator/templates/sdk/dbSeed/types.ts b/waspc/data/Generator/templates/sdk/dbSeed/types.ts deleted file mode 100644 index 7a72bca7ef..0000000000 --- a/waspc/data/Generator/templates/sdk/dbSeed/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { PrismaClient } from '@prisma/client' - -export type DbSeedFn = (prisma: PrismaClient) => Promise diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index e785457aba..92c05b42fc 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -12,10 +12,6 @@ {=! todo(filip): Check all exports when done with SDK generation =} {=! Some of the statements in the comments might become incorrect. =} {=! "our code" means: "web-app", "server" or "SDK", or "some combination of the three". =} - {=! Used by users, documented. =} - "./core/HttpError": "./dist/core/HttpError.js", - {=! Used by users, documented. =} - "./core/AuthError": "./dist/core/AuthError.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./core/config": "./dist/core/config.js", {=! Used by our code, uncodumented (but accessible) for users. =} @@ -70,8 +66,6 @@ "./universal/types": "./dist/universal/types.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./universal/validators": "./dist/universal/validators.js", - {=! Used by users and by our code, documented. =} - "./server/middleware": "./dist/server/middleware/index.js", {=! Parts are used by users, documented. Parts are probably used by our code, undocumented (but accessible). =} "./server/utils": "./dist/server/utils.js", {=! Used by our code, uncodumented (but accessible) for users. =} @@ -81,8 +75,6 @@ {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email": "./dist/server/auth/email/index.js", {=! Used by users, documented. =} - "./dbSeed/types": "./dist/dbSeed/types.js", - {=! Used by users, documented. =} "./test": "./dist/test/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./test/*": "./dist/test/*.js", @@ -108,11 +100,13 @@ {=! Used by our code, uncodumented (but accessible) for users. =} "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", - {=! Still needed, reconsider during refactoring =} + {=! Still needed, reconsider during refactoring. =} "./server/types": "./dist/server/types/index.js", + {=! Still used by the server code, reconsider during refactoring. =} + "./server/middleware": "./dist/server/middleware/index.js", {=! ================= NEW API HERE =================== =} - {=! Public: { config, prisma, type ServerSetupFn } =} + {=! Public: { config, prisma, type ServerSetupFn, HttpError, AuthError, type DbSeedFn, type MiddlewareConfigFn } =} {=! Private: [] =} "./server": "./dist/server/index.js", {=! Public: { type MyApiRoute1, type MyApiRoute2, ... } =} diff --git a/waspc/data/Generator/templates/sdk/core/AuthError.ts b/waspc/data/Generator/templates/sdk/server/AuthError.ts similarity index 64% rename from waspc/data/Generator/templates/sdk/core/AuthError.ts rename to waspc/data/Generator/templates/sdk/server/AuthError.ts index 3ea4b536ea..4b44d8c30b 100644 --- a/waspc/data/Generator/templates/sdk/core/AuthError.ts +++ b/waspc/data/Generator/templates/sdk/server/AuthError.ts @@ -1,7 +1,7 @@ -class AuthError extends Error { +export class AuthError extends Error { public data: unknown - constructor (message: string, data?: unknown, ...params: unknown[]) { + constructor(message: string, data?: unknown, ...params: unknown[]) { super(message, ...params) if (Error.captureStackTrace) { @@ -15,5 +15,3 @@ class AuthError extends Error { } } } - -export default AuthError diff --git a/waspc/data/Generator/templates/sdk/core/HttpError.ts b/waspc/data/Generator/templates/sdk/server/HttpError.ts similarity index 90% rename from waspc/data/Generator/templates/sdk/core/HttpError.ts rename to waspc/data/Generator/templates/sdk/server/HttpError.ts index 4f229882e4..3e8d3f5a0d 100644 --- a/waspc/data/Generator/templates/sdk/core/HttpError.ts +++ b/waspc/data/Generator/templates/sdk/server/HttpError.ts @@ -1,7 +1,7 @@ -class HttpError extends Error { +export class HttpError extends Error { public statusCode: number public data: unknown - + constructor (statusCode: number, message?: string, data?: Record, ...params: unknown[]) { super(message, ...params) @@ -21,5 +21,3 @@ class HttpError extends Error { } } } - -export default HttpError diff --git a/waspc/data/Generator/templates/sdk/server/index.ts b/waspc/data/Generator/templates/sdk/server/index.ts index 8d527a331b..89b5889298 100644 --- a/waspc/data/Generator/templates/sdk/server/index.ts +++ b/waspc/data/Generator/templates/sdk/server/index.ts @@ -1,7 +1,18 @@ +import type { PrismaClient } from '@prisma/client' + // PUBLIC API export { default as config } from './config.js' // PUBLIC API export { default as prisma } from './dbClient.js' // PUBLIC API export { type ServerSetupFn } from './types/index.js' +// PUBLIC API +export { HttpError } from './HttpError.js' +// PUBLIC API +export { AuthError } from './AuthError.js' +// PUBLIC API +export { MiddlewareConfigFn } from './middleware/index.js' + +// PUBLIC API +export type DbSeedFn = (prisma: PrismaClient) => Promise diff --git a/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts b/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts index 28c2631149..8bab5e4709 100644 --- a/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts +++ b/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts @@ -1,6 +1,8 @@ import { type RequestHandler } from 'express' -export type MiddlewareConfig = Map - +// PUBLIC API export type MiddlewareConfigFn = (middlewareConfig: MiddlewareConfig) => MiddlewareConfig +// PRIVATE API +export type MiddlewareConfig = Map + diff --git a/waspc/data/Generator/templates/server/src/app.js b/waspc/data/Generator/templates/server/src/app.js index 9db0e1b75d..025a3c4c14 100644 --- a/waspc/data/Generator/templates/server/src/app.js +++ b/waspc/data/Generator/templates/server/src/app.js @@ -1,6 +1,6 @@ import express from 'express' -import HttpError from 'wasp/core/HttpError' +import { HttpError } from 'wasp/server' import indexRouter from './routes/index.js' // TODO: Consider extracting most of this logic into createApp(routes, path) function so that diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts index 92d3dea0fb..be0c685335 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts @@ -13,7 +13,7 @@ import { import { ensureValidEmail } from 'wasp/auth/validation'; import type { EmailFromField } from 'wasp/email/core/types'; import { GetPasswordResetEmailContentFn } from 'wasp/server/auth/email'; -import HttpError from 'wasp/core/HttpError'; +import { HttpError } from 'wasp/server'; export function getRequestPasswordResetRoute({ fromField, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts index ba00a4ae9f..8f5681a994 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts @@ -8,7 +8,7 @@ import { } from 'wasp/auth/utils'; import { ensureTokenIsPresent, ensurePasswordIsPresent, ensureValidPassword } from 'wasp/auth/validation'; import { tokenVerificationErrors } from "./types.js"; -import HttpError from 'wasp/core/HttpError'; +import { HttpError } from 'wasp/server'; export async function resetPassword( req: Request<{ token: string; password: string; }>, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts index 3039f44746..8aab2e3019 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts @@ -18,7 +18,7 @@ import { import { ensureValidEmail, ensureValidPassword, ensurePasswordIsPresent } from 'wasp/auth/validation'; import { GetVerificationEmailContentFn } from 'wasp/server/auth/email'; import { validateAndGetUserFields } from 'wasp/auth/utils' -import HttpError from 'wasp/core/HttpError'; +import { HttpError } from 'wasp/server'; import { type UserSignupFields } from 'wasp/auth/providers/types'; export function getSignupRoute({ diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts index c54963e411..a337b6e616 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts @@ -7,7 +7,7 @@ import { deserializeAndSanitizeProviderData, } from 'wasp/auth/utils'; import { tokenVerificationErrors } from './types.js'; -import HttpError from 'wasp/core/HttpError'; +import { HttpError } from 'wasp/server'; export async function verifyEmail( diff --git a/waspc/data/Generator/templates/server/src/auth/utils.ts b/waspc/data/Generator/templates/server/src/auth/utils.ts index 887997b5cb..2efc01d671 100644 --- a/waspc/data/Generator/templates/server/src/auth/utils.ts +++ b/waspc/data/Generator/templates/server/src/auth/utils.ts @@ -1,9 +1,7 @@ {{={= =}=}} import { hashPassword } from 'wasp/auth/password' import { verify } from 'wasp/auth/jwt' -import AuthError from 'wasp/core/AuthError' -import HttpError from 'wasp/core/HttpError' -import { prisma } from 'wasp/server' +import { prisma, HttpError, AuthError } from 'wasp/server' import { sleep } from 'wasp/server/utils' import { type {= userEntityUpper =}, diff --git a/waspc/data/Generator/templates/server/src/dbSeed.ts b/waspc/data/Generator/templates/server/src/dbSeed.ts index 51378ed8f9..d7e1d77f88 100644 --- a/waspc/data/Generator/templates/server/src/dbSeed.ts +++ b/waspc/data/Generator/templates/server/src/dbSeed.ts @@ -6,8 +6,7 @@ // TODO: Consider in the future moving it into a a separate project (maybe db/ ?), while still // maintaining access to logic from the server/ . -import { prisma } from 'wasp/server' -import type { DbSeedFn } from 'wasp/dbSeed/types' +import { prisma, DbSeedFn } from 'wasp/server' {=# dbSeeds =} {=& importStatement =} diff --git a/waspc/examples/todo-typescript/src/db/seeds.ts b/waspc/examples/todo-typescript/src/db/seeds.ts index 854a53c313..fc7c0a362e 100644 --- a/waspc/examples/todo-typescript/src/db/seeds.ts +++ b/waspc/examples/todo-typescript/src/db/seeds.ts @@ -1,4 +1,4 @@ -import { DbSeedFn } from "wasp/dbSeed/types"; +import { DbSeedFn } from 'wasp/server'; export const seedMyDb: DbSeedFn = async (prisma) => { const user = await prisma.user.findFirst({}); diff --git a/waspc/examples/todo-typescript/src/setup/serverSetup.ts b/waspc/examples/todo-typescript/src/setup/serverSetup.ts index 1b49dcf081..ae49219b08 100644 --- a/waspc/examples/todo-typescript/src/setup/serverSetup.ts +++ b/waspc/examples/todo-typescript/src/setup/serverSetup.ts @@ -1,6 +1,6 @@ import express, { Application } from 'express' import cors from 'cors' -import type { MiddlewareConfigFn } from 'wasp/server/middleware' +import { type MiddlewareConfigFn } from 'wasp/server' import { config, ServerSetupFn, prisma } from 'wasp/server' export const serverMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => { diff --git a/waspc/examples/todo-typescript/src/task/actions.ts b/waspc/examples/todo-typescript/src/task/actions.ts index 5428ee34df..98a56e925a 100644 --- a/waspc/examples/todo-typescript/src/task/actions.ts +++ b/waspc/examples/todo-typescript/src/task/actions.ts @@ -1,4 +1,4 @@ -import HttpError from 'wasp/core/HttpError' +import { HttpError } from 'wasp/server' import type { CreateTask, UpdateTask, diff --git a/waspc/examples/todo-typescript/src/task/queries.ts b/waspc/examples/todo-typescript/src/task/queries.ts index fbc2e858bd..1b635d17c7 100644 --- a/waspc/examples/todo-typescript/src/task/queries.ts +++ b/waspc/examples/todo-typescript/src/task/queries.ts @@ -1,5 +1,4 @@ -import HttpError from 'wasp/core/HttpError' -import AuthError from 'wasp/core/AuthError' +import { HttpError, AuthError } from 'wasp/server' import type { GetTasks } from 'wasp/server/queries/types' import type { Task } from 'wasp/entities' import { ensureValidEmail, createProviderId } from 'wasp/server/auth' diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index ddc7b97495..043875c0b9 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -82,8 +82,6 @@ genSdkReal spec = genFileCopy [relfile|core/auth.ts|], genFileCopy [relfile|core/storage.ts|], genFileCopy [relfile|core/stitches.config.ts|], - genFileCopy [relfile|core/AuthError.ts|], - genFileCopy [relfile|core/HttpError.ts|], -- Not migrated to TS yet genFileCopy [relfile|operations/resources.js|], genFileCopy [relfile|operations/index.ts|], @@ -91,8 +89,9 @@ genSdkReal spec = genFileCopy [relfile|operations/updateHandlersMap.js|], genFileCopy [relfile|server/index.ts|], genFileCopy [relfile|server/dbClient.ts|], + genFileCopy [relfile|server/HttpError.ts|], + genFileCopy [relfile|server/AuthError.ts|], genFileCopy [relfile|types/index.ts|], - genFileCopy [relfile|dbSeed/types.ts|], genFileCopy [relfile|test/vitest/helpers.tsx|], genFileCopy [relfile|test/index.ts|], genFileCopy [relfile|jobs/pgBoss/types.ts|], From 7c93340dda97b42586f930b6a31ceda991d34337 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 31 Jan 2024 11:38:52 +0100 Subject: [PATCH 10/19] Implement wasp/client/crud API (#1700) --- .../templates/sdk/{ => client}/crud/_crud.ts | 1 + .../templates/sdk/client/crud/index.ts | 4 ++ .../data/Generator/templates/sdk/package.json | 4 +- .../.wasp/out/sdk/wasp/package.json | 4 +- .../examples/todo-typescript/src/MainPage.tsx | 2 +- .../todo-typescript/src/Todo.test.tsx | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 2 + .../Generator/SdkGenerator/Client/CrudG.hs | 51 +++++++++++++++++++ .../src/Wasp/Generator/SdkGenerator/CrudG.hs | 30 +---------- .../Wasp/Generator/ServerGenerator/CrudG.hs | 4 +- waspc/waspc.cabal | 1 + 11 files changed, 68 insertions(+), 37 deletions(-) rename waspc/data/Generator/templates/sdk/{ => client}/crud/_crud.ts (99%) create mode 100644 waspc/data/Generator/templates/sdk/client/crud/index.ts create mode 100644 waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs diff --git a/waspc/data/Generator/templates/sdk/crud/_crud.ts b/waspc/data/Generator/templates/sdk/client/crud/_crud.ts similarity index 99% rename from waspc/data/Generator/templates/sdk/crud/_crud.ts rename to waspc/data/Generator/templates/sdk/client/crud/_crud.ts index 24a14bd26f..0516373384 100644 --- a/waspc/data/Generator/templates/sdk/crud/_crud.ts +++ b/waspc/data/Generator/templates/sdk/client/crud/_crud.ts @@ -96,4 +96,5 @@ function createCrud() { } } +// PUBLIC API export const {= name =} = createCrud(); diff --git a/waspc/data/Generator/templates/sdk/client/crud/index.ts b/waspc/data/Generator/templates/sdk/client/crud/index.ts new file mode 100644 index 0000000000..851b9823de --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/crud/index.ts @@ -0,0 +1,4 @@ +{{={= =}=}} +{=# cruds =} + export { {= name =} } from './{= name =}'; +{=/ cruds =} diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 92c05b42fc..e698999273 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -78,7 +78,6 @@ "./test": "./dist/test/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./test/*": "./dist/test/*.js", - "./crud/*": "./dist/crud/*.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/crud/*": "./dist/server/crud/*", "./email": "./dist/email/index.js", @@ -118,7 +117,8 @@ "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", "./server/auth": "./dist/server/auth/index.js", - "./server/crud": "./dist/server/crud/index.js" + "./server/crud": "./dist/server/crud/index.js", + "./client/crud": "./dist/client/crud/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json index 9455246888..5471c7a681 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -44,7 +44,6 @@ "./dbSeed/types": "./dist/dbSeed/types.js", "./test": "./dist/test/index.js", "./test/*": "./dist/test/*.js", - "./crud/*": "./dist/crud/*.js", "./server/crud/*": "./dist/server/crud/*", "./email": "./dist/email/index.js", "./email/core/types": "./dist/email/core/types.js", @@ -64,7 +63,8 @@ "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", "./server/auth": "./dist/server/auth/index.js", - "./server/crud": "./dist/server/crud/index.js" + "./server/crud": "./dist/server/crud/index.js", + "./client/crud": "./dist/client/crud/index.js" }, "typesVersions": { "*": { diff --git a/waspc/examples/todo-typescript/src/MainPage.tsx b/waspc/examples/todo-typescript/src/MainPage.tsx index cc400853bd..cb34521acd 100644 --- a/waspc/examples/todo-typescript/src/MainPage.tsx +++ b/waspc/examples/todo-typescript/src/MainPage.tsx @@ -12,7 +12,7 @@ import waspLogo from './waspLogo.png' import type { Task } from 'wasp/entities' import { AuthUser, getFirstProviderUserId } from 'wasp/auth' import { Link } from 'react-router-dom' -import { Tasks } from 'wasp/crud/Tasks' +import { Tasks } from 'wasp/client/crud' // import login from 'wasp/auth/login' // import signup from 'wasp/auth/signup' import { Todo } from './Todo' diff --git a/waspc/examples/todo-typescript/src/Todo.test.tsx b/waspc/examples/todo-typescript/src/Todo.test.tsx index b515530f0e..ce54febd76 100644 --- a/waspc/examples/todo-typescript/src/Todo.test.tsx +++ b/waspc/examples/todo-typescript/src/Todo.test.tsx @@ -7,7 +7,7 @@ import { Todo, areThereAnyTasks } from './Todo' import { MainPage } from './MainPage' import type { AuthUser } from 'wasp/auth' import { getMe } from 'wasp/client/auth' -import { Tasks } from 'wasp/crud/Tasks' +import { Tasks } from 'wasp/client/crud' const mockTasks = [ { diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 043875c0b9..e1621de90a 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -38,6 +38,7 @@ import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.SdkGenerator.AuthG (genAuth) import Wasp.Generator.SdkGenerator.Client.AuthG (genNewClientAuth) +import Wasp.Generator.SdkGenerator.Client.CrudG (genNewClientrudApi) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSender) @@ -118,6 +119,7 @@ genSdkReal spec = <++> genNewClientAuth spec <++> genNewServerApi spec <++> genNewServerCrudApi spec + <++> genNewClientrudApi spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs new file mode 100644 index 0000000000..f73cf2c3ee --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs @@ -0,0 +1,51 @@ +module Wasp.Generator.SdkGenerator.Client.CrudG + ( genNewClientrudApi, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import Data.Maybe (fromJust) +import StrongPath (reldir, relfile, ()) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec, getCruds) +import qualified Wasp.AppSpec.Crud as AS.Crud +import Wasp.AppSpec.Valid (getIdFieldFromCrudEntity) +import Wasp.Generator.Crud (getCrudOperationJson) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Util ((<++>)) + +genNewClientrudApi :: AppSpec -> Generator [FileDraft] +genNewClientrudApi spec = + if areThereAnyCruds + then + sequence + [ genCrudIndex spec cruds + ] + <++> genCrudOperations spec cruds + else return [] + where + cruds = getCruds spec + areThereAnyCruds = not $ null cruds + +genCrudIndex :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator FileDraft +genCrudIndex spec cruds = return $ C.mkTmplFdWithData [relfile|client/crud/index.ts|] tmplData + where + tmplData = object ["cruds" .= map getCrudOperationJsonFromCrud cruds] + getCrudOperationJsonFromCrud :: (String, AS.Crud.Crud) -> Aeson.Value + getCrudOperationJsonFromCrud (name, crud) = getCrudOperationJson name crud idField + where + idField = getIdFieldFromCrudEntity spec crud + +genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] +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|client/crud/_crud.ts|] + destPath = [reldir|client/crud|] fromJust (SP.parseRelFile (name ++ ".ts")) + tmplData = getCrudOperationJson name crud idField + idField = getIdFieldFromCrudEntity spec crud diff --git a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs index 6f3b08284c..30bc7ccdf9 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs @@ -1,23 +1,15 @@ module Wasp.Generator.SdkGenerator.CrudG ( genCrud, - getCrudTypesImportPathForName, ) where import Data.Aeson (KeyValue ((.=)), object) import qualified Data.Aeson.Types as Aeson.Types -import Data.Maybe (fromJust) import StrongPath - ( File', - Path, - Posix, - Rel, - reldir, - reldirP, + ( 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 @@ -28,33 +20,18 @@ import Wasp.Generator.Crud (crudDeclarationToOperationsList, getCrudFilePath, ge import Wasp.Generator.FileDraft (FileDraft) import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.Monad (Generator) -import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath) 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 + then genCrudServerOperations spec cruds else return [] where cruds = getCruds spec areThereAnyCruds = not $ null cruds -genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] -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] genCrudServerOperations spec cruds = return $ map genCrudOperation cruds where @@ -91,6 +68,3 @@ genCrudServerOperations spec cruds = return $ map genCrudOperation cruds operationToOverrideImport (operation, options) = makeCrudOperationKeyAndJsonPair operation importJson where importJson = GJI.jsImportToImportJson $ extImportToJsImport <$> AS.Crud.overrideFn options - -getCrudTypesImportPathForName :: String -> Path Posix (Rel r) File' -getCrudTypesImportPathForName crudName = makeSdkImportPath $ [reldirP|server/crud|] fromJust (SP.parseRelFileP crudName) diff --git a/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs b/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs index bdf184a8dd..0b5689d582 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs @@ -24,7 +24,6 @@ import Wasp.Generator.Crud import qualified Wasp.Generator.Crud.Routes as Routes import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.SdkGenerator.CrudG (getCrudTypesImportPathForName) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson) import Wasp.JsImport (JsImportPath (RelativeImportPath)) @@ -96,8 +95,7 @@ genCrudOperations spec cruds = return $ map genCrudOperation cruds "userEntityUpper" .= maybeUserEntity, "overrides" .= object overrides, "queryType" .= queryTsType, - "actionType" .= actionTsType, - "crudTypesImportPath" .= SP.fromRelFileP (getCrudTypesImportPathForName name) + "actionType" .= actionTsType ] idField = getIdFieldFromCrudEntity spec crud maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 560d20398d..fa227ff71c 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -311,6 +311,7 @@ library Wasp.Generator.SdkGenerator.Client.AuthG Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.SdkGenerator.Server.CrudG + Wasp.Generator.SdkGenerator.Client.CrudG Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG From 2e9841058f3830c4e211d01502601115a08310bf Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 31 Jan 2024 11:51:55 +0100 Subject: [PATCH 11/19] Implement new wasp/server/email API (#1701) --- .../data/Generator/templates/sdk/package.json | 6 +++--- .../templates/sdk/server/auth/email/utils.ts | 4 ++-- .../sdk/{ => server}/email/core/helpers.ts | 5 ++++- .../sdk/{ => server}/email/core/index.ts | 4 ++++ .../{ => server}/email/core/providers/dummy.ts | 3 ++- .../email/core/providers/mailgun.ts | 3 ++- .../email/core/providers/sendgrid.ts | 3 ++- .../{ => server}/email/core/providers/smtp.ts | 3 ++- .../sdk/{ => server}/email/core/types.ts | 9 +++++++++ .../templates/sdk/{ => server}/email/index.ts | 1 + .../server/src/auth/providers/config/email.ts | 2 +- .../providers/email/requestPasswordReset.ts | 2 +- .../server/src/auth/providers/email/signup.ts | 2 +- .../.wasp/out/sdk/wasp/auth/utils.ts | 4 +--- .../.wasp/out/sdk/wasp/auth/validation.ts | 2 +- .../.wasp/out/sdk/wasp/package.json | 11 ++++------- .../todo-typescript/src/task/actions.ts | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 8 ++++---- .../Generator/SdkGenerator/Client/CrudG.hs | 6 +++--- .../SdkGenerator/EmailSender/Providers.hs | 2 +- .../SdkGenerator/{ => Server}/EmailSenderG.hs | 14 +++++++------- waspc/waspc.cabal | 18 +++++++++--------- 22 files changed, 65 insertions(+), 49 deletions(-) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/helpers.ts (90%) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/index.ts (90%) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/providers/dummy.ts (94%) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/providers/mailgun.ts (87%) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/providers/sendgrid.ts (88%) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/providers/smtp.ts (89%) rename waspc/data/Generator/templates/sdk/{ => server}/email/core/types.ts (87%) rename waspc/data/Generator/templates/sdk/{ => server}/email/index.ts (98%) rename waspc/src/Wasp/Generator/SdkGenerator/{ => Server}/EmailSenderG.hs (91%) diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index e698999273..bf9db83f49 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -80,9 +80,8 @@ "./test/*": "./dist/test/*.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/crud/*": "./dist/server/crud/*", - "./email": "./dist/email/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} - "./email/core/types": "./dist/email/core/types.js", + "./server/email/core/types": "./dist/server/email/core/types.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email/utils": "./dist/server/auth/email/utils.js", {=! Parts are used by users and documented (types), other parts are used by the framework code (entities). =} @@ -118,7 +117,8 @@ "./client/auth": "./dist/client/auth/index.js", "./server/auth": "./dist/server/auth/index.js", "./server/crud": "./dist/server/crud/index.js", - "./client/crud": "./dist/client/crud/index.js" + "./client/crud": "./dist/client/crud/index.js", + "./server/email": "./dist/server/email/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts index 1270589940..5b2bfbee56 100644 --- a/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts +++ b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts @@ -1,7 +1,7 @@ {{={= =}=}} import { signData } from 'wasp/auth/jwt' -import { emailSender } from 'wasp/email'; -import { Email } from 'wasp/email/core/types'; +import { emailSender } from 'wasp/server/email'; +import { Email } from 'wasp/server/email/core/types'; import { createProviderId, updateAuthIdentityProviderData, diff --git a/waspc/data/Generator/templates/sdk/email/core/helpers.ts b/waspc/data/Generator/templates/sdk/server/email/core/helpers.ts similarity index 90% rename from waspc/data/Generator/templates/sdk/email/core/helpers.ts rename to waspc/data/Generator/templates/sdk/server/email/core/helpers.ts index 9fc5652ba6..f7dc673d62 100644 --- a/waspc/data/Generator/templates/sdk/email/core/helpers.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/helpers.ts @@ -1,6 +1,7 @@ {{={= =}=}} -import { EmailFromField } from "wasp/email/core/types"; +import { EmailFromField } from "./types"; +// PRIVATE API // Formats an email address and an optional name into a string that can be used // as the "from" field in an email. // { email: "test@test.com, name: "Test" } -> "Test " @@ -18,6 +19,7 @@ export function formatFromField({ } {=# isDefaultFromFieldDefined =} +// PRIVATE API export function getDefaultFromField(): EmailFromField { return { email: "{= defaultFromField.email =}", @@ -28,6 +30,7 @@ export function getDefaultFromField(): EmailFromField { } {=/ isDefaultFromFieldDefined =} {=^ isDefaultFromFieldDefined =} +// PRIVATE API export function getDefaultFromField(): EmailFromField { return { email: "", diff --git a/waspc/data/Generator/templates/sdk/email/core/index.ts b/waspc/data/Generator/templates/sdk/server/email/core/index.ts similarity index 90% rename from waspc/data/Generator/templates/sdk/email/core/index.ts rename to waspc/data/Generator/templates/sdk/server/email/core/index.ts index 78430344b7..746cd023e3 100644 --- a/waspc/data/Generator/templates/sdk/email/core/index.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/index.ts @@ -1,13 +1,17 @@ {{={= =}=}} {=# isSmtpProviderUsed =} +// PRIVATE API export { initSmtpEmailSender as initEmailSender } from "./providers/smtp.js"; {=/ isSmtpProviderUsed =} {=# isSendGridProviderUsed =} +// PRIVATE API export { initSendGridEmailSender as initEmailSender } from "./providers/sendgrid.js"; {=/ isSendGridProviderUsed =} {=# isMailgunProviderUsed =} +// PRIVATE API export { initMailgunEmailSender as initEmailSender } from "./providers/mailgun.js"; {=/ isMailgunProviderUsed =} {=# isDummyProviderUsed =} +// PRIVATE API export { initDummyEmailSender as initEmailSender } from "./providers/dummy.js"; {=/ isDummyProviderUsed =} diff --git a/waspc/data/Generator/templates/sdk/email/core/providers/dummy.ts b/waspc/data/Generator/templates/sdk/server/email/core/providers/dummy.ts similarity index 94% rename from waspc/data/Generator/templates/sdk/email/core/providers/dummy.ts rename to waspc/data/Generator/templates/sdk/server/email/core/providers/dummy.ts index 48d0f08fe2..51b921b12f 100644 --- a/waspc/data/Generator/templates/sdk/email/core/providers/dummy.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/providers/dummy.ts @@ -1,8 +1,9 @@ -import { DummyEmailProvider, EmailSender } from "wasp/email/core/types"; +import { DummyEmailProvider, EmailSender } from "../types"; import { getDefaultFromField } from "../helpers.js"; const yellowColor = "\x1b[33m%s\x1b[0m"; +// PRIVATE API export function initDummyEmailSender( config?: DummyEmailProvider, ): EmailSender { diff --git a/waspc/data/Generator/templates/sdk/email/core/providers/mailgun.ts b/waspc/data/Generator/templates/sdk/server/email/core/providers/mailgun.ts similarity index 87% rename from waspc/data/Generator/templates/sdk/email/core/providers/mailgun.ts rename to waspc/data/Generator/templates/sdk/server/email/core/providers/mailgun.ts index 549b393589..7d008aa9f7 100644 --- a/waspc/data/Generator/templates/sdk/email/core/providers/mailgun.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/providers/mailgun.ts @@ -1,7 +1,8 @@ import { NodeMailgun } from "ts-mailgun"; import { getDefaultFromField } from "../helpers.js"; -import type { MailgunEmailProvider, EmailSender } from "wasp/email/core/types"; +import type { MailgunEmailProvider, EmailSender } from "../types"; +// PRIVATE API export function initMailgunEmailSender( config: MailgunEmailProvider ): EmailSender { diff --git a/waspc/data/Generator/templates/sdk/email/core/providers/sendgrid.ts b/waspc/data/Generator/templates/sdk/server/email/core/providers/sendgrid.ts similarity index 88% rename from waspc/data/Generator/templates/sdk/email/core/providers/sendgrid.ts rename to waspc/data/Generator/templates/sdk/server/email/core/providers/sendgrid.ts index 3afcc42bd1..60dd8feb80 100644 --- a/waspc/data/Generator/templates/sdk/email/core/providers/sendgrid.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/providers/sendgrid.ts @@ -1,7 +1,8 @@ import SendGrid from "@sendgrid/mail"; import { getDefaultFromField } from "../helpers.js"; -import type { SendGridProvider, EmailSender } from "wasp/email/core/types"; +import type { SendGridProvider, EmailSender } from from "../types"; +// PRIVATE API export function initSendGridEmailSender( provider: SendGridProvider ): EmailSender { diff --git a/waspc/data/Generator/templates/sdk/email/core/providers/smtp.ts b/waspc/data/Generator/templates/sdk/server/email/core/providers/smtp.ts similarity index 89% rename from waspc/data/Generator/templates/sdk/email/core/providers/smtp.ts rename to waspc/data/Generator/templates/sdk/server/email/core/providers/smtp.ts index 43d8d6c211..25194b2897 100644 --- a/waspc/data/Generator/templates/sdk/email/core/providers/smtp.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/providers/smtp.ts @@ -1,7 +1,8 @@ import { createTransport } from "nodemailer"; import { formatFromField, getDefaultFromField } from "../helpers.js"; -import type { SMTPEmailProvider, EmailSender } from "wasp/email/core/types"; +import type { SMTPEmailProvider, EmailSender } from "../types"; +// PRIVATE API export function initSmtpEmailSender(config: SMTPEmailProvider): EmailSender { const transporter = createTransport({ host: config.host, diff --git a/waspc/data/Generator/templates/sdk/email/core/types.ts b/waspc/data/Generator/templates/sdk/server/email/core/types.ts similarity index 87% rename from waspc/data/Generator/templates/sdk/email/core/types.ts rename to waspc/data/Generator/templates/sdk/server/email/core/types.ts index 8b2a984799..7da4e80c54 100644 --- a/waspc/data/Generator/templates/sdk/email/core/types.ts +++ b/waspc/data/Generator/templates/sdk/server/email/core/types.ts @@ -1,6 +1,8 @@ {{={= =}=}} +// PRIVATE API export type EmailProvider = SMTPEmailProvider | SendGridProvider | MailgunEmailProvider | DummyEmailProvider; +// PRIVATE API export type SMTPEmailProvider = { type: "smtp"; host: string; @@ -9,27 +11,33 @@ export type SMTPEmailProvider = { password: string; }; +// PRIVATE API export type SendGridProvider = { type: "sendgrid"; apiKey: string; }; +// PRIVATE API export type MailgunEmailProvider = { type: "mailgun"; apiKey: string; domain: string; }; +// PRIVATE API export type DummyEmailProvider = { type: "dummy"; } +// PRIVATE API export type EmailSender = { send: (email: Email) => Promise; }; +// PRIVATE API export type SentMessageInfo = any; +// PRIVATE API export type Email = { {=# isDefaultFromFieldDefined =} from?: EmailFromField; @@ -43,6 +51,7 @@ export type Email = { html: string; }; +// PRIVATE API export type EmailFromField = { name?: string; email: string; diff --git a/waspc/data/Generator/templates/sdk/email/index.ts b/waspc/data/Generator/templates/sdk/server/email/index.ts similarity index 98% rename from waspc/data/Generator/templates/sdk/email/index.ts rename to waspc/data/Generator/templates/sdk/server/email/index.ts index 3ed1f35b2b..9d83baf8cc 100644 --- a/waspc/data/Generator/templates/sdk/email/index.ts +++ b/waspc/data/Generator/templates/sdk/server/email/index.ts @@ -29,4 +29,5 @@ const emailProvider = { } as const; {=/ isDummyProviderUsed =} +// PUBLIC API export const emailSender = initEmailSender(emailProvider); diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts index 0957768722..a350e07b00 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts @@ -2,7 +2,7 @@ import { Router, Request, Response, NextFunction } from "express"; import { ProviderConfig } from "wasp/auth/providers/types"; -import type { EmailFromField } from "wasp/email/core/types"; +import type { EmailFromField } from "wasp/server/email/core/types"; import { getLoginRoute } from "../email/login.js"; import { getSignupRoute } from "../email/signup.js"; diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts index be0c685335..74b1168a26 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts @@ -11,7 +11,7 @@ import { isEmailResendAllowed, } from "wasp/server/auth/email/utils"; import { ensureValidEmail } from 'wasp/auth/validation'; -import type { EmailFromField } from 'wasp/email/core/types'; +import type { EmailFromField } from 'wasp/server/email/core/types'; import { GetPasswordResetEmailContentFn } from 'wasp/server/auth/email'; import { HttpError } from 'wasp/server'; diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts index 8aab2e3019..a22a7efb66 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { EmailFromField } from "wasp/email/core/types"; +import { EmailFromField } from "wasp/server/email/core/types"; import { createUser, createProviderId, diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts index fc6847cb59..ba04768d7e 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts @@ -1,8 +1,6 @@ import { hashPassword } from './password.js' import { verify } from './jwt.js' -import AuthError from 'wasp/core/AuthError' -import HttpError from 'wasp/core/HttpError' -import { prisma } from 'wasp/server' +import { prisma, HttpError, AuthError } from 'wasp/server' import { sleep } from 'wasp/server/utils' import { type User, diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts index d7cda4e29f..637f4203fd 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts @@ -1,4 +1,4 @@ -import HttpError from 'wasp/core/HttpError'; +import { HttpError } from 'wasp/server'; export const PASSWORD_FIELD = 'password'; const USERNAME_FIELD = 'username'; diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json index 5471c7a681..10857f880c 100644 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json @@ -8,8 +8,6 @@ "types": "tsc --declaration --emitDeclarationOnly --stripInternal --declarationDir dist" }, "exports": { - "./core/HttpError": "./dist/core/HttpError.js", - "./core/AuthError": "./dist/core/AuthError.js", "./core/config": "./dist/core/config.js", "./core/stitches.config": "./dist/core/stitches.config.js", "./core/storage": "./dist/core/storage.js", @@ -36,17 +34,14 @@ "./universal/url": "./dist/universal/url.js", "./universal/types": "./dist/universal/types.js", "./universal/validators": "./dist/universal/validators.js", - "./server/middleware": "./dist/server/middleware/index.js", "./server/utils": "./dist/server/utils.js", "./server/actions": "./dist/server/actions/index.js", "./server/queries": "./dist/server/queries/index.js", "./server/auth/email": "./dist/server/auth/email/index.js", - "./dbSeed/types": "./dist/dbSeed/types.js", "./test": "./dist/test/index.js", "./test/*": "./dist/test/*.js", "./server/crud/*": "./dist/server/crud/*", - "./email": "./dist/email/index.js", - "./email/core/types": "./dist/email/core/types.js", + "./server/email/core/types": "./dist/server/email/core/types.js", "./server/auth/email/utils": "./dist/server/auth/email/utils.js", "./jobs/*": "./dist/jobs/*.js", "./jobs/pgBoss/types": "./dist/jobs/pgBoss/types.js", @@ -56,6 +51,7 @@ "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", "./server/types": "./dist/server/types/index.js", + "./server/middleware": "./dist/server/middleware/index.js", "./server": "./dist/server/index.js", "./server/api": "./dist/server/api/index.js", @@ -64,7 +60,8 @@ "./client/auth": "./dist/client/auth/index.js", "./server/auth": "./dist/server/auth/index.js", "./server/crud": "./dist/server/crud/index.js", - "./client/crud": "./dist/client/crud/index.js" + "./client/crud": "./dist/client/crud/index.js", + "./server/email": "./dist/server/email/index.js" }, "typesVersions": { "*": { diff --git a/waspc/examples/todo-typescript/src/task/actions.ts b/waspc/examples/todo-typescript/src/task/actions.ts index 98a56e925a..fbeba40687 100644 --- a/waspc/examples/todo-typescript/src/task/actions.ts +++ b/waspc/examples/todo-typescript/src/task/actions.ts @@ -5,7 +5,7 @@ import type { DeleteTasks, } from 'wasp/server/actions/types' import type { Task } from 'wasp/entities' -import { emailSender } from 'wasp/email' +import { emailSender } from 'wasp/server/email' type CreateArgs = Pick diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index e1621de90a..3bd94f8d2e 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -38,15 +38,15 @@ import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.SdkGenerator.AuthG (genAuth) import Wasp.Generator.SdkGenerator.Client.AuthG (genNewClientAuth) -import Wasp.Generator.SdkGenerator.Client.CrudG (genNewClientrudApi) +import Wasp.Generator.SdkGenerator.Client.CrudG (genNewClientCrudApi) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) -import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSender) import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) +import Wasp.Generator.SdkGenerator.Server.EmailSenderG (depsRequiredByEmail, genNewEmailSenderApi) import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) @@ -114,12 +114,12 @@ genSdkReal spec = <++> genRouter spec <++> genMiddleware spec <++> genExportedTypesDir spec - <++> genEmailSender spec -- New API <++> genNewClientAuth spec <++> genNewServerApi spec <++> genNewServerCrudApi spec - <++> genNewClientrudApi spec + <++> genNewClientCrudApi spec + <++> genNewEmailSenderApi spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs index f73cf2c3ee..0819df3ee6 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/CrudG.hs @@ -1,5 +1,5 @@ module Wasp.Generator.SdkGenerator.Client.CrudG - ( genNewClientrudApi, + ( genNewClientCrudApi, ) where @@ -17,8 +17,8 @@ import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Util ((<++>)) -genNewClientrudApi :: AppSpec -> Generator [FileDraft] -genNewClientrudApi spec = +genNewClientCrudApi :: AppSpec -> Generator [FileDraft] +genNewClientCrudApi spec = if areThereAnyCruds then sequence diff --git a/waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs b/waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs index 6bb0e13cd2..0af2ae1656 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs @@ -25,7 +25,7 @@ data EmailSenderProvider = EmailSenderProvider data ProvidersDir providersDirInSdkTemplatesDir :: Path' (Rel C.SdkTemplatesDir) (Dir ProvidersDir) -providersDirInSdkTemplatesDir = [reldir|email/core/providers|] +providersDirInSdkTemplatesDir = [reldir|server/email/core/providers|] smtp :: EmailSenderProvider smtp = diff --git a/waspc/src/Wasp/Generator/SdkGenerator/EmailSenderG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/EmailSenderG.hs similarity index 91% rename from waspc/src/Wasp/Generator/SdkGenerator/EmailSenderG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Server/EmailSenderG.hs index 9a669247c9..fa6cb2b315 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/EmailSenderG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/EmailSenderG.hs @@ -1,4 +1,4 @@ -module Wasp.Generator.SdkGenerator.EmailSenderG where +module Wasp.Generator.SdkGenerator.Server.EmailSenderG where import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson @@ -17,8 +17,8 @@ import qualified Wasp.Generator.SdkGenerator.Common as C import qualified Wasp.Generator.SdkGenerator.EmailSender.Providers as Providers import Wasp.Util ((<++>)) -genEmailSender :: AppSpec -> Generator [FileDraft] -genEmailSender spec = case maybeEmailSender of +genNewEmailSenderApi :: AppSpec -> Generator [FileDraft] +genNewEmailSenderApi spec = case maybeEmailSender of Just emailSender -> sequence [ genIndex emailSender @@ -31,7 +31,7 @@ genEmailSender spec = case maybeEmailSender of genIndex :: EmailSender -> Generator FileDraft genIndex email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|email/index.ts|] + tmplPath = [relfile|server/email/index.ts|] tmplData = getEmailProvidersJson email genCore :: EmailSender -> Generator [FileDraft] @@ -46,13 +46,13 @@ genCore email = genCoreIndex :: EmailSender -> Generator FileDraft genCoreIndex email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|email/core/index.ts|] + tmplPath = [relfile|server/email/core/index.ts|] tmplData = getEmailProvidersJson email genCoreTypes :: EmailSender -> Generator FileDraft genCoreTypes email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|email/core/types.ts|] + tmplPath = [relfile|server/email/core/types.ts|] tmplData = object ["isDefaultFromFieldDefined" .= isDefaultFromFieldDefined] isDefaultFromFieldDefined = isJust defaultFromField @@ -61,7 +61,7 @@ genCoreTypes email = return $ C.mkTmplFdWithData tmplPath tmplData genCoreHelpers :: EmailSender -> Generator FileDraft genCoreHelpers email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|email/core/helpers.ts|] + tmplPath = [relfile|server/email/core/helpers.ts|] tmplData = object [ "defaultFromField" diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index fa227ff71c..a15a13f4dc 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -293,25 +293,25 @@ library Wasp.Generator.NpmInstall.Common Wasp.Generator.NpmInstall.InstalledNpmDepsLog Wasp.Generator.SdkGenerator - Wasp.Generator.SdkGenerator.ServerApiG Wasp.Generator.SdkGenerator.Auth.AuthFormsG Wasp.Generator.SdkGenerator.Auth.EmailAuthG - Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.Auth.LocalAuthG Wasp.Generator.SdkGenerator.Auth.OAuthAuthG Wasp.Generator.SdkGenerator.AuthG + Wasp.Generator.SdkGenerator.Client.AuthG + Wasp.Generator.SdkGenerator.Client.CrudG Wasp.Generator.SdkGenerator.Common - Wasp.Generator.SdkGenerator.RpcGenerator - Wasp.Generator.SdkGenerator.JobGenerator - Wasp.Generator.SdkGenerator.ServerOpsGenerator - Wasp.Generator.SdkGenerator.EmailSenderG + Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.EmailSender.Providers - Wasp.Generator.SdkGenerator.WebSocketGenerator + Wasp.Generator.SdkGenerator.JobGenerator Wasp.Generator.SdkGenerator.RouterGenerator - Wasp.Generator.SdkGenerator.Client.AuthG + Wasp.Generator.SdkGenerator.RpcGenerator Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.SdkGenerator.Server.CrudG - Wasp.Generator.SdkGenerator.Client.CrudG + Wasp.Generator.SdkGenerator.Server.EmailSenderG + Wasp.Generator.SdkGenerator.ServerApiG + Wasp.Generator.SdkGenerator.ServerOpsGenerator + Wasp.Generator.SdkGenerator.WebSocketGenerator Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG From 64deb8b69466575d05dbaebcf6aa852097d34b4c Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 31 Jan 2024 12:04:33 +0100 Subject: [PATCH 12/19] Implement new wasp/server/jobs API (#1702) --- .../data/Generator/templates/sdk/package.json | 9 +- .../sdk/{ => server}/jobs/_jobTypes.ts | 4 +- .../templates/sdk/server/jobs/index.ts | 5 + .../sdk/{ => server}/jobs/pgBoss/types.ts | 1 + .../server/src/jobs/core/pgBoss/pgBossJob.ts | 2 +- .../.wasp/out/sdk/wasp/package.json | 104 ------------------ .../todo-typescript/src/jobs/print.ts | 9 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 6 +- .../SdkGenerator/{ => Server}/JobGenerator.hs | 50 ++++++--- .../Generator/ServerGenerator/JobGenerator.hs | 2 +- waspc/waspc.cabal | 2 +- 11 files changed, 59 insertions(+), 135 deletions(-) rename waspc/data/Generator/templates/sdk/{ => server}/jobs/_jobTypes.ts (82%) create mode 100644 waspc/data/Generator/templates/sdk/server/jobs/index.ts rename waspc/data/Generator/templates/sdk/{ => server}/jobs/pgBoss/types.ts (95%) delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json rename waspc/src/Wasp/Generator/SdkGenerator/{ => Server}/JobGenerator.hs (54%) diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index bf9db83f49..c7477e7a26 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -84,11 +84,11 @@ "./server/email/core/types": "./dist/server/email/core/types.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email/utils": "./dist/server/auth/email/utils.js", - {=! Parts are used by users and documented (types), other parts are used by the framework code (entities). =} - "./jobs/*": "./dist/jobs/*.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./server/jobs/*": "./dist/server/jobs/*.js", {=! Used by our code, uncodumented (but accessible) for users. =} {=! Todo(filip): This export becomes problematic once we start supporting different executors =} - "./jobs/pgBoss/types": "./dist/jobs/pgBoss/types.js", + "./server/jobs/pgBoss/types": "./dist/server/jobs/pgBoss/types.js", {=! Used by users, documented. =} "./router": "./dist/router/index.js", {=! Used by users, documented. =} @@ -118,7 +118,8 @@ "./server/auth": "./dist/server/auth/index.js", "./server/crud": "./dist/server/crud/index.js", "./client/crud": "./dist/client/crud/index.js", - "./server/email": "./dist/server/email/index.js" + "./server/email": "./dist/server/email/index.js", + "./server/jobs": "./dist/server/jobs/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts b/waspc/data/Generator/templates/sdk/server/jobs/_jobTypes.ts similarity index 82% rename from waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts rename to waspc/data/Generator/templates/sdk/server/jobs/_jobTypes.ts index 6c601a650e..87660de6f1 100644 --- a/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts +++ b/waspc/data/Generator/templates/sdk/server/jobs/_jobTypes.ts @@ -3,12 +3,12 @@ import { prisma } from 'wasp/server' import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' import { type JobFn } from '{= jobExecutorTypesImportPath =}' -{=! Used in framework code, shouldn't be public =} +// PRIVATE API export const entities = { {=# entities =} {= name =}: prisma.{= prismaIdentifier =}, {=/ entities =} }; -{=! Used by users, should be public =} +// PUBLIC API export type {= typeName =} = JobFn diff --git a/waspc/data/Generator/templates/sdk/server/jobs/index.ts b/waspc/data/Generator/templates/sdk/server/jobs/index.ts new file mode 100644 index 0000000000..296811e957 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/jobs/index.ts @@ -0,0 +1,5 @@ +{{={= =}=}} + +{=# jobs =} +export type { {= typeName =} } from './{= jobName =}' +{=/ jobs =} diff --git a/waspc/data/Generator/templates/sdk/jobs/pgBoss/types.ts b/waspc/data/Generator/templates/sdk/server/jobs/pgBoss/types.ts similarity index 95% rename from waspc/data/Generator/templates/sdk/jobs/pgBoss/types.ts rename to waspc/data/Generator/templates/sdk/server/jobs/pgBoss/types.ts index 3e9e685bc5..4ebb34b61b 100644 --- a/waspc/data/Generator/templates/sdk/jobs/pgBoss/types.ts +++ b/waspc/data/Generator/templates/sdk/server/jobs/pgBoss/types.ts @@ -1,6 +1,7 @@ import { PrismaDelegate } from 'wasp/server/_types' import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' +// PRIVATE API export type JobFn< Input extends JSONObject, Output extends JSONValue | void, diff --git a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts index 34b36ae470..c4742bbb97 100644 --- a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts +++ b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts @@ -3,7 +3,7 @@ import { pgBossStarted } from './pgBoss.js' import { Job, SubmittedJob } from '../job.js' import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' import { PrismaDelegate } from 'wasp/server/_types' -import type { JobFn } from 'wasp/jobs/pgBoss/types' +import type { JobFn } from 'wasp/server/jobs/pgBoss/types' export const PG_BOSS_EXECUTOR_NAME = Symbol('PgBoss') diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json deleted file mode 100644 index 10857f880c..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/package.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "name": "wasp", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "types": "tsc --declaration --emitDeclarationOnly --stripInternal --declarationDir dist" - }, - "exports": { - "./core/config": "./dist/core/config.js", - "./core/stitches.config": "./dist/core/stitches.config.js", - "./core/storage": "./dist/core/storage.js", - "./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/helpers/user": "./dist/auth/helpers/user.js", - "./auth/session": "./dist/auth/session.js", - "./auth/providers/types": "./dist/auth/providers/types.js", - "./auth/utils": "./dist/auth/utils.js", - "./auth/password": "./dist/auth/password.js", - "./auth/jwt": "./dist/auth/jwt.js", - "./auth/validation": "./dist/auth/validation.js", - "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", - "./api/events": "./dist/api/events.js", - "./operations": "./dist/operations/index.js", - "./ext-src/*": "./dist/ext-src/*.js", - "./operations/*": "./dist/operations/*", - "./universal/url": "./dist/universal/url.js", - "./universal/types": "./dist/universal/types.js", - "./universal/validators": "./dist/universal/validators.js", - "./server/utils": "./dist/server/utils.js", - "./server/actions": "./dist/server/actions/index.js", - "./server/queries": "./dist/server/queries/index.js", - "./server/auth/email": "./dist/server/auth/email/index.js", - "./test": "./dist/test/index.js", - "./test/*": "./dist/test/*.js", - "./server/crud/*": "./dist/server/crud/*", - "./server/email/core/types": "./dist/server/email/core/types.js", - "./server/auth/email/utils": "./dist/server/auth/email/utils.js", - "./jobs/*": "./dist/jobs/*.js", - "./jobs/pgBoss/types": "./dist/jobs/pgBoss/types.js", - "./router": "./dist/router/index.js", - "./server/webSocket": "./dist/server/webSocket/index.js", - "./webSocket": "./dist/webSocket/index.js", - "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", - - "./server/types": "./dist/server/types/index.js", - "./server/middleware": "./dist/server/middleware/index.js", - - "./server": "./dist/server/index.js", - "./server/api": "./dist/server/api/index.js", - "./client/api": "./dist/api/index.js", - "./auth": "./dist/auth/index.js", - "./client/auth": "./dist/client/auth/index.js", - "./server/auth": "./dist/server/auth/index.js", - "./server/crud": "./dist/server/crud/index.js", - "./client/crud": "./dist/client/crud/index.js", - "./server/email": "./dist/server/email/index.js" - }, - "typesVersions": { - "*": { - "client/api": ["api/index.ts"] - } - }, - "license": "ISC", - "include": [ - "src/**/*" - ], - "dependencies": {"@prisma/client": "4.16.2", - "prisma": "4.16.2", - "@tanstack/react-query": "^4.29.0", - "axios": "^1.4.0", - "express": "~4.18.1", - "jsonwebtoken": "^8.5.1", - "mitt": "3.0.0", - "react": "^18.2.0", - "lodash.merge": "^4.6.2", - "react-router-dom": "^5.3.3", - "react-hook-form": "^7.45.4", - "secure-password": "^4.0.0", - "superjson": "^1.12.2", - "@types/express-serve-static-core": "^4.17.13", - "@stitches/react": "^1.2.8", - "lucia": "^3.0.0-beta.14", - "@lucia-auth/adapter-prisma": "^4.0.0-beta.9", - "socket.io": "^4.6.1", - "socket.io-client": "^4.6.1", - "@socket.io/component-emitter": "^4.0.0", - "vitest": "^1.2.1", - "@vitest/ui": "^1.2.1", - "jsdom": "^21.1.1", - "@testing-library/react": "^14.1.2", - "@testing-library/jest-dom": "^6.3.0", - "msw": "^1.1.0" -}, - "devDependencies": {"@tsconfig/node18": "latest" -} -} diff --git a/waspc/examples/todo-typescript/src/jobs/print.ts b/waspc/examples/todo-typescript/src/jobs/print.ts index a98941b9f3..4b9d79145b 100644 --- a/waspc/examples/todo-typescript/src/jobs/print.ts +++ b/waspc/examples/todo-typescript/src/jobs/print.ts @@ -1,9 +1,8 @@ -import { PrintTimeAndNumberOfTasks } from "wasp/jobs/PrintTimeAndNumberOfTasks"; - +import { PrintTimeAndNumberOfTasks } from 'wasp/server/jobs' export const printTimeAndNumberOfTasks: PrintTimeAndNumberOfTasks< {}, void > = async (data, context) => { - const count = await context.entities.Task.count(); - console.log(Date.now(), "Number of tasks:", count); -}; + const count = await context.entities.Task.count() + console.log(Date.now(), 'Number of tasks:', count) +} diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 3bd94f8d2e..d0556bb7cc 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -41,12 +41,12 @@ import Wasp.Generator.SdkGenerator.Client.AuthG (genNewClientAuth) import Wasp.Generator.SdkGenerator.Client.CrudG (genNewClientCrudApi) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) -import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) import Wasp.Generator.SdkGenerator.Server.EmailSenderG (depsRequiredByEmail, genNewEmailSenderApi) +import Wasp.Generator.SdkGenerator.Server.JobGenerator (genNewJobsApi) import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) @@ -95,7 +95,7 @@ genSdkReal spec = genFileCopy [relfile|types/index.ts|], genFileCopy [relfile|test/vitest/helpers.tsx|], genFileCopy [relfile|test/index.ts|], - genFileCopy [relfile|jobs/pgBoss/types.ts|], + genFileCopy [relfile|server/jobs/pgBoss/types.ts|], genServerConfigFile spec, genTsConfigJson, genServerUtils spec, @@ -108,7 +108,6 @@ genSdkReal spec = <++> genExternalCodeDir (AS.externalCodeFiles spec) <++> genEntitiesAndServerTypesDirs spec <++> genCrud spec - <++> genJobTypes spec <++> genServerApi spec <++> genWebSockets spec <++> genRouter spec @@ -120,6 +119,7 @@ genSdkReal spec = <++> genNewServerCrudApi spec <++> genNewClientCrudApi spec <++> genNewEmailSenderApi spec + <++> genNewJobsApi spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/JobGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/JobGenerator.hs similarity index 54% rename from waspc/src/Wasp/Generator/SdkGenerator/JobGenerator.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Server/JobGenerator.hs index 96ea4ae074..48699c9495 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/JobGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/JobGenerator.hs @@ -1,4 +1,9 @@ -module Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes, getImportPathForJobName, getJobExecutorTypesImportPath) where +module Wasp.Generator.SdkGenerator.Server.JobGenerator + ( genNewJobsApi, + getImportPathForJobName, + getJobExecutorTypesImportPath, + ) +where import Data.Aeson (object, (.=)) import Data.Maybe (fromJust) @@ -16,20 +21,37 @@ import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Util -genJobTypes :: AppSpec -> Generator [FileDraft] -genJobTypes spec = case getJobs spec of - [] -> return [] - jobs -> return $ map genJobType jobs +genNewJobsApi :: AppSpec -> Generator [FileDraft] +genNewJobsApi spec = + case getJobs spec of + [] -> return [] + jobs -> + sequence + [ genIndexTs jobs + ] + <++> mapM genJobType jobs + +genIndexTs :: [(String, Job)] -> Generator FileDraft +genIndexTs jobs = return $ C.mkTmplFdWithData tmplFile tmplData + where + tmplFile = [relfile|server/jobs/index.ts|] + tmplData = object ["jobs" .= map getJobTmplData jobs] + getJobTmplData (jobName, _) = + object + [ "typeName" .= toUpperFirst jobName, + "jobName" .= jobName + ] -genJobType :: (String, Job) -> FileDraft +genJobType :: (String, Job) -> Generator FileDraft genJobType (jobName, job) = - C.mkTmplFdWithDstAndData - tmplFile - dstFile - $ Just tmplData + return $ + C.mkTmplFdWithDstAndData + tmplFile + dstFile + $ Just tmplData where - tmplFile = [relfile|jobs/_jobTypes.ts|] - dstFile = [reldir|jobs|] fromJust (SP.parseRelFile $ jobName ++ ".ts") + tmplFile = [relfile|server/jobs/_jobTypes.ts|] + dstFile = [reldir|server/jobs|] fromJust (SP.parseRelFile $ jobName ++ ".ts") tmplData = object [ "typeName" .= toUpperFirst jobName, @@ -40,9 +62,9 @@ genJobType (jobName, job) = jobExecutorTypesImportPath = getJobExecutorTypesImportPath (J.executor job) getImportPathForJobName :: String -> Path Posix (Rel d) File' -getImportPathForJobName jobName = makeSdkImportPath $ [reldirP|jobs|] fromJust (SP.parseRelFileP jobName) +getImportPathForJobName jobName = makeSdkImportPath $ [reldirP|server/jobs|] fromJust (SP.parseRelFileP jobName) -- | We are importing relevant types per executor e.g. JobFn, this functions maps -- the executor to the import path of the relevant types. getJobExecutorTypesImportPath :: JobExecutor -> Path Posix (Rel r) File' -getJobExecutorTypesImportPath PgBoss = makeSdkImportPath [relfileP|jobs/pgBoss/types|] +getJobExecutorTypesImportPath PgBoss = makeSdkImportPath [relfileP|server/jobs/pgBoss/types|] diff --git a/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs index 17de8feedc..3b98ba177f 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs @@ -34,7 +34,7 @@ import Wasp.AppSpec.Util (isPgBossJobExecutorUsed) import Wasp.Generator.Common (ServerRootDir) import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.SdkGenerator.JobGenerator (getImportPathForJobName, getJobExecutorTypesImportPath) +import Wasp.Generator.SdkGenerator.Server.JobGenerator (getImportPathForJobName, getJobExecutorTypesImportPath) import Wasp.Generator.ServerGenerator.Common ( ServerTemplatesDir, srcDirInServerTemplatesDir, diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index a15a13f4dc..03063900a8 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -303,12 +303,12 @@ library Wasp.Generator.SdkGenerator.Common Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.EmailSender.Providers - Wasp.Generator.SdkGenerator.JobGenerator Wasp.Generator.SdkGenerator.RouterGenerator Wasp.Generator.SdkGenerator.RpcGenerator Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.SdkGenerator.Server.CrudG Wasp.Generator.SdkGenerator.Server.EmailSenderG + Wasp.Generator.SdkGenerator.Server.JobGenerator Wasp.Generator.SdkGenerator.ServerApiG Wasp.Generator.SdkGenerator.ServerOpsGenerator Wasp.Generator.SdkGenerator.WebSocketGenerator From d1f544e2dc4fa44db8b7a5341360288a667dd7ea Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 31 Jan 2024 12:14:50 +0100 Subject: [PATCH 13/19] Implement the wasp/client/router API (#1703) --- .../Generator/templates/react-app/src/router.tsx | 2 +- .../templates/sdk/{ => client}/router/Link.tsx | 1 + .../templates/sdk/{ => client}/router/index.ts | 3 +++ .../sdk/{ => client}/router/linkHelpers.ts | 1 + .../templates/sdk/{ => client}/router/types.ts | 5 +++++ waspc/data/Generator/templates/sdk/package.json | 5 ++--- waspc/examples/todo-typescript/src/user/auth.tsx | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 4 ++-- .../SdkGenerator/{ => Client}/RouterGenerator.hs | 16 ++++++++-------- waspc/waspc.cabal | 2 +- 10 files changed, 25 insertions(+), 16 deletions(-) rename waspc/data/Generator/templates/sdk/{ => client}/router/Link.tsx (97%) rename waspc/data/Generator/templates/sdk/{ => client}/router/index.ts (95%) rename waspc/data/Generator/templates/sdk/{ => client}/router/linkHelpers.ts (98%) rename waspc/data/Generator/templates/sdk/{ => client}/router/types.ts (91%) rename waspc/src/Wasp/Generator/SdkGenerator/{ => Client}/RouterGenerator.hs (74%) diff --git a/waspc/data/Generator/templates/react-app/src/router.tsx b/waspc/data/Generator/templates/react-app/src/router.tsx index 8379bfa3e1..1113b1e6f1 100644 --- a/waspc/data/Generator/templates/react-app/src/router.tsx +++ b/waspc/data/Generator/templates/react-app/src/router.tsx @@ -17,7 +17,7 @@ import createAuthRequiredPage from "./auth/pages/createAuthRequiredPage" import OAuthCodeExchange from "./auth/pages/OAuthCodeExchange" {=/ isExternalAuthEnabled =} -import { routes } from 'wasp/router' +import { routes } from 'wasp/client/router' export const routeNameToRouteComponent = { {=# routes =} diff --git a/waspc/data/Generator/templates/sdk/router/Link.tsx b/waspc/data/Generator/templates/sdk/client/router/Link.tsx similarity index 97% rename from waspc/data/Generator/templates/sdk/router/Link.tsx rename to waspc/data/Generator/templates/sdk/client/router/Link.tsx index 571262cd57..2215ddc639 100644 --- a/waspc/data/Generator/templates/sdk/router/Link.tsx +++ b/waspc/data/Generator/templates/sdk/client/router/Link.tsx @@ -5,6 +5,7 @@ import { type Routes } from './index' type RouterLinkProps = Parameters[0] +// PUBLIC API export function Link( { to, params, search, hash, ...restOfProps }: Omit & { diff --git a/waspc/data/Generator/templates/sdk/router/index.ts b/waspc/data/Generator/templates/sdk/client/router/index.ts similarity index 95% rename from waspc/data/Generator/templates/sdk/router/index.ts rename to waspc/data/Generator/templates/sdk/client/router/index.ts index b1dfa68ab8..91e2ccb954 100644 --- a/waspc/data/Generator/templates/sdk/router/index.ts +++ b/waspc/data/Generator/templates/sdk/client/router/index.ts @@ -6,6 +6,7 @@ import type { ParamValue, } from './types' +// PUBLIC API export const routes = { {=# routes =} {= name =}: { @@ -26,6 +27,8 @@ export const routes = { {=/ routes =} } as const; +// PRIVATE API export type Routes = RouteDefinitionsToRoutes +// PUBLIC API export { Link } from './Link' diff --git a/waspc/data/Generator/templates/sdk/router/linkHelpers.ts b/waspc/data/Generator/templates/sdk/client/router/linkHelpers.ts similarity index 98% rename from waspc/data/Generator/templates/sdk/router/linkHelpers.ts rename to waspc/data/Generator/templates/sdk/client/router/linkHelpers.ts index 291d8238a3..e10cba9281 100644 --- a/waspc/data/Generator/templates/sdk/router/linkHelpers.ts +++ b/waspc/data/Generator/templates/sdk/client/router/linkHelpers.ts @@ -1,5 +1,6 @@ import type { Params, Search } from "./types"; +// PRIVATE API export function interpolatePath( path: string, params?: Params, diff --git a/waspc/data/Generator/templates/sdk/router/types.ts b/waspc/data/Generator/templates/sdk/client/router/types.ts similarity index 91% rename from waspc/data/Generator/templates/sdk/router/types.ts rename to waspc/data/Generator/templates/sdk/client/router/types.ts index 0688d0bbba..ffaeb4fe01 100644 --- a/waspc/data/Generator/templates/sdk/router/types.ts +++ b/waspc/data/Generator/templates/sdk/client/router/types.ts @@ -1,13 +1,18 @@ +// PRIVATE API export type RouteDefinitionsToRoutes = RouteDefinitionsToRoutesObj[keyof RouteDefinitionsToRoutesObj] + // PRIVATE API export type OptionalRouteOptions = { search?: Search hash?: string } +// PRIVATE API export type ParamValue = string | number +// PRIVATE API export type Params = { [name: string]: ParamValue } +// PRIVATE API export type Search = | string[][] | Record diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index c7477e7a26..b3f7feeea7 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -90,8 +90,6 @@ {=! Todo(filip): This export becomes problematic once we start supporting different executors =} "./server/jobs/pgBoss/types": "./dist/server/jobs/pgBoss/types.js", {=! Used by users, documented. =} - "./router": "./dist/router/index.js", - {=! Used by users, documented. =} "./server/webSocket": "./dist/server/webSocket/index.js", {=! Used by users, documented. =} "./webSocket": "./dist/webSocket/index.js", @@ -119,7 +117,8 @@ "./server/crud": "./dist/server/crud/index.js", "./client/crud": "./dist/client/crud/index.js", "./server/email": "./dist/server/email/index.js", - "./server/jobs": "./dist/server/jobs/index.js" + "./server/jobs": "./dist/server/jobs/index.js", + "./client/router": "./dist/client/router/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/examples/todo-typescript/src/user/auth.tsx b/waspc/examples/todo-typescript/src/user/auth.tsx index 9a15825461..272a16fbdb 100644 --- a/waspc/examples/todo-typescript/src/user/auth.tsx +++ b/waspc/examples/todo-typescript/src/user/auth.tsx @@ -19,7 +19,7 @@ import { // signInUrl as googleSignInUrl, // } from "wasp/auth/helpers/Google"; -import { Link, routes } from 'wasp/router' +import { Link, routes } from 'wasp/client/router' export function SignupPage() { return ( diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index d0556bb7cc..d5221ab229 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -39,9 +39,9 @@ import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.SdkGenerator.AuthG (genAuth) import Wasp.Generator.SdkGenerator.Client.AuthG (genNewClientAuth) import Wasp.Generator.SdkGenerator.Client.CrudG (genNewClientCrudApi) +import Wasp.Generator.SdkGenerator.Client.RouterGenerator (genNewClientRouterApi) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) -import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) @@ -110,7 +110,6 @@ genSdkReal spec = <++> genCrud spec <++> genServerApi spec <++> genWebSockets spec - <++> genRouter spec <++> genMiddleware spec <++> genExportedTypesDir spec -- New API @@ -120,6 +119,7 @@ genSdkReal spec = <++> genNewClientCrudApi spec <++> genNewEmailSenderApi spec <++> genNewJobsApi spec + <++> genNewClientRouterApi spec where genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/RouterGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/RouterGenerator.hs similarity index 74% rename from waspc/src/Wasp/Generator/SdkGenerator/RouterGenerator.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Client/RouterGenerator.hs index c5f89fae90..57875a355f 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/RouterGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/RouterGenerator.hs @@ -1,5 +1,5 @@ -module Wasp.Generator.SdkGenerator.RouterGenerator - ( genRouter, +module Wasp.Generator.SdkGenerator.Client.RouterGenerator + ( genNewClientRouterApi, ) where @@ -14,19 +14,19 @@ import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Util.WebRouterPath (Param (Optional, Required), extractPathParams) -genRouter :: AppSpec -> Generator [FileDraft] -genRouter spec = +genNewClientRouterApi :: AppSpec -> Generator [FileDraft] +genNewClientRouterApi spec = sequence [ genRouterTsx spec, - genFileCopy [relfile|router/types.ts|], - genFileCopy [relfile|router/linkHelpers.ts|], - genFileCopy [relfile|router/Link.tsx|] + genFileCopy [relfile|client/router/types.ts|], + genFileCopy [relfile|client/router/linkHelpers.ts|], + genFileCopy [relfile|client/router/Link.tsx|] ] where genFileCopy = return . C.mkTmplFd genRouterTsx :: AppSpec -> Generator FileDraft -genRouterTsx spec = return $ C.mkTmplFdWithData [relfile|router/index.ts|] tmplData +genRouterTsx spec = return $ C.mkTmplFdWithData [relfile|client/router/index.ts|] tmplData where tmplData = object ["routes" .= map createRouteTemplateData (AS.getRoutes spec)] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 03063900a8..00f9282f18 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -300,10 +300,10 @@ library Wasp.Generator.SdkGenerator.AuthG Wasp.Generator.SdkGenerator.Client.AuthG Wasp.Generator.SdkGenerator.Client.CrudG + Wasp.Generator.SdkGenerator.Client.RouterGenerator Wasp.Generator.SdkGenerator.Common Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.EmailSender.Providers - Wasp.Generator.SdkGenerator.RouterGenerator Wasp.Generator.SdkGenerator.RpcGenerator Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.SdkGenerator.Server.CrudG From 1ded74f5379564df24eb0cbb7fec9cc580fbfbf1 Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 31 Jan 2024 12:31:52 +0100 Subject: [PATCH 14/19] Implement wasp/client/test API (#1704) --- .../Generator/templates/sdk/{ => client}/test/index.ts | 0 .../templates/sdk/{ => client}/test/vitest/helpers.tsx | 5 +++++ waspc/data/Generator/templates/sdk/package.json | 7 +++---- waspc/examples/todo-typescript/src/Todo.test.tsx | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 4 ++-- 5 files changed, 11 insertions(+), 7 deletions(-) rename waspc/data/Generator/templates/sdk/{ => client}/test/index.ts (100%) rename waspc/data/Generator/templates/sdk/{ => client}/test/vitest/helpers.tsx (97%) diff --git a/waspc/data/Generator/templates/sdk/test/index.ts b/waspc/data/Generator/templates/sdk/client/test/index.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/test/index.ts rename to waspc/data/Generator/templates/sdk/client/test/index.ts diff --git a/waspc/data/Generator/templates/sdk/test/vitest/helpers.tsx b/waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx similarity index 97% rename from waspc/data/Generator/templates/sdk/test/vitest/helpers.tsx rename to waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx index 6c4eadbec3..aa2eba4fa8 100644 --- a/waspc/data/Generator/templates/sdk/test/vitest/helpers.tsx +++ b/waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx @@ -10,15 +10,19 @@ import { Query } from 'wasp/rpc' import config from 'wasp/core/config' import { HttpMethod, Route } from 'wasp/types' +// PRIVATE API export type { Route } from 'wasp/types' +// PRIVATE API export type MockQuery = ( query: Query, resJson: MockOutput ) => void +// PRIVATE API export type MockApi = (route: Route, resJson: unknown) => void +// PUBLIC API // Inspired by the Tanstack React Query helper: // https://github.com/TanStack/query/blob/4ae99561ca3383d6de3f4aad656a49ba4a17b57a/packages/react-query/src/__tests__/utils.tsx#L7-L26 export function renderInContext(ui: ReactElement): RenderResult { @@ -39,6 +43,7 @@ export function renderInContext(ui: ReactElement): RenderResult { } } +// PUBLIC API export function mockServer(): { server: SetupServer mockQuery: MockQuery diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index b3f7feeea7..e853eed44a 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -74,10 +74,8 @@ "./server/queries": "./dist/server/queries/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email": "./dist/server/auth/email/index.js", - {=! Used by users, documented. =} - "./test": "./dist/test/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} - "./test/*": "./dist/test/*.js", + "./client/test/*": "./dist/client/test/*.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/crud/*": "./dist/server/crud/*", {=! Used by our code, uncodumented (but accessible) for users. =} @@ -118,7 +116,8 @@ "./client/crud": "./dist/client/crud/index.js", "./server/email": "./dist/server/email/index.js", "./server/jobs": "./dist/server/jobs/index.js", - "./client/router": "./dist/client/router/index.js" + "./client/router": "./dist/client/router/index.js", + "./client/test": "./dist/client/test/index.js" }, {=! TypeScript doesn't care about the redirects we define above in "exports" field; those diff --git a/waspc/examples/todo-typescript/src/Todo.test.tsx b/waspc/examples/todo-typescript/src/Todo.test.tsx index ce54febd76..87bff2532d 100644 --- a/waspc/examples/todo-typescript/src/Todo.test.tsx +++ b/waspc/examples/todo-typescript/src/Todo.test.tsx @@ -1,7 +1,7 @@ import { test, expect } from 'vitest' import { screen } from '@testing-library/react' -import { mockServer, renderInContext } from 'wasp/test' +import { mockServer, renderInContext } from 'wasp/client/test' import { getTasks } from 'wasp/rpc/queries' import { Todo, areThereAnyTasks } from './Todo' import { MainPage } from './MainPage' diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index d5221ab229..719f89851d 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -93,9 +93,9 @@ genSdkReal spec = genFileCopy [relfile|server/HttpError.ts|], genFileCopy [relfile|server/AuthError.ts|], genFileCopy [relfile|types/index.ts|], - genFileCopy [relfile|test/vitest/helpers.tsx|], - genFileCopy [relfile|test/index.ts|], genFileCopy [relfile|server/jobs/pgBoss/types.ts|], + genFileCopy [relfile|client/test/vitest/helpers.tsx|], + genFileCopy [relfile|client/test/index.ts|], genServerConfigFile spec, genTsConfigJson, genServerUtils spec, From 9ccf4f8a0812b0567ff03ad7a8ec79167f0034f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Sodi=C4=87?= Date: Wed, 31 Jan 2024 15:45:43 +0100 Subject: [PATCH 15/19] Remove generated files from git --- .../.wasp/out/sdk/wasp/api/index.ts | 108 ------ .../.wasp/out/sdk/wasp/auth/forms/Auth.tsx | 96 ------ .../.wasp/out/sdk/wasp/auth/forms/Login.tsx | 18 - .../.wasp/out/sdk/wasp/auth/forms/Signup.tsx | 24 -- .../out/sdk/wasp/auth/forms/internal/Form.tsx | 102 ------ .../sdk/wasp/auth/forms/internal/Message.tsx | 21 -- .../forms/internal/common/LoginSignupForm.tsx | 184 ---------- .../.wasp/out/sdk/wasp/auth/forms/types.ts | 49 --- .../.wasp/out/sdk/wasp/auth/helpers/user.ts | 15 - .../.wasp/out/sdk/wasp/auth/logout.ts | 18 - .../out/sdk/wasp/auth/providers/types.ts | 46 --- .../.wasp/out/sdk/wasp/auth/types.ts | 2 - .../.wasp/out/sdk/wasp/auth/useAuth.ts | 40 --- .../.wasp/out/sdk/wasp/auth/user.ts | 27 -- .../.wasp/out/sdk/wasp/auth/utils.ts | 321 ------------------ .../.wasp/out/sdk/wasp/auth/validation.ts | 83 ----- .../.wasp/out/sdk/wasp/operations/index.ts | 22 -- .../.wasp/out/sdk/wasp/server/_types/index.ts | 99 ------ .../out/sdk/wasp/server/actions/index.ts | 50 --- .../out/sdk/wasp/server/queries/index.ts | 14 - .../.wasp/out/sdk/wasp/server/utils.ts | 67 ---- 21 files changed, 1406 deletions(-) delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts delete mode 100644 waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts deleted file mode 100644 index d066bd5448..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/api/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import axios, { type AxiosError } from 'axios' - -import config from 'wasp/core/config' -import { storage } from 'wasp/core/storage' -import { apiEventsEmitter } from './events.js' - -// PUBLIC API -export const api = axios.create({ - baseURL: config.apiUrl, -}) - -const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' - -let waspAppAuthSessionId = storage.get(WASP_APP_AUTH_SESSION_ID_NAME) as string | undefined - -// PRIVATE API (sdk) -export function setSessionId(sessionId: string): void { - waspAppAuthSessionId = sessionId - storage.set(WASP_APP_AUTH_SESSION_ID_NAME, sessionId) - apiEventsEmitter.emit('sessionId.set') -} - -// PRIVATE API (sdk) -export function getSessionId(): string | undefined { - return waspAppAuthSessionId -} - -// PRIVATE API (sdk) -export function clearSessionId(): void { - waspAppAuthSessionId = undefined - storage.remove(WASP_APP_AUTH_SESSION_ID_NAME) - apiEventsEmitter.emit('sessionId.clear') -} - -// PRIVATE API (sdk) -export function removeLocalUserData(): void { - waspAppAuthSessionId = undefined - storage.clear() - apiEventsEmitter.emit('sessionId.clear') -} - -api.interceptors.request.use((request) => { - const sessionId = getSessionId() - if (sessionId) { - request.headers['Authorization'] = `Bearer ${sessionId}` - } - return request -}) - -api.interceptors.response.use(undefined, (error) => { - if (error.response?.status === 401) { - clearSessionId() - } - return Promise.reject(error) -}) - -// This handler will run on other tabs (not the active one calling API functions), -// and will ensure they know about auth session ID changes. -// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event -// "Note: This won't work on the same page that is making the changes — it is really a way -// for other pages on the domain using the storage to sync any changes that are made." -window.addEventListener('storage', (event) => { - if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) { - if (!!event.newValue) { - waspAppAuthSessionId = event.newValue - apiEventsEmitter.emit('sessionId.set') - } else { - waspAppAuthSessionId = undefined - apiEventsEmitter.emit('sessionId.clear') - } - } -}) - -// PRIVATE API (sdk) -/** - * Takes an error returned by the app's API (as returned by axios), and transforms into a more - * standard format to be further used by the client. It is also assumed that given API - * error has been formatted as implemented by HttpError on the server. - */ -export function handleApiError(error: AxiosError<{ message?: string, data?: unknown }>): void { - if (error?.response) { - // If error came from HTTP response, we capture most informative message - // and also add .statusCode information to it. - // If error had JSON response, we assume it is of format { message, data } and - // add that info to the error. - // TODO: We might want to use HttpError here instead of just Error, since - // HttpError is also used on server to throw errors like these. - // That would require copying HttpError code to web-app also and using it here. - const responseJson = error.response?.data - const responseStatusCode = error.response.status - throw new WaspHttpError(responseStatusCode, responseJson?.message ?? error.message, responseJson) - } else { - // If any other error, we just propagate it. - throw error - } -} - -class WaspHttpError extends Error { - statusCode: number - - data: unknown - - constructor (statusCode: number, message: string, data: unknown) { - super(message) - this.statusCode = statusCode - this.data = data - } -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx deleted file mode 100644 index c612e33865..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Auth.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useState, createContext } from 'react' -import { createTheme } from '@stitches/react' -import { styled } from 'wasp/core/stitches.config' - -import { - type State, - type CustomizationOptions, - type ErrorMessage, - type AdditionalSignupFields, -} from './types' -import { LoginSignupForm } from './internal/common/LoginSignupForm' -import { MessageError, MessageSuccess } from './internal/Message' -import { ForgotPasswordForm } from './internal/email/ForgotPasswordForm' -import { ResetPasswordForm } from './internal/email/ResetPasswordForm' -import { VerifyEmailForm } from './internal/email/VerifyEmailForm' - -const logoStyle = { - height: '3rem' -} - -const Container = styled('div', { - display: 'flex', - flexDirection: 'column', -}) - -const HeaderText = styled('h2', { - fontSize: '1.875rem', - fontWeight: '700', - marginTop: '1.5rem' -}) - - -// PRIVATE API -export const AuthContext = createContext({ - isLoading: false, - setIsLoading: (isLoading: boolean) => {}, - setErrorMessage: (errorMessage: ErrorMessage | null) => {}, - setSuccessMessage: (successMessage: string | null) => {}, -}) - -function Auth ({ state, appearance, logo, socialLayout = 'horizontal', additionalSignupFields }: { - state: State; -} & CustomizationOptions & { - additionalSignupFields?: AdditionalSignupFields; -}) { - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - // TODO(matija): this is called on every render, is it a problem? - // If we do it in useEffect(), then there is a glitch between the default color and the - // user provided one. - const customTheme = createTheme(appearance ?? {}) - - const titles: Record = { - login: 'Log in to your account', - signup: 'Create a new account', - "forgot-password": "Forgot your password?", - "reset-password": "Reset your password", - "verify-email": "Email verification", - } - const title = titles[state] - - const socialButtonsDirection = socialLayout === 'vertical' ? 'vertical' : 'horizontal' - - return ( - -
- {logo && (Your Company)} - {title} -
- - {errorMessage && ( - - {errorMessage.title}{errorMessage.description && ': '}{errorMessage.description} - - )} - {successMessage && {successMessage}} - - {(state === 'login' || state === 'signup') && ( - - )} - {state === 'forgot-password' && ()} - {state === 'reset-password' && ()} - {state === 'verify-email' && ()} - -
- ) -} - -// PRIVATE API -export default Auth; diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx deleted file mode 100644 index f8fca66082..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Login.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Auth from './Auth' -import { type CustomizationOptions, State } from './types' - -// PUBLIC API -export function LoginForm({ - appearance, - logo, - socialLayout, -}: CustomizationOptions) { - return ( - - ) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx deleted file mode 100644 index 32c7afc38a..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/Signup.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Auth from './Auth' -import { - type CustomizationOptions, - type AdditionalSignupFields, - State, -} from './types' - -// PUBLIC API -export function SignupForm({ - appearance, - logo, - socialLayout, - additionalFields, -}: CustomizationOptions & { additionalFields?: AdditionalSignupFields; }) { - return ( - - ) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx deleted file mode 100644 index 1634307427..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Form.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { styled } from 'wasp/core/stitches.config' - -// PRIVATE API -export const Form = styled('form', { - marginTop: '1.5rem', -}) - -// PUBLIC API -export const FormItemGroup = styled('div', { - '& + div': { - marginTop: '1.5rem', - }, -}) - -// PUBLIC API -export const FormLabel = styled('label', { - display: 'block', - fontSize: '$sm', - fontWeight: '500', - marginBottom: '0.5rem', -}) - -const commonInputStyles = { - display: 'block', - lineHeight: '1.5rem', - fontSize: '$sm', - borderWidth: '1px', - borderColor: '$gray600', - backgroundColor: '#f8f4ff', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - '&:focus': { - borderWidth: '1px', - borderColor: '$gray700', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - }, - '&:disabled': { - opacity: 0.5, - cursor: 'not-allowed', - backgroundColor: '$gray400', - borderColor: '$gray400', - color: '$gray500', - }, - - borderRadius: '0.375rem', - width: '100%', - - paddingTop: '0.375rem', - paddingBottom: '0.375rem', - paddingLeft: '0.75rem', - paddingRight: '0.75rem', - margin: 0, -} - -// PUBLIC API -export const FormInput = styled('input', commonInputStyles) - -// PUBLIC API -export const FormTextarea = styled('textarea', commonInputStyles) - -// PUBLIC API -export const FormError = styled('div', { - display: 'block', - fontSize: '$sm', - fontWeight: '500', - color: '$formErrorText', - marginTop: '0.5rem', -}) - -// PRIVATE API -export const SubmitButton = styled('button', { - display: 'flex', - justifyContent: 'center', - - width: '100%', - borderWidth: '1px', - borderColor: '$brand', - backgroundColor: '$brand', - color: '$submitButtonText', - - padding: '0.5rem 0.75rem', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - - fontWeight: '600', - fontSize: '$sm', - lineHeight: '1.25rem', - borderRadius: '0.375rem', - - // TODO(matija): extract this into separate BaseButton component and then inherit it. - '&:hover': { - backgroundColor: '$brandAccent', - borderColor: '$brandAccent', - }, - '&:disabled': { - opacity: 0.5, - cursor: 'not-allowed', - backgroundColor: '$gray400', - borderColor: '$gray400', - color: '$gray500', - }, - transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', - transitionDuration: '100ms', -}) diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx deleted file mode 100644 index 362ff9dfda..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/Message.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { styled } from 'wasp/core/stitches.config' - -// PRIVATE API -export const Message = styled('div', { - padding: '0.5rem 0.75rem', - borderRadius: '0.375rem', - marginTop: '1rem', - background: '$gray400', -}) - -// PRIVATE API -export const MessageError = styled(Message, { - background: '$errorBackground', - color: '$errorText', -}) - -// PRIVATE API -export const MessageSuccess = styled(Message, { - background: '$successBackground', - color: '$successText', -}) diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx deleted file mode 100644 index db7519fceb..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useContext } from 'react' -import { useForm, UseFormReturn } from 'react-hook-form' -import { styled } from 'wasp/core/stitches.config' -import config from 'wasp/core/config' - -import { AuthContext } from '../../Auth' -import { - Form, - FormInput, - FormItemGroup, - FormLabel, - FormError, - FormTextarea, - SubmitButton, -} from '../Form' -import type { - AdditionalSignupFields, - AdditionalSignupField, - AdditionalSignupFieldRenderFn, - FormState, -} from '../../types' -import { useHistory } from 'react-router-dom' -import { useEmail } from '../email/useEmail' - - -// PRIVATE API -export type LoginSignupFormFields = { - [key: string]: string; -} - -// PRIVATE API -export const LoginSignupForm = ({ - state, - socialButtonsDirection = 'horizontal', - additionalSignupFields, -}: { - state: 'login' | 'signup' - socialButtonsDirection?: 'horizontal' | 'vertical' - additionalSignupFields?: AdditionalSignupFields -}) => { - const { - isLoading, - setErrorMessage, - setSuccessMessage, - setIsLoading, - } = useContext(AuthContext) - const isLogin = state === 'login' - const cta = isLogin ? 'Log in' : 'Sign up'; - const history = useHistory(); - const onErrorHandler = (error) => { - setErrorMessage({ title: error.message, description: error.data?.data?.message }) - }; - const hookForm = useForm() - const { register, formState: { errors }, handleSubmit: hookFormHandleSubmit } = hookForm - const { handleSubmit } = useEmail({ - isLogin, - onError: onErrorHandler, - showEmailVerificationPending() { - hookForm.reset() - setSuccessMessage(`You've signed up successfully! Check your email for the confirmation link.`) - }, - onLoginSuccess() { - history.push('/') - }, - }); - async function onSubmit (data) { - setIsLoading(true); - setErrorMessage(null); - setSuccessMessage(null); - try { - await handleSubmit(data); - } finally { - setIsLoading(false); - } - } - - return (<> -
- - E-mail - - {errors.email && {errors.email.message}} - - - Password - - {errors.password && {errors.password.message}} - - - - {cta} - - - ) -} - -function AdditionalFormFields({ - hookForm, - formState: { isLoading }, - additionalSignupFields, -}: { - hookForm: UseFormReturn; - formState: FormState; - additionalSignupFields: AdditionalSignupFields; -}) { - const { - register, - formState: { errors }, - } = hookForm; - - function renderField>( - field: AdditionalSignupField, - // Ideally we would use ComponentType here, but it doesn't work with react-hook-form - Component: any, - props?: React.ComponentProps - ) { - return ( - - {field.label} - - {errors[field.name] && ( - {errors[field.name].message} - )} - - ); - } - - if (areAdditionalFieldsRenderFn(additionalSignupFields)) { - return additionalSignupFields(hookForm, { isLoading }) - } - - return ( - additionalSignupFields && - additionalSignupFields.map((field) => { - if (isFieldRenderFn(field)) { - return field(hookForm, { isLoading }) - } - switch (field.type) { - case 'input': - return renderField(field, FormInput, { - type: 'text', - }) - case 'textarea': - return renderField(field, FormTextarea) - default: - throw new Error( - `Unsupported additional signup field type: ${field.type}` - ) - } - }) - ) -} - -function isFieldRenderFn( - additionalSignupField: AdditionalSignupField | AdditionalSignupFieldRenderFn -): additionalSignupField is AdditionalSignupFieldRenderFn { - return typeof additionalSignupField === 'function' -} - -function areAdditionalFieldsRenderFn( - additionalSignupFields: AdditionalSignupFields -): additionalSignupFields is AdditionalSignupFieldRenderFn { - return typeof additionalSignupFields === 'function' -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts deleted file mode 100644 index 5156472689..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/forms/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { createTheme } from '@stitches/react' -import { UseFormReturn, RegisterOptions } from 'react-hook-form' -import type { LoginSignupFormFields } from './internal/common/LoginSignupForm' - -// PRIVATE API -export enum State { - Login = 'login', - Signup = 'signup', - ForgotPassword = 'forgot-password', - ResetPassword = 'reset-password', - VerifyEmail = 'verify-email', -} - -// PUBLIC API -export type CustomizationOptions = { - logo?: string - socialLayout?: 'horizontal' | 'vertical' - appearance?: Parameters[0] -} - -// PRIVATE API -export type ErrorMessage = { - title: string - description?: string -} - -// PRIVATE API -export type FormState = { - isLoading: boolean -} - -// PRIVATE API -export type AdditionalSignupFieldRenderFn = ( - hookForm: UseFormReturn, - formState: FormState -) => React.ReactNode - -// PRIVATE API -export type AdditionalSignupField = { - name: string - label: string - type: 'input' | 'textarea' - validations?: RegisterOptions -} - -// PRIVATE API -export type AdditionalSignupFields = - | (AdditionalSignupField | AdditionalSignupFieldRenderFn)[] - | AdditionalSignupFieldRenderFn diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts deleted file mode 100644 index 050dd3f32a..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/helpers/user.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { setSessionId } from 'wasp/client/api' -import { invalidateAndRemoveQueries } from 'wasp/operations/resources' - -// PRIVATE API -export async function initSession(sessionId: string): Promise { - setSessionId(sessionId) - // We need to invalidate queries after login in order to get the correct user - // data in the React components (using `useAuth`). - // Redirects after login won't work properly without this. - - // TODO(filip): We are currently removing all the queries, but we should - // remove only non-public, user-dependent queries - public queries are - // expected not to change in respect to the currently logged in user. - await invalidateAndRemoveQueries() -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts deleted file mode 100644 index 4a51817560..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/logout.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { api, removeLocalUserData } from 'wasp/client/api' -import { invalidateAndRemoveQueries } from 'wasp/operations/resources' - -// PUBLIC API -export default async function logout(): Promise { - try { - await api.post('/auth/logout') - } finally { - // Even if the logout request fails, we still want to remove the local user data - // in case the logout failed because of a network error and the user walked away - // from the computer. - removeLocalUserData() - - // TODO(filip): We are currently invalidating and removing all the queries, but - // we should remove only the non-public, user-dependent ones. - await invalidateAndRemoveQueries() - } -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts deleted file mode 100644 index 8cd06b8afc..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/providers/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Router, Request } from 'express' -import type { Prisma } from '@prisma/client' -import type { Expand } from 'wasp/universal/types' -import type { ProviderName } from '../utils' - -// PUBLIC API -export function defineUserSignupFields(fields: UserSignupFields) { - return fields -} - -type UserEntityCreateInput = Prisma.UserCreateInput - -// PRIVATE API -export type ProviderConfig = { - // Unique provider identifier, used as part of URL paths - id: ProviderName; - displayName: string; - // Each provider config can have an init method which is ran on setup time - // e.g. for oAuth providers this is the time when the Passport strategy is registered. - init?(provider: ProviderConfig): Promise; - // Every provider must have a setupRouter method which returns the Express router. - // In this function we are flexibile to do what ever is necessary to make the provider work. - createRouter(provider: ProviderConfig, initData: InitData): Router; -}; - -// PRIVATE API -export type InitData = { - [key: string]: any; -} - -// PRIVATE API -export type RequestWithWasp = Request & { wasp?: { [key: string]: any } } - -// PRIVATE API -export type PossibleUserFields = Expand> - -// PRIVATE API -export type UserSignupFields = { - [key in keyof PossibleUserFields]: FieldGetter< - PossibleUserFields[key] - > -} - -type FieldGetter = ( - data: { [key: string]: unknown } -) => Promise | T | undefined diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts deleted file mode 100644 index 03d33b5016..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -// todo(filip): turn into a proper import/path -export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts deleted file mode 100644 index cd7630ade6..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/useAuth.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { deserialize as superjsonDeserialize } from 'superjson' -import { useQuery } from 'wasp/rpc' -import { api, handleApiError } from 'wasp/client/api' -import { HttpMethod } from 'wasp/types' -import type { AuthUser } from './types' -import { addMetadataToQuery } from 'wasp/rpc/queries' - -// PUBLIC API -export const getMe = createUserGetter() - -// PUBLIC API -export default function useAuth(queryFnArgs?: unknown, config?: any) { - return useQuery(getMe, queryFnArgs, config) -} - -function createUserGetter() { - const getMeRelativePath = 'auth/me' - const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` } - async function getMe(): Promise { - try { - const response = await api.get(getMeRoute.path) - - return superjsonDeserialize(response.data) - } catch (error) { - if (error.response?.status === 401) { - return null - } else { - handleApiError(error) - } - } - } - - addMetadataToQuery(getMe, { - relativeQueryPath: getMeRelativePath, - queryRoute: getMeRoute, - entitiesUsed: ['User'], - }) - - return getMe -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts deleted file mode 100644 index f9bc6d39a3..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/user.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types' - -// PUBLIC API -export function getEmail(user: AuthUser): string | null { - return findUserIdentity(user, "email")?.providerUserId ?? null; -} - -// PUBLIC API -export function getUsername(user: AuthUser): string | null { - return findUserIdentity(user, "username")?.providerUserId ?? null; -} - -// PUBLIC API -export function getFirstProviderUserId(user?: AuthUser): string | null { - if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { - return null; - } - - return user.auth.identities[0].providerUserId ?? null; -} - -// PUBLIC API -export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined { - return user.auth.identities.find( - (identity) => identity.providerName === providerName - ); -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts deleted file mode 100644 index ba04768d7e..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/utils.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { hashPassword } from './password.js' -import { verify } from './jwt.js' -import { prisma, HttpError, AuthError } from 'wasp/server' -import { sleep } from 'wasp/server/utils' -import { - type User, - type Auth, - type AuthIdentity, -} from 'wasp/entities' -import { Prisma } from '@prisma/client'; - -import { throwValidationError } from './validation.js' - -import { type UserSignupFields, type PossibleUserFields } from './providers/types.js' - -// PRIVATE API -export type EmailProviderData = { - hashedPassword: string; - isEmailVerified: boolean; - emailVerificationSentAt: string | null; - passwordResetSentAt: string | null; -} - -// PRIVATE API -export type UsernameProviderData = { - hashedPassword: string; -} - -// PRIVATE API -export type OAuthProviderData = {} - -// PRIVATE API -/** - * This type is used for type-level programming e.g. to enumerate - * all possible provider data types. - * - * The keys of this type are the names of the providers and the values - * are the types of the provider data. - */ -export type PossibleProviderData = { - email: EmailProviderData; - username: UsernameProviderData; - google: OAuthProviderData; - github: OAuthProviderData; -} - -// PRIVATE API -export type ProviderName = keyof PossibleProviderData - -// PRIVATE API -export const contextWithUserEntity = { - entities: { - User: prisma.user - } -} - -// PRIVATE API -export const authConfig = { - failureRedirectPath: "/login", - successRedirectPath: "/", -} - -// PRIVATE API -/** - * ProviderId uniquely identifies an auth identity e.g. - * "email" provider with user id "test@test.com" or - * "google" provider with user id "1234567890". - * - * We use this type to avoid passing the providerName and providerUserId - * separately. Also, we can normalize the providerUserId to make sure it's - * consistent across different DB operations. - */ -export type ProviderId = { - providerName: ProviderName; - providerUserId: string; -} - -// PUBLIC API -export function createProviderId(providerName: ProviderName, providerUserId: string): ProviderId { - return { - providerName, - providerUserId: providerUserId.toLowerCase(), - } -} - -// PUBLIC API -export async function findAuthIdentity(providerId: ProviderId): Promise { - return prisma.authIdentity.findUnique({ - where: { - providerName_providerUserId: providerId, - } - }); -} - -// PUBLIC API -/** - * Updates the provider data for the given auth identity. - * - * This function performs data sanitization and serialization. - * Sanitization is done by hashing the password, so this function - * expects the password received in the `providerDataUpdates` - * **not to be hashed**. - */ -export async function updateAuthIdentityProviderData( - providerId: ProviderId, - existingProviderData: PossibleProviderData[PN], - providerDataUpdates: Partial, -): Promise { - // We are doing the sanitization here only on updates to avoid - // hashing the password multiple times. - const sanitizedProviderDataUpdates = await sanitizeProviderData(providerDataUpdates); - const newProviderData = { - ...existingProviderData, - ...sanitizedProviderDataUpdates, - } - const serializedProviderData = await serializeProviderData(newProviderData); - return prisma.authIdentity.update({ - where: { - providerName_providerUserId: providerId, - }, - data: { providerData: serializedProviderData }, - }); -} - -type FindAuthWithUserResult = Auth & { - user: User -} - -// PRIVATE API -export async function findAuthWithUserBy( - where: Prisma.AuthWhereInput -): Promise { - return prisma.auth.findFirst({ where, include: { user: true }}); -} - -// PUBLIC API -export async function createUser( - providerId: ProviderId, - serializedProviderData?: string, - userFields?: PossibleUserFields, -): Promise { - return prisma.user.create({ - data: { - // Using any here to prevent type errors when userFields are not - // defined. We want Prisma to throw an error in that case. - ...(userFields ?? {} as any), - auth: { - create: { - identities: { - create: { - providerName: providerId.providerName, - providerUserId: providerId.providerUserId, - providerData: serializedProviderData, - }, - }, - } - }, - }, - // We need to include the Auth entity here because we need `authId` - // to be able to create a session. - include: { - auth: true, - }, - }) -} - -// PRIVATE API -export async function deleteUserByAuthId(authId: string): Promise<{ count: number }> { - return prisma.user.deleteMany({ where: { auth: { - id: authId, - } } }) -} - -// PRIVATE API -export async function verifyToken(token: string): Promise { - return verify(token); -} - -// PRIVATE API -// If an user exists, we don't want to leak information -// about it. Pretending that we're doing some work -// will make it harder for an attacker to determine -// if a user exists or not. -// NOTE: Attacker measuring time to response can still determine -// if a user exists or not. We'll be able to avoid it when -// we implement e-mail sending via jobs. -export async function doFakeWork(): Promise { - const timeToWork = Math.floor(Math.random() * 1000) + 1000; - return sleep(timeToWork); -} - -// PRIVATE API -export function rethrowPossibleAuthError(e: unknown): void { - if (e instanceof AuthError) { - throwValidationError(e.message); - } - - // Prisma code P2002 is for unique constraint violations. - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') { - throw new HttpError(422, 'Save failed', { - message: `user with the same identity already exists`, - }) - } - - if (e instanceof Prisma.PrismaClientValidationError) { - // NOTE: Logging the error since this usually means that there are - // required fields missing in the request, we want the developer - // to know about it. - console.error(e) - throw new HttpError(422, 'Save failed', { - message: 'there was a database error' - }) - } - - // Prisma code P2021 is for missing table errors. - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2021') { - // NOTE: Logging the error since this usually means that the database - // migrations weren't run, we want the developer to know about it. - console.error(e) - console.info('🐝 This error can happen if you did\'t run the database migrations.') - throw new HttpError(500, 'Save failed', { - message: `there was a database error`, - }) - } - - // Prisma code P2003 is for foreign key constraint failure - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2003') { - console.error(e) - console.info(`🐝 This error can happen if you have some relation on your User entity - but you didn't specify the "onDelete" behaviour to either "Cascade" or "SetNull". - Read more at: https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/referential-actions`) - throw new HttpError(500, 'Save failed', { - message: `there was a database error`, - }) - } - - throw e -} - -// PRIVATE API -export async function validateAndGetUserFields( - data: { - [key: string]: unknown - }, - userSignupFields?: UserSignupFields, -): Promise> { - const { - password: _password, - ...sanitizedData - } = data; - const result: Record = {}; - - if (!userSignupFields) { - return result; - } - - for (const [field, getFieldValue] of Object.entries(userSignupFields)) { - try { - const value = await getFieldValue(sanitizedData) - result[field] = value - } catch (e) { - throwValidationError(e.message) - } - } - return result; -} - -// PUBLIC API -export function deserializeAndSanitizeProviderData( - providerData: string, - { shouldRemovePasswordField = false }: { shouldRemovePasswordField?: boolean } = {}, -): PossibleProviderData[PN] { - // NOTE: We are letting JSON.parse throw an error if the providerData is not valid JSON. - let data = JSON.parse(providerData) as PossibleProviderData[PN]; - - if (providerDataHasPasswordField(data) && shouldRemovePasswordField) { - delete data.hashedPassword; - } - - return data; -} - -// PUBLIC API -export async function sanitizeAndSerializeProviderData( - providerData: PossibleProviderData[PN], -): Promise { - return serializeProviderData( - await sanitizeProviderData(providerData) - ); -} - -function serializeProviderData(providerData: PossibleProviderData[PN]): string { - return JSON.stringify(providerData); -} - -async function sanitizeProviderData( - providerData: PossibleProviderData[PN], -): Promise { - const data = { - ...providerData, - }; - if (providerDataHasPasswordField(data)) { - data.hashedPassword = await hashPassword(data.hashedPassword); - } - - return data; -} - - -function providerDataHasPasswordField( - providerData: PossibleProviderData[keyof PossibleProviderData], -): providerData is { hashedPassword: string } { - return 'hashedPassword' in providerData; -} - -// PRIVATE API -export function throwInvalidCredentialsError(message?: string): void { - throw new HttpError(401, 'Invalid credentials', { message }) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts deleted file mode 100644 index 637f4203fd..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/auth/validation.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { HttpError } from 'wasp/server'; - -export const PASSWORD_FIELD = 'password'; -const USERNAME_FIELD = 'username'; -const EMAIL_FIELD = 'email'; -const TOKEN_FIELD = 'token'; - -// PUBLIC API -export function ensureValidEmail(args: unknown): void { - validate(args, [ - { validates: EMAIL_FIELD, message: 'email must be present', validator: email => !!email }, - { validates: EMAIL_FIELD, message: 'email must be a valid email', validator: email => isValidEmail(email) }, - ]); -} - -// PUBLIC API -export function ensureValidUsername(args: unknown): void { - validate(args, [ - { validates: USERNAME_FIELD, message: 'username must be present', validator: username => !!username } - ]); -} - -// PUBLIC API -export function ensurePasswordIsPresent(args: unknown): void { - validate(args, [ - { validates: PASSWORD_FIELD, message: 'password must be present', validator: password => !!password }, - ]); -} - -// PUBLIC API -export function ensureValidPassword(args: unknown): void { - validate(args, [ - { validates: PASSWORD_FIELD, message: 'password must be at least 8 characters', validator: password => isMinLength(password, 8) }, - { validates: PASSWORD_FIELD, message: 'password must contain a number', validator: password => containsNumber(password) }, - ]); -} - -// PUBLIC API -export function ensureTokenIsPresent(args: unknown): void { - validate(args, [ - { validates: TOKEN_FIELD, message: 'token must be present', validator: token => !!token }, - ]); -} - -// PRIVATE API -export function throwValidationError(message: string): void { - throw new HttpError(422, 'Validation failed', { message }) -} - -function validate(args: unknown, validators: { validates: string, message: string, validator: (value: unknown) => boolean }[]): void { - for (const { validates, message, validator } of validators) { - if (!validator(args[validates])) { - throwValidationError(message); - } - } -} - -// NOTE(miho): it would be good to replace our custom validations with e.g. Zod - -const validEmailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ -function isValidEmail(input: unknown): boolean { - if (typeof input !== 'string') { - return false - } - - return input.match(validEmailRegex) !== null -} - -function isMinLength(input: unknown, minLength: number): boolean { - if (typeof input !== 'string') { - return false - } - - return input.length >= minLength -} - -function containsNumber(input: unknown): boolean { - if (typeof input !== 'string') { - return false - } - - return /\d/.test(input) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts deleted file mode 100644 index 8ef076ee1f..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/operations/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { api, handleApiError } from 'wasp/client/api' -import { HttpMethod } from 'wasp/types' -import { - serialize as superjsonSerialize, - deserialize as superjsonDeserialize, - } from 'superjson' - -export type OperationRoute = { method: HttpMethod, path: string } - -export async function callOperation(operationRoute: OperationRoute & { method: HttpMethod.Post }, args: any) { - try { - const superjsonArgs = superjsonSerialize(args) - const response = await api.post(operationRoute.path, superjsonArgs) - return superjsonDeserialize(response.data) - } catch (error) { - handleApiError(error) - } -} - -export function makeOperationRoute(relativeOperationRoute: string): OperationRoute { - return { method: HttpMethod.Post, path: `/${relativeOperationRoute}` } -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts deleted file mode 100644 index fa27d07d00..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/_types/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { type Expand } from 'wasp/universal/types'; -import { type Request, type Response } from 'express' -import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' -import { prisma } from 'wasp/server' -import { - type User, - type Auth, - type AuthIdentity, -} from "wasp/entities" -import { - type EmailProviderData, - type UsernameProviderData, - type OAuthProviderData, -} from 'wasp/auth/utils' -import { type _Entity } from "./taggedEntities" -import { type Payload } from "./serialization"; - -export * from "./taggedEntities" -export * from "./serialization" - -export type Query = - Operation - -export type Action = - Operation - -export type AuthenticatedQuery = - AuthenticatedOperation - -export type AuthenticatedAction = - AuthenticatedOperation - -type AuthenticatedOperation = ( - args: Input, - context: ContextWithUser, -) => Output | Promise - -export type AuthenticatedApi< - Entities extends _Entity[], - Params extends ExpressParams, - ResBody, - ReqBody, - ReqQuery extends ExpressQuery, - Locals extends Record -> = ( - req: Request, - res: Response, - context: ContextWithUser, -) => void - -type Operation = ( - args: Input, - context: Context, -) => Output | Promise - -export type Api< - Entities extends _Entity[], - Params extends ExpressParams, - ResBody, - ReqBody, - ReqQuery extends ExpressQuery, - Locals extends Record -> = ( - req: Request, - res: Response, - context: Context, -) => void - -type EntityMap = { - [EntityName in Entities[number]["_entityName"]]: PrismaDelegate[EntityName] -} - -export type PrismaDelegate = { - "User": typeof prisma.user, - "Task": typeof prisma.task, -} - -type Context = Expand<{ - entities: Expand> -}> - -type ContextWithUser = Expand & { user?: AuthUser }> - -// TODO: This type must match the logic in auth/session.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 - -export type DeserializedAuthIdentity = Expand & { - providerData: Omit | Omit | OAuthProviderData -}> - -export type AuthUser = User & { - auth: Auth & { - identities: DeserializedAuthIdentity[] - } | null -} - -export type { ProviderName } from 'wasp/auth/utils' diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts deleted file mode 100644 index 54c224ba2e..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/actions/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { prisma } from 'wasp/server' - -import { createTask as createTask_ext } from 'wasp/ext-src/task/actions.js' -import { updateTask as updateTask_ext } from 'wasp/ext-src/task/actions.js' -import { deleteTasks as deleteTasks_ext } from 'wasp/ext-src/task/actions.js' -import { send as send_ext } from 'wasp/ext-src/user/customEmailSending.js' - -export type CreateTask = typeof createTask_ext - -export const createTask = async (args, context) => { - return (createTask_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type UpdateTask = typeof updateTask_ext - -export const updateTask = async (args, context) => { - return (updateTask_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type DeleteTasks = typeof deleteTasks_ext - -export const deleteTasks = async (args, context) => { - return (deleteTasks_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type CustomEmailSending = typeof send_ext - -export const customEmailSending = async (args, context) => { - return (send_ext as any)(args, { - ...context, - entities: { - User: prisma.user, - }, - }) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts deleted file mode 100644 index cbfb76d351..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/queries/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { prisma } from 'wasp/server' - -import { getTasks as getTasks_ext } from 'wasp/ext-src/task/queries.js' - -export type GetTasks = typeof getTasks_ext - -export const getTasks = async (args, context) => { - return (getTasks_ext as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} diff --git a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts b/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts deleted file mode 100644 index d7fe314996..0000000000 --- a/waspc/examples/todo-typescript/.wasp/out/sdk/wasp/server/utils.ts +++ /dev/null @@ -1,67 +0,0 @@ -import crypto from 'crypto' -import { Request, Response, NextFunction } from 'express' - -import { readdir } from 'fs' -import { dirname } from 'path' -import { fileURLToPath } from 'url' - -import { type AuthUser } from 'wasp/auth' - -type RequestWithExtraFields = Request & { - user?: AuthUser; - sessionId?: string; -} - -/** - * Decorator for async express middleware that handles promise rejections. - * @param {Func} middleware - Express middleware function. - * @returns Express middleware that is exactly the same as the given middleware but, - * if given middleware returns promise, reject of that promise will be correctly handled, - * meaning that error will be forwarded to next(). - */ -export const handleRejection = ( - middleware: ( - req: RequestWithExtraFields, - res: Response, - next: NextFunction - ) => any -) => -async (req: RequestWithExtraFields, res: Response, next: NextFunction) => { - try { - await middleware(req, res, next) - } catch (error) { - next(error) - } -} - -export const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)) - -export function getDirPathFromFileUrl(fileUrl: string): string { - return fileURLToPath(dirname(fileUrl)) -} - -export async function importJsFilesFromDir( - pathToDir: string, - whitelistedFileNames: string[] | null = null -): Promise { - return new Promise((resolve, reject) => { - readdir(pathToDir, async (err, files) => { - if (err) { - return reject(err) - } - const importPromises = files - .filter((file) => file.endsWith('.js') && isWhitelistedFileName(file)) - .map((file) => import(`${pathToDir}/${file}`)) - resolve(Promise.all(importPromises)) - }) - }) - - function isWhitelistedFileName(fileName: string) { - // No whitelist means all files are whitelisted - if (!Array.isArray(whitelistedFileNames)) { - return true - } - - return whitelistedFileNames.some((whitelistedFileName) => fileName === whitelistedFileName) - } -} From 51f299e2ca8e7e4c5d3694ed2c82da03316a2005 Mon Sep 17 00:00:00 2001 From: Martin Sosic Date: Wed, 31 Jan 2024 13:18:11 +0100 Subject: [PATCH 16/19] [New SDK]: 'wasp/client/webSocket'. --- .../data/Generator/templates/react-app/src/index.tsx | 2 +- .../sdk/{ => client}/webSocket/WebSocketProvider.tsx | 5 ++++- .../templates/sdk/{ => client}/webSocket/index.ts | 4 ++++ waspc/data/Generator/templates/sdk/package.json | 11 ++++++----- waspc/examples/todo-typescript/src/ChatPage.tsx | 2 +- .../Wasp/Generator/SdkGenerator/WebSocketGenerator.hs | 4 ++-- 6 files changed, 18 insertions(+), 10 deletions(-) rename waspc/data/Generator/templates/sdk/{ => client}/webSocket/WebSocketProvider.tsx (95%) rename waspc/data/Generator/templates/sdk/{ => client}/webSocket/index.ts (95%) diff --git a/waspc/data/Generator/templates/react-app/src/index.tsx b/waspc/data/Generator/templates/react-app/src/index.tsx index 6ecad2d2a4..cf57444d12 100644 --- a/waspc/data/Generator/templates/react-app/src/index.tsx +++ b/waspc/data/Generator/templates/react-app/src/index.tsx @@ -14,7 +14,7 @@ import { {=/ setupFn.isDefined =} {=# areWebSocketsUsed =} -import { WebSocketProvider } from 'wasp/webSocket/WebSocketProvider' +import { WebSocketProvider } from 'wasp/client/webSocket/WebSocketProvider' {=/ areWebSocketsUsed =} startApp() diff --git a/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx b/waspc/data/Generator/templates/sdk/client/webSocket/WebSocketProvider.tsx similarity index 95% rename from waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx rename to waspc/data/Generator/templates/sdk/client/webSocket/WebSocketProvider.tsx index 062aba19d8..d4e37e648c 100644 --- a/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx +++ b/waspc/data/Generator/templates/sdk/client/webSocket/WebSocketProvider.tsx @@ -8,11 +8,12 @@ import config from 'wasp/core/config' import type { ClientToServerEvents, ServerToClientEvents } from 'wasp/server/webSocket'; +// PRIVATE API // TODO: In the future, it would be nice if users could pass more // options to `io`, likely via some `configFn`. export const socket: Socket = io(config.apiUrl, { autoConnect: {= autoConnect =} }) -function refreshAuthToken() { +function refreshAuthToken() { // NOTE: When we figure out how `auth: true` works for Operations, we should // mirror that behavior here for WebSockets. Ref: https://github.com/wasp-lang/wasp/issues/1133 socket.auth = { @@ -29,11 +30,13 @@ refreshAuthToken() apiEventsEmitter.on('sessionId.set', refreshAuthToken) apiEventsEmitter.on('sessionId.clear', refreshAuthToken) +// PRIVATE API export const WebSocketContext = createContext({ socket, isConnected: false, }); +// PRIVATE API export function WebSocketProvider({ children }: { children: JSX.Element }) { const [isConnected, setIsConnected] = useState(socket.connected) diff --git a/waspc/data/Generator/templates/sdk/webSocket/index.ts b/waspc/data/Generator/templates/sdk/client/webSocket/index.ts similarity index 95% rename from waspc/data/Generator/templates/sdk/webSocket/index.ts rename to waspc/data/Generator/templates/sdk/client/webSocket/index.ts index 3dce7da720..ccd6ce9e46 100644 --- a/waspc/data/Generator/templates/sdk/webSocket/index.ts +++ b/waspc/data/Generator/templates/sdk/client/webSocket/index.ts @@ -5,15 +5,19 @@ import type { ServerToClientEvents, } from 'wasp/server/webSocket' +// PUBLIC API export type ServerToClientPayload = Parameters[0] +// PUBLIC API export type ClientToServerPayload = Parameters[0] +// PUBLIC API export function useSocket() { return useContext(WebSocketContext) } +// PUBLIC API export function useSocketListener( event: Event, handler: ServerToClientEvents[Event] diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index e853eed44a..fb0a46e365 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -89,14 +89,12 @@ "./server/jobs/pgBoss/types": "./dist/server/jobs/pgBoss/types.js", {=! Used by users, documented. =} "./server/webSocket": "./dist/server/webSocket/index.js", - {=! Used by users, documented. =} - "./webSocket": "./dist/webSocket/index.js", - {=! Used by our code, uncodumented (but accessible) for users. =} - "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx", + {=! Used by the framework client code, reconsider during refactoring. =} + "./client/webSocket/WebSocketProvider": "./dist/client/webSocket/WebSocketProvider.jsx", {=! Still needed, reconsider during refactoring. =} "./server/types": "./dist/server/types/index.js", - {=! Still used by the server code, reconsider during refactoring. =} + {=! Used by the framework server code, reconsider during refactoring. =} "./server/middleware": "./dist/server/middleware/index.js", {=! ================= NEW API HERE =================== =} @@ -109,6 +107,9 @@ {=! Public: { api } =} {=! Private: [sdk] =} "./client/api": "./dist/api/index.js", + {=! Public: { type ServerToClientPayload, type ClientToServerPayload, useSocket, useSocketListener } =} + {=! Private: [] =} + "./client/webSocket": "./dist/client/webSocket/index.js", "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", "./server/auth": "./dist/server/auth/index.js", diff --git a/waspc/examples/todo-typescript/src/ChatPage.tsx b/waspc/examples/todo-typescript/src/ChatPage.tsx index 30f8c3191f..6978a10c09 100644 --- a/waspc/examples/todo-typescript/src/ChatPage.tsx +++ b/waspc/examples/todo-typescript/src/ChatPage.tsx @@ -4,7 +4,7 @@ import { useSocket, useSocketListener, ServerToClientPayload, -} from 'wasp/webSocket' +} from 'wasp/client/webSocket' async function fetchCustomRoute() { const res = await api.get('/foo/bar') diff --git a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs index 187778be18..ae6758780e 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs @@ -26,7 +26,7 @@ genWebSockets spec | AS.WS.areWebSocketsUsed spec = sequence [ genWebSocketServerIndex spec, - genFileCopy [relfile|webSocket/index.ts|], + genFileCopy [relfile|client/webSocket/index.ts|], genWebSocketProvider spec ] | otherwise = return [] @@ -46,7 +46,7 @@ genWebSocketServerIndex spec = return $ C.mkTmplFdWithData [relfile|server/webSo mayebWebSocketFn = AS.App.WS.fn <$> maybeWebSocket genWebSocketProvider :: AppSpec -> Generator FileDraft -genWebSocketProvider spec = return $ C.mkTmplFdWithData [relfile|webSocket/WebSocketProvider.tsx|] tmplData +genWebSocketProvider spec = return $ C.mkTmplFdWithData [relfile|client/webSocket/WebSocketProvider.tsx|] tmplData where maybeWebSocket = AS.App.webSocket $ snd $ getApp spec shouldAutoConnect = (AS.App.WS.autoConnect <$> maybeWebSocket) /= Just (Just False) From bfc7e3b7c2e79f60d5451d0d03c2b94df8c437ce Mon Sep 17 00:00:00 2001 From: Martin Sosic Date: Wed, 31 Jan 2024 13:29:53 +0100 Subject: [PATCH 17/19] [New SDK]: 'wasp/server/webSocket'. --- waspc/data/Generator/templates/sdk/package.json | 5 +++-- waspc/data/Generator/templates/sdk/server/webSocket/index.ts | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index fb0a46e365..6e3b7978e4 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -87,8 +87,6 @@ {=! Used by our code, uncodumented (but accessible) for users. =} {=! Todo(filip): This export becomes problematic once we start supporting different executors =} "./server/jobs/pgBoss/types": "./dist/server/jobs/pgBoss/types.js", - {=! Used by users, documented. =} - "./server/webSocket": "./dist/server/webSocket/index.js", {=! Used by the framework client code, reconsider during refactoring. =} "./client/webSocket/WebSocketProvider": "./dist/client/webSocket/WebSocketProvider.jsx", @@ -110,6 +108,9 @@ {=! Public: { type ServerToClientPayload, type ClientToServerPayload, useSocket, useSocketListener } =} {=! Private: [] =} "./client/webSocket": "./dist/client/webSocket/index.js", + {=! Public: { type WebSocketDefinition, type WaspSocketData } =} + {=! Private: [server, sdk] =} + "./server/webSocket": "./dist/server/webSocket/index.js", "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", "./server/auth": "./dist/server/auth/index.js", diff --git a/waspc/data/Generator/templates/sdk/server/webSocket/index.ts b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts index 82a044185b..2fdbd86729 100644 --- a/waspc/data/Generator/templates/sdk/server/webSocket/index.ts +++ b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts @@ -10,6 +10,7 @@ import { type AuthUser } from 'wasp/auth' {=& userWebSocketFn.importStatement =} +// Public API export type WebSocketDefinition< ClientToServerEvents extends EventsMap = DefaultEventsMap, ServerToClientEvents extends EventsMap = DefaultEventsMap, @@ -31,15 +32,19 @@ export type WebSocketDefinition< } ) => Promise | void +// PUBLIC API export interface WaspSocketData { {=# isAuthEnabled =} user?: AuthUser {=/ isAuthEnabled =} } +// PRIVATE API export type ServerType = Parameters[0] +// PRIVATE API export type ClientToServerEvents = Events[0] +// PRIVATE API export type ServerToClientEvents = Events[1] type WebSocketFn = typeof {= userWebSocketFn.importIdentifier =} From 3b9b716ffeb3a1a61ffb65bcbe630883ff7e044a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Sodi=C4=87?= Date: Wed, 31 Jan 2024 17:08:09 +0100 Subject: [PATCH 18/19] Update imports for wasp/client/operations --- .../templates/react-app/src/index.tsx | 2 +- .../templates/sdk/auth/helpers/user.ts | 2 +- .../Generator/templates/sdk/auth/logout.ts | 2 +- .../Generator/templates/sdk/auth/useAuth.ts | 3 +- .../templates/sdk/client/crud/_crud.ts | 7 ++- .../operations}/actions/core.d.ts | 3 +- .../operations}/actions/core.js | 7 ++- .../operations}/actions/index.ts | 1 + .../index.ts => client/operations/core.ts} | 12 ++++- .../templates/sdk/client/operations/index.ts | 22 +++++++++ .../operations/internal}/index.ts | 3 ++ .../operations/internal}/resources.js | 4 +- .../operations/internal}/updateHandlersMap.js | 0 .../operations}/queries/core.d.ts | 4 +- .../operations}/queries/core.js | 6 ++- .../operations}/queries/index.ts | 2 + .../{rpc => client/operations}/queryClient.ts | 6 +-- .../sdk/client/test/vitest/helpers.tsx | 2 +- .../data/Generator/templates/sdk/package.json | 13 +----- .../examples/todo-typescript/src/MainPage.tsx | 9 ++-- .../todo-typescript/src/Todo.test.tsx | 2 +- waspc/examples/todo-typescript/src/Todo.tsx | 6 +-- .../todo-typescript/src/setup/clientSetup.ts | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 13 ++---- .../OperationsGenerator.hs} | 46 ++++++++++++------- .../src/Wasp/Generator/SdkGenerator/Common.hs | 5 ++ waspc/waspc.cabal | 2 +- 27 files changed, 113 insertions(+), 73 deletions(-) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/actions/core.d.ts (88%) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/actions/core.js (89%) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/actions/index.ts (94%) rename waspc/data/Generator/templates/sdk/{rpc/index.ts => client/operations/core.ts} (95%) create mode 100644 waspc/data/Generator/templates/sdk/client/operations/index.ts rename waspc/data/Generator/templates/sdk/{operations => client/operations/internal}/index.ts (94%) rename waspc/data/Generator/templates/sdk/{operations => client/operations/internal}/resources.js (97%) rename waspc/data/Generator/templates/sdk/{operations => client/operations/internal}/updateHandlersMap.js (100%) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/queries/core.d.ts (90%) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/queries/core.js (85%) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/queries/index.ts (91%) rename waspc/data/Generator/templates/sdk/{rpc => client/operations}/queryClient.ts (86%) rename waspc/src/Wasp/Generator/SdkGenerator/{RpcGenerator.hs => Client/OperationsGenerator.hs} (70%) diff --git a/waspc/data/Generator/templates/react-app/src/index.tsx b/waspc/data/Generator/templates/react-app/src/index.tsx index cf57444d12..9a7a3241e8 100644 --- a/waspc/data/Generator/templates/react-app/src/index.tsx +++ b/waspc/data/Generator/templates/react-app/src/index.tsx @@ -7,7 +7,7 @@ import router from './router' import { initializeQueryClient, queryClientInitialized, -} from 'wasp/rpc/queryClient' +} from 'wasp/client/operations' {=# setupFn.isDefined =} {=& setupFn.importStatement =} diff --git a/waspc/data/Generator/templates/sdk/auth/helpers/user.ts b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts index 050dd3f32a..d5e99a7e40 100644 --- a/waspc/data/Generator/templates/sdk/auth/helpers/user.ts +++ b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts @@ -1,5 +1,5 @@ import { setSessionId } from 'wasp/client/api' -import { invalidateAndRemoveQueries } from 'wasp/operations/resources' +import { invalidateAndRemoveQueries } from '../../client/operations/internal/resources.js' // PRIVATE API export async function initSession(sessionId: string): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/logout.ts b/waspc/data/Generator/templates/sdk/auth/logout.ts index 4a51817560..2ecb055d9e 100644 --- a/waspc/data/Generator/templates/sdk/auth/logout.ts +++ b/waspc/data/Generator/templates/sdk/auth/logout.ts @@ -1,5 +1,5 @@ import { api, removeLocalUserData } from 'wasp/client/api' -import { invalidateAndRemoveQueries } from 'wasp/operations/resources' +import { invalidateAndRemoveQueries } from '../client/operations/internal/resources.js' // PUBLIC API export default async function logout(): Promise { diff --git a/waspc/data/Generator/templates/sdk/auth/useAuth.ts b/waspc/data/Generator/templates/sdk/auth/useAuth.ts index 5038c40d77..22327b107a 100644 --- a/waspc/data/Generator/templates/sdk/auth/useAuth.ts +++ b/waspc/data/Generator/templates/sdk/auth/useAuth.ts @@ -1,10 +1,9 @@ {{={= =}=}} import { deserialize as superjsonDeserialize } from 'superjson' -import { useQuery } from 'wasp/rpc' +import { useQuery, addMetadataToQuery } from 'wasp/client/operations' import { api, handleApiError } from 'wasp/client/api' import { HttpMethod } from 'wasp/types' import type { AuthUser } from './types' -import { addMetadataToQuery } from 'wasp/rpc/queries' // PUBLIC API export const getMe = createUserGetter() diff --git a/waspc/data/Generator/templates/sdk/client/crud/_crud.ts b/waspc/data/Generator/templates/sdk/client/crud/_crud.ts index 0516373384..b2feb6ee66 100644 --- a/waspc/data/Generator/templates/sdk/client/crud/_crud.ts +++ b/waspc/data/Generator/templates/sdk/client/crud/_crud.ts @@ -1,8 +1,7 @@ {{={= =}=}} -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 { useAction, useQuery } from "wasp/client/operations"; +import { createAction } from "../operations/actions/core.js"; +import { createQuery } from "../operations/queries/core.js"; import { {=# operations.Get =} GetQueryResolved, diff --git a/waspc/data/Generator/templates/sdk/rpc/actions/core.d.ts b/waspc/data/Generator/templates/sdk/client/operations/actions/core.d.ts similarity index 88% rename from waspc/data/Generator/templates/sdk/rpc/actions/core.d.ts rename to waspc/data/Generator/templates/sdk/client/operations/actions/core.d.ts index fbd0cc1d0a..894c01f91c 100644 --- a/waspc/data/Generator/templates/sdk/rpc/actions/core.d.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/actions/core.d.ts @@ -1,6 +1,7 @@ -import { type Action } from '..' +import { type Action } from '../core.js' import type { Expand, _Awaited, _ReturnType } from 'wasp/universal/types' +// PRIVATE API export function createAction( actionRoute: string, entitiesUsed: unknown[] diff --git a/waspc/data/Generator/templates/sdk/rpc/actions/core.js b/waspc/data/Generator/templates/sdk/client/operations/actions/core.js similarity index 89% rename from waspc/data/Generator/templates/sdk/rpc/actions/core.js rename to waspc/data/Generator/templates/sdk/client/operations/actions/core.js index cd1c60ecef..17dd6aba4d 100644 --- a/waspc/data/Generator/templates/sdk/rpc/actions/core.js +++ b/waspc/data/Generator/templates/sdk/client/operations/actions/core.js @@ -1,11 +1,10 @@ -import { callOperation, makeOperationRoute } from 'wasp/operations' +import { callOperation, makeOperationRoute } from '../internal/index.js' import { registerActionInProgress, registerActionDone, -} from 'wasp/operations/resources' - -// todo(filip) - turn helpers and core into the same thing +} from '../internal/resources.js' +// PRIVATE API export function createAction(relativeActionRoute, entitiesUsed) { const actionRoute = makeOperationRoute(relativeActionRoute) diff --git a/waspc/data/Generator/templates/sdk/rpc/actions/index.ts b/waspc/data/Generator/templates/sdk/client/operations/actions/index.ts similarity index 94% rename from waspc/data/Generator/templates/sdk/rpc/actions/index.ts rename to waspc/data/Generator/templates/sdk/client/operations/actions/index.ts index d9d58fc527..1999e4ec5e 100644 --- a/waspc/data/Generator/templates/sdk/rpc/actions/index.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/actions/index.ts @@ -5,6 +5,7 @@ import { createAction } from './core' {=/ actions =} {=# actions =} +// PUBLIC API export const {= operationName =} = createAction<{= operationTypeName =}>( '{= actionRoute =}', {=& entitiesArray =}, diff --git a/waspc/data/Generator/templates/sdk/rpc/index.ts b/waspc/data/Generator/templates/sdk/client/operations/core.ts similarity index 95% rename from waspc/data/Generator/templates/sdk/rpc/index.ts rename to waspc/data/Generator/templates/sdk/client/operations/core.ts index 8a743e3456..282c4698a7 100644 --- a/waspc/data/Generator/templates/sdk/rpc/index.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/core.ts @@ -9,16 +9,19 @@ import { } from "@tanstack/react-query"; export { configureQueryClient } from "./queryClient"; +// PRIVATE API (but should maybe be public, users use values of this type) export type Query = { (queryCacheKey: string[], args: Input): Promise; }; +// PUBLIC API export function useQuery( queryFn: Query, queryFnArgs?: Input, options?: any ): UseQueryResult; +// PUBLIC API export function useQuery(queryFn, queryFnArgs, options) { if (typeof queryFn !== "function") { throw new TypeError("useQuery requires queryFn to be a function."); @@ -40,12 +43,12 @@ export function useQuery(queryFn, queryFnArgs, options) { }); } -// todo - turn helpers and core into the same thing - +// PRIVATE API (but should maybe be public, users use values of this type) export type Action = [Input] extends [never] ? (args?: unknown) => Promise : (args: Input) => Promise; +// PRIVATE API (but should maybe be public, users define values of this type) /** * An options object passed into the `useAction` hook and used to enhance the * action with extra options. @@ -55,6 +58,7 @@ export type ActionOptions = { optimisticUpdates: OptimisticUpdateDefinition[]; }; +// PUBLIC API /** * A documented (public) way to define optimistic updates. */ @@ -63,6 +67,7 @@ export type OptimisticUpdateDefinition = { updateQuery: UpdateQuery; }; +// PRIVATE API (but should maybe be public, users define values of this type) /** * A function that takes an item and returns a Wasp Query specifier. */ @@ -70,6 +75,7 @@ export type GetQuerySpecifier = ( item: ActionInput ) => QuerySpecifier; +// PRIVATE API (but should maybe be public, users define values of this type) /** * A function that takes an item and the previous state of the cache, and returns * the desired (new) state of the cache. @@ -79,12 +85,14 @@ export type UpdateQuery = ( oldData: CachedData | undefined ) => CachedData; +// PRIVATE API (but should maybe be public, users define values of this type) /** * A public query specifier used for addressing Wasp queries. See our docs for details: * https://wasp-lang.dev/docs/language/features#the-useaction-hook. */ export type QuerySpecifier = [Query, ...any[]]; +// PUBLIC API /** * A hook for adding extra behavior to a Wasp Action (e.g., optimistic updates). * diff --git a/waspc/data/Generator/templates/sdk/client/operations/index.ts b/waspc/data/Generator/templates/sdk/client/operations/index.ts new file mode 100644 index 0000000000..ec9ca9f689 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/client/operations/index.ts @@ -0,0 +1,22 @@ +// PUBLIC API +export * from './actions' +// MOSTLY PUBLIC API (see the file for details) +export * from './queries' + +export { + // PUBLIC API + useAction, + // PUBLIC API + useQuery, + // PUBLIC API + type OptimisticUpdateDefinition, +} from './core' + +export { + // PUBLIC API + configureQueryClient, + // PRIVATE API (framework code) + initializeQueryClient, + // PRIVATE API (framework code) + queryClientInitialized +} from './queryClient' diff --git a/waspc/data/Generator/templates/sdk/operations/index.ts b/waspc/data/Generator/templates/sdk/client/operations/internal/index.ts similarity index 94% rename from waspc/data/Generator/templates/sdk/operations/index.ts rename to waspc/data/Generator/templates/sdk/client/operations/internal/index.ts index 8ef076ee1f..551c595da5 100644 --- a/waspc/data/Generator/templates/sdk/operations/index.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/internal/index.ts @@ -5,8 +5,10 @@ import { deserialize as superjsonDeserialize, } from 'superjson' +// PRIVATE API export type OperationRoute = { method: HttpMethod, path: string } +// PRIVATE API export async function callOperation(operationRoute: OperationRoute & { method: HttpMethod.Post }, args: any) { try { const superjsonArgs = superjsonSerialize(args) @@ -17,6 +19,7 @@ export async function callOperation(operationRoute: OperationRoute & { method: H } } +// PRIVATE API export function makeOperationRoute(relativeOperationRoute: string): OperationRoute { return { method: HttpMethod.Post, path: `/${relativeOperationRoute}` } } diff --git a/waspc/data/Generator/templates/sdk/operations/resources.js b/waspc/data/Generator/templates/sdk/client/operations/internal/resources.js similarity index 97% rename from waspc/data/Generator/templates/sdk/operations/resources.js rename to waspc/data/Generator/templates/sdk/client/operations/internal/resources.js index 5261654600..470ef70b82 100644 --- a/waspc/data/Generator/templates/sdk/operations/resources.js +++ b/waspc/data/Generator/templates/sdk/client/operations/internal/resources.js @@ -1,4 +1,4 @@ -import { queryClientInitialized } from 'wasp/rpc/queryClient' +import { queryClientInitialized } from '../queryClient.js' import { makeUpdateHandlersMap } from './updateHandlersMap' import { hashQueryKey } from '@tanstack/react-query' @@ -8,6 +8,8 @@ import { hashQueryKey } from '@tanstack/react-query' const resourceToQueryCacheKeys = new Map() const updateHandlers = makeUpdateHandlersMap(hashQueryKey) + +// PRIVATE API /** * Remembers that specified query is using specified resources. * If called multiple times for same query, resources are added, not reset. diff --git a/waspc/data/Generator/templates/sdk/operations/updateHandlersMap.js b/waspc/data/Generator/templates/sdk/client/operations/internal/updateHandlersMap.js similarity index 100% rename from waspc/data/Generator/templates/sdk/operations/updateHandlersMap.js rename to waspc/data/Generator/templates/sdk/client/operations/internal/updateHandlersMap.js diff --git a/waspc/data/Generator/templates/sdk/rpc/queries/core.d.ts b/waspc/data/Generator/templates/sdk/client/operations/queries/core.d.ts similarity index 90% rename from waspc/data/Generator/templates/sdk/rpc/queries/core.d.ts rename to waspc/data/Generator/templates/sdk/client/operations/queries/core.d.ts index ddbb4f2b8e..0334c2cba6 100644 --- a/waspc/data/Generator/templates/sdk/rpc/queries/core.d.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/queries/core.d.ts @@ -1,12 +1,14 @@ -import { type Query } from '..' +import { type Query } from '../core.js' import { Route } from 'wasp/types' import type { Expand, _Awaited, _ReturnType } from 'wasp/universal/types' +// PRIVATE API export function createQuery( queryRoute: string, entitiesUsed: any[] ): QueryFor +// PRIVATE API export function addMetadataToQuery( query: (...args: any[]) => Promise, metadata: { diff --git a/waspc/data/Generator/templates/sdk/rpc/queries/core.js b/waspc/data/Generator/templates/sdk/client/operations/queries/core.js similarity index 85% rename from waspc/data/Generator/templates/sdk/rpc/queries/core.js rename to waspc/data/Generator/templates/sdk/client/operations/queries/core.js index 616fb82958..c4b3e5f2e4 100644 --- a/waspc/data/Generator/templates/sdk/rpc/queries/core.js +++ b/waspc/data/Generator/templates/sdk/client/operations/queries/core.js @@ -1,9 +1,10 @@ -import { callOperation, makeOperationRoute } from 'wasp/operations' +import { callOperation, makeOperationRoute } from '../internal/index.js' import { addResourcesUsedByQuery, getActiveOptimisticUpdates, -} from 'wasp/operations/resources' +} from '../internal/resources' +// PRIVATE API export function createQuery(relativeQueryPath, entitiesUsed) { const queryRoute = makeOperationRoute(relativeQueryPath) @@ -20,6 +21,7 @@ export function createQuery(relativeQueryPath, entitiesUsed) { return query } +// PRIVATE API export function addMetadataToQuery( query, { relativeQueryPath, queryRoute, entitiesUsed } diff --git a/waspc/data/Generator/templates/sdk/rpc/queries/index.ts b/waspc/data/Generator/templates/sdk/client/operations/queries/index.ts similarity index 91% rename from waspc/data/Generator/templates/sdk/rpc/queries/index.ts rename to waspc/data/Generator/templates/sdk/client/operations/queries/index.ts index 631e787cf0..6dfaa615b8 100644 --- a/waspc/data/Generator/templates/sdk/rpc/queries/index.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/queries/index.ts @@ -5,10 +5,12 @@ import { createQuery } from './core' {=/ queries =} {=# queries =} +// PUBLIC API export const {= operationName =} = createQuery<{= operationTypeName =}>( '{= queryRoute =}', {=& entitiesArray =}, ) {=/ queries =} +// PRIVATE API export { addMetadataToQuery } from './core' diff --git a/waspc/data/Generator/templates/sdk/rpc/queryClient.ts b/waspc/data/Generator/templates/sdk/client/operations/queryClient.ts similarity index 86% rename from waspc/data/Generator/templates/sdk/rpc/queryClient.ts rename to waspc/data/Generator/templates/sdk/client/operations/queryClient.ts index cf33c071ee..c2d1cb86df 100644 --- a/waspc/data/Generator/templates/sdk/rpc/queryClient.ts +++ b/waspc/data/Generator/templates/sdk/client/operations/queryClient.ts @@ -6,14 +6,14 @@ let queryClientConfig: QueryClientConfig, resolveQueryClientInitialized: (...args: any[]) => any, isQueryClientInitialized: boolean; -// Used in framework code, shouldn't be public +// PRIVATE API (framework code) export const queryClientInitialized: Promise = new Promise( (resolve) => { resolveQueryClientInitialized = resolve; } ); -// Used by users, should be public +// PUBLIC API export function configureQueryClient(config: QueryClientConfig): void { if (isQueryClientInitialized) { throw new Error( @@ -24,7 +24,7 @@ export function configureQueryClient(config: QueryClientConfig): void { queryClientConfig = config; } -// Used in framework code, shouldn't be public +// PRIVATE API (framework code) export function initializeQueryClient(): void { const queryClient = new QueryClient( queryClientConfig ?? defaultQueryClientConfig diff --git a/waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx b/waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx index aa2eba4fa8..4abd4f49de 100644 --- a/waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx +++ b/waspc/data/Generator/templates/sdk/client/test/vitest/helpers.tsx @@ -6,7 +6,7 @@ import { BrowserRouter as Router } from 'react-router-dom' import { render, RenderResult, cleanup } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { beforeAll, afterEach, afterAll } from 'vitest' -import { Query } from 'wasp/rpc' +import { Query } from 'wasp/client/operations/core' import config from 'wasp/core/config' import { HttpMethod, Route } from 'wasp/types' diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 6e3b7978e4..4d57268bdd 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -20,18 +20,6 @@ "./core/storage": "./dist/core/storage.js", "./core/auth": "./dist/core/auth.js", {=! Used by users, documented. =} - "./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", {=! Used by our code, uncodumented (but accessible) for users. =} "./auth/helpers/user": "./dist/auth/helpers/user.js", @@ -113,6 +101,7 @@ "./server/webSocket": "./dist/server/webSocket/index.js", "./auth": "./dist/auth/index.js", "./client/auth": "./dist/client/auth/index.js", + "./client/operations": "./dist/client/operations/index.js", "./server/auth": "./dist/server/auth/index.js", "./server/crud": "./dist/server/crud/index.js", "./client/crud": "./dist/client/crud/index.js", diff --git a/waspc/examples/todo-typescript/src/MainPage.tsx b/waspc/examples/todo-typescript/src/MainPage.tsx index cb34521acd..c166a10816 100644 --- a/waspc/examples/todo-typescript/src/MainPage.tsx +++ b/waspc/examples/todo-typescript/src/MainPage.tsx @@ -1,13 +1,12 @@ import './Main.css' import React, { useEffect, FormEventHandler, FormEvent } from 'react' -import { useQuery, useAction } from 'wasp/rpc' // Wasp uses a thin wrapper around react-query -import { getTasks } from 'wasp/rpc/queries' import { createTask, - updateTask, - deleteTasks, customEmailSending, -} from 'wasp/rpc/actions' + deleteTasks, + getTasks, + useQuery, +} from 'wasp/client/operations' import waspLogo from './waspLogo.png' import type { Task } from 'wasp/entities' import { AuthUser, getFirstProviderUserId } from 'wasp/auth' diff --git a/waspc/examples/todo-typescript/src/Todo.test.tsx b/waspc/examples/todo-typescript/src/Todo.test.tsx index 87bff2532d..1c47e94fab 100644 --- a/waspc/examples/todo-typescript/src/Todo.test.tsx +++ b/waspc/examples/todo-typescript/src/Todo.test.tsx @@ -2,11 +2,11 @@ import { test, expect } from 'vitest' import { screen } from '@testing-library/react' import { mockServer, renderInContext } from 'wasp/client/test' -import { getTasks } from 'wasp/rpc/queries' import { Todo, areThereAnyTasks } from './Todo' import { MainPage } from './MainPage' import type { AuthUser } from 'wasp/auth' import { getMe } from 'wasp/client/auth' +import { getTasks } from 'wasp/client/operations' import { Tasks } from 'wasp/client/crud' const mockTasks = [ diff --git a/waspc/examples/todo-typescript/src/Todo.tsx b/waspc/examples/todo-typescript/src/Todo.tsx index f40dc10ca9..c6b61e6cc2 100644 --- a/waspc/examples/todo-typescript/src/Todo.tsx +++ b/waspc/examples/todo-typescript/src/Todo.tsx @@ -1,6 +1,6 @@ -import { FormEventHandler } from "react" -import { Task } from "wasp/entities" -import { updateTask, deleteTasks } from 'wasp/rpc/actions' +import { FormEventHandler } from 'react' +import { Task } from 'wasp/entities' +import { updateTask, deleteTasks } from 'wasp/client/operations' export function Todo({ id, isDone, description }: Task) { const handleIsDoneChange: FormEventHandler = async ( diff --git a/waspc/examples/todo-typescript/src/setup/clientSetup.ts b/waspc/examples/todo-typescript/src/setup/clientSetup.ts index 361f2892bc..6dcbfe0d7d 100644 --- a/waspc/examples/todo-typescript/src/setup/clientSetup.ts +++ b/waspc/examples/todo-typescript/src/setup/clientSetup.ts @@ -1,4 +1,4 @@ -import { configureQueryClient } from 'wasp/rpc' +import { configureQueryClient } from "wasp/client/operations" export default async function mySetupFunction(): Promise { console.log('Setting up client...') diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 719f89851d..ca3332ef1e 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -39,16 +39,16 @@ import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.SdkGenerator.AuthG (genAuth) import Wasp.Generator.SdkGenerator.Client.AuthG (genNewClientAuth) import Wasp.Generator.SdkGenerator.Client.CrudG (genNewClientCrudApi) +import qualified Wasp.Generator.SdkGenerator.Client.OperationsGenerator as ClientOpsGen import Wasp.Generator.SdkGenerator.Client.RouterGenerator (genNewClientRouterApi) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.CrudG (genCrud) -import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) import Wasp.Generator.SdkGenerator.Server.EmailSenderG (depsRequiredByEmail, genNewEmailSenderApi) import Wasp.Generator.SdkGenerator.Server.JobGenerator (genNewJobsApi) import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) -import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) +import qualified Wasp.Generator.SdkGenerator.ServerOpsGenerator as ServerOpsGen import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) import qualified Wasp.Generator.ServerGenerator.AuthG as ServerAuthG import qualified Wasp.Generator.WebAppGenerator.Common as WebApp @@ -83,11 +83,6 @@ genSdkReal spec = genFileCopy [relfile|core/auth.ts|], genFileCopy [relfile|core/storage.ts|], genFileCopy [relfile|core/stitches.config.ts|], - -- Not migrated to TS yet - genFileCopy [relfile|operations/resources.js|], - genFileCopy [relfile|operations/index.ts|], - -- Not migrated to TS yet - genFileCopy [relfile|operations/updateHandlersMap.js|], genFileCopy [relfile|server/index.ts|], genFileCopy [relfile|server/dbClient.ts|], genFileCopy [relfile|server/HttpError.ts|], @@ -101,9 +96,9 @@ genSdkReal spec = genServerUtils spec, genPackageJson spec ] - <++> genRpc spec + <++> ClientOpsGen.genOperations spec <++> genAuth spec - <++> genOperations spec + <++> ServerOpsGen.genOperations spec <++> genUniversalDir <++> genExternalCodeDir (AS.externalCodeFiles spec) <++> genEntitiesAndServerTypesDirs spec diff --git a/waspc/src/Wasp/Generator/SdkGenerator/RpcGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs similarity index 70% rename from waspc/src/Wasp/Generator/SdkGenerator/RpcGenerator.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs index d532e00cf0..2d2213c7c7 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/RpcGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs @@ -1,10 +1,10 @@ -module Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) where +module Wasp.Generator.SdkGenerator.Client.OperationsGenerator (genOperations) where import Data.Aeson (KeyValue ((.=)), object) import qualified Data.Aeson as Aeson import Data.Aeson.Types (Pair) import Data.Maybe (fromJust) -import StrongPath (relfile) +import StrongPath (Dir, File', Path', Rel, reldir, relfile, ()) import qualified StrongPath as SP import Wasp.AppSpec (AppSpec (..)) import qualified Wasp.AppSpec as AS @@ -14,7 +14,7 @@ import qualified Wasp.AppSpec.Query as AS.Query import Wasp.Generator.Common (makeJsArrayFromHaskellList) import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath, relDirToRelFileP) +import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir, clientTemplatesDirInSdkTemplatesDir, makeSdkImportPath, relDirToRelFileP) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.Generator.SdkGenerator.ServerOpsGenerator (serverOperationsDirInSdkRootDir) import qualified Wasp.Generator.ServerGenerator as ServerGenerator @@ -22,37 +22,49 @@ import qualified Wasp.Generator.ServerGenerator.OperationsRoutesG as ServerOpera import Wasp.JsImport (JsImportName (JsImportField), JsImportPath (ModuleImportPath), getJsImportStmtAndIdentifier, makeJsImport) import Wasp.Util (toUpperFirst, (<++>)) -genRpc :: AppSpec -> Generator [FileDraft] -genRpc spec = +data ClientOpsTemplatesDir + +clientOpsDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) (Dir ClientOpsTemplatesDir) +clientOpsDirInSdkTemplatesDir = clientTemplatesDirInSdkTemplatesDir [reldir|operations|] + +genClientOpsFileCopy :: Path' (Rel ClientOpsTemplatesDir) File' -> Generator FileDraft +genClientOpsFileCopy path = return $ C.mkTmplFd $ clientOpsDirInSdkTemplatesDir path + +genOperations :: AppSpec -> Generator [FileDraft] +genOperations spec = sequence - [ genFileCopy [relfile|rpc/index.ts|], - genFileCopy [relfile|rpc/queryClient.ts|] + [ -- Not migrated to TS yet + genClientOpsFileCopy [relfile|internal/resources.js|], + genClientOpsFileCopy [relfile|internal/index.ts|], + -- Not migrated to TS yet + genClientOpsFileCopy [relfile|internal/updateHandlersMap.js|], + genClientOpsFileCopy [relfile|core.ts|], + genClientOpsFileCopy [relfile|index.ts|], + genClientOpsFileCopy [relfile|queryClient.ts|] ] <++> genQueries spec <++> genActions spec - where - genFileCopy = return . C.mkTmplFd genQueries :: AppSpec -> Generator [FileDraft] genQueries spec = (:) <$> genQueriesIndex spec - <*> return - [ C.mkTmplFd [relfile|rpc/queries/core.js|], - C.mkTmplFd [relfile|rpc/queries/core.d.ts|] + <*> sequence + [ genClientOpsFileCopy [relfile|queries/core.js|], + genClientOpsFileCopy [relfile|queries/core.d.ts|] ] genActions :: AppSpec -> Generator [FileDraft] genActions spec = (:) <$> genActionsIndex spec - <*> return - [ C.mkTmplFd [relfile|rpc/actions/core.js|], - C.mkTmplFd [relfile|rpc/actions/core.d.ts|] + <*> sequence + [ genClientOpsFileCopy [relfile|actions/core.js|], + genClientOpsFileCopy [relfile|actions/core.d.ts|] ] genQueriesIndex :: AppSpec -> Generator FileDraft genQueriesIndex spec = return $ C.mkTmplFdWithData relPath tmplData where - relPath = [relfile|rpc/queries/index.ts|] + relPath = clientOpsDirInSdkTemplatesDir [relfile|queries/index.ts|] tmplData = object [ "queries" .= map getQueryData (AS.getQueries spec) @@ -61,7 +73,7 @@ genQueriesIndex spec = return $ C.mkTmplFdWithData relPath tmplData genActionsIndex :: AppSpec -> Generator FileDraft genActionsIndex spec = return $ C.mkTmplFdWithData relPath tmplData where - relPath = [relfile|rpc/actions/index.ts|] + relPath = clientOpsDirInSdkTemplatesDir [relfile|actions/index.ts|] tmplData = object [ "actions" .= map getActionData (AS.getActions spec) diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs index ed3cf975f0..189e1db167 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs @@ -13,6 +13,8 @@ data SdkRootDir data SdkTemplatesDir +data ClientTemplatesDir + asTmplFile :: Path' (Rel d) File' -> Path' (Rel SdkTemplatesDir) File' asTmplFile = SP.castRel @@ -60,3 +62,6 @@ makeSdkImportPath path = [reldirP|wasp|] path extCodeDirInSdkRootDir :: Path' (Rel SdkRootDir) Dir' extCodeDirInSdkRootDir = [reldir|ext-src|] + +clientTemplatesDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) (Dir ClientTemplatesDir) +clientTemplatesDirInSdkTemplatesDir = [reldir|client|] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 00f9282f18..27f6283397 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -300,11 +300,11 @@ library Wasp.Generator.SdkGenerator.AuthG Wasp.Generator.SdkGenerator.Client.AuthG Wasp.Generator.SdkGenerator.Client.CrudG + Wasp.Generator.SdkGenerator.Client.OperationsGenerator Wasp.Generator.SdkGenerator.Client.RouterGenerator Wasp.Generator.SdkGenerator.Common Wasp.Generator.SdkGenerator.CrudG Wasp.Generator.SdkGenerator.EmailSender.Providers - Wasp.Generator.SdkGenerator.RpcGenerator Wasp.Generator.SdkGenerator.Server.AuthG Wasp.Generator.SdkGenerator.Server.CrudG Wasp.Generator.SdkGenerator.Server.EmailSenderG From 1754e47b6accfd01d9356f442a32ce6f45d1406d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Sodi=C4=87?= Date: Wed, 31 Jan 2024 17:43:15 +0100 Subject: [PATCH 19/19] Update imports and API for `server/operations` (#1711) --- .../data/Generator/templates/sdk/package.json | 9 +-- .../server/{ => operations}/actions/index.ts | 2 + .../server/{ => operations}/actions/types.ts | 1 + .../templates/sdk/server/operations/index.ts | 4 ++ .../server/{ => operations}/queries/index.ts | 2 + .../server/{ => operations}/queries/types.ts | 1 + .../todo-typescript/src/task/actions.ts | 2 +- .../todo-typescript/src/task/queries.ts | 2 +- waspc/src/Wasp/Generator/SdkGenerator.hs | 4 +- .../Client/OperationsGenerator.hs | 2 +- .../src/Wasp/Generator/SdkGenerator/Common.hs | 5 ++ .../src/Wasp/Generator/SdkGenerator/CrudG.hs | 2 +- .../OperationsGenerator.hs} | 57 ++++++++----------- .../SdkGenerator/WebSocketGenerator.hs | 2 +- waspc/waspc.cabal | 2 +- 15 files changed, 53 insertions(+), 44 deletions(-) rename waspc/data/Generator/templates/sdk/server/{ => operations}/actions/index.ts (96%) rename waspc/data/Generator/templates/sdk/server/{ => operations}/actions/types.ts (98%) create mode 100644 waspc/data/Generator/templates/sdk/server/operations/index.ts rename waspc/data/Generator/templates/sdk/server/{ => operations}/queries/index.ts (96%) rename waspc/data/Generator/templates/sdk/server/{ => operations}/queries/types.ts (98%) rename waspc/src/Wasp/Generator/SdkGenerator/{ServerOpsGenerator.hs => Server/OperationsGenerator.hs} (72%) diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 4d57268bdd..4cf105ab63 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -56,10 +56,10 @@ "./universal/validators": "./dist/universal/validators.js", {=! Parts are used by users, documented. Parts are probably used by our code, undocumented (but accessible). =} "./server/utils": "./dist/server/utils.js", - {=! 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", + {=! Used by our code (SDK for full-stack type safety), uncodumented (but accessible) for users. =} + "./server/opeations/actions": "./dist/server/actions/index.js", + {=! Used by our code (SDK for full-stack type safety), uncodumented (but accessible) for users. =} + "./server/operations/queries": "./dist/server/queries/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} "./server/auth/email": "./dist/server/auth/email/index.js", {=! Used by our code, uncodumented (but accessible) for users. =} @@ -107,6 +107,7 @@ "./client/crud": "./dist/client/crud/index.js", "./server/email": "./dist/server/email/index.js", "./server/jobs": "./dist/server/jobs/index.js", + "./server/operations": "./dist/server/operations/index.js", "./client/router": "./dist/client/router/index.js", "./client/test": "./dist/client/test/index.js" }, diff --git a/waspc/data/Generator/templates/sdk/server/actions/index.ts b/waspc/data/Generator/templates/sdk/server/operations/actions/index.ts similarity index 96% rename from waspc/data/Generator/templates/sdk/server/actions/index.ts rename to waspc/data/Generator/templates/sdk/server/operations/actions/index.ts index a00094e8fe..6ef6340f16 100644 --- a/waspc/data/Generator/templates/sdk/server/actions/index.ts +++ b/waspc/data/Generator/templates/sdk/server/operations/actions/index.ts @@ -11,8 +11,10 @@ import { prisma } from 'wasp/server' {=/ operations =} {=# operations =} +// PRIVATE API export type {= operationTypeName =} = typeof {= jsFn.importIdentifier =} +// PUBLIC API export const {= operationName =} = async (args, context) => { return ({= jsFn.importIdentifier =} as any)(args, { ...context, diff --git a/waspc/data/Generator/templates/sdk/server/actions/types.ts b/waspc/data/Generator/templates/sdk/server/operations/actions/types.ts similarity index 98% rename from waspc/data/Generator/templates/sdk/server/actions/types.ts rename to waspc/data/Generator/templates/sdk/server/operations/actions/types.ts index 15a045acdf..aa8f56d0d8 100644 --- a/waspc/data/Generator/templates/sdk/server/actions/types.ts +++ b/waspc/data/Generator/templates/sdk/server/operations/actions/types.ts @@ -15,6 +15,7 @@ import { } from 'wasp/server/_types' {=# operations =} +// PUBLIC API export type {= typeName =} = {=# usesAuth =} AuthenticatedAction< diff --git a/waspc/data/Generator/templates/sdk/server/operations/index.ts b/waspc/data/Generator/templates/sdk/server/operations/index.ts new file mode 100644 index 0000000000..021333fdf2 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/operations/index.ts @@ -0,0 +1,4 @@ +// PUBLIC API +export * from './queries/types' +// PUBLIC API +export * from './actions/types' diff --git a/waspc/data/Generator/templates/sdk/server/queries/index.ts b/waspc/data/Generator/templates/sdk/server/operations/queries/index.ts similarity index 96% rename from waspc/data/Generator/templates/sdk/server/queries/index.ts rename to waspc/data/Generator/templates/sdk/server/operations/queries/index.ts index 1c8cf33b05..dd7d537eea 100644 --- a/waspc/data/Generator/templates/sdk/server/queries/index.ts +++ b/waspc/data/Generator/templates/sdk/server/operations/queries/index.ts @@ -11,8 +11,10 @@ import { prisma } from 'wasp/server' {=/ operations =} {=# operations =} +// PRIVATE API export type {= operationTypeName =} = typeof {= jsFn.importIdentifier =} +// PUBLIC API export const {= operationName =} = async (args, context) => { return ({= jsFn.importIdentifier =} as any)(args, { ...context, diff --git a/waspc/data/Generator/templates/sdk/server/queries/types.ts b/waspc/data/Generator/templates/sdk/server/operations/queries/types.ts similarity index 98% rename from waspc/data/Generator/templates/sdk/server/queries/types.ts rename to waspc/data/Generator/templates/sdk/server/operations/queries/types.ts index 3ae08913a3..7243631993 100644 --- a/waspc/data/Generator/templates/sdk/server/queries/types.ts +++ b/waspc/data/Generator/templates/sdk/server/operations/queries/types.ts @@ -16,6 +16,7 @@ import { } from 'wasp/server/_types' {=# operations =} +// PUBLIC API export type {= typeName =} = {=# usesAuth =} AuthenticatedQuery< diff --git a/waspc/examples/todo-typescript/src/task/actions.ts b/waspc/examples/todo-typescript/src/task/actions.ts index fbeba40687..0abaeb68d5 100644 --- a/waspc/examples/todo-typescript/src/task/actions.ts +++ b/waspc/examples/todo-typescript/src/task/actions.ts @@ -3,7 +3,7 @@ import type { CreateTask, UpdateTask, DeleteTasks, -} from 'wasp/server/actions/types' +} from 'wasp/server/operations' import type { Task } from 'wasp/entities' import { emailSender } from 'wasp/server/email' diff --git a/waspc/examples/todo-typescript/src/task/queries.ts b/waspc/examples/todo-typescript/src/task/queries.ts index 1b635d17c7..91e0d9fa11 100644 --- a/waspc/examples/todo-typescript/src/task/queries.ts +++ b/waspc/examples/todo-typescript/src/task/queries.ts @@ -1,5 +1,5 @@ import { HttpError, AuthError } from 'wasp/server' -import type { GetTasks } from 'wasp/server/queries/types' +import type { GetTasks } from 'wasp/server/operations' import type { Task } from 'wasp/entities' import { ensureValidEmail, createProviderId } from 'wasp/server/auth' diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index ca3332ef1e..31cc543c1b 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -47,8 +47,8 @@ import Wasp.Generator.SdkGenerator.Server.AuthG (genNewServerApi) import Wasp.Generator.SdkGenerator.Server.CrudG (genNewServerCrudApi) import Wasp.Generator.SdkGenerator.Server.EmailSenderG (depsRequiredByEmail, genNewEmailSenderApi) import Wasp.Generator.SdkGenerator.Server.JobGenerator (genNewJobsApi) +import qualified Wasp.Generator.SdkGenerator.Server.OperationsGenerator as ServerOpsGen import Wasp.Generator.SdkGenerator.ServerApiG (genServerApi) -import qualified Wasp.Generator.SdkGenerator.ServerOpsGenerator as ServerOpsGen import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) import qualified Wasp.Generator.ServerGenerator.AuthG as ServerAuthG import qualified Wasp.Generator.WebAppGenerator.Common as WebApp @@ -96,9 +96,9 @@ genSdkReal spec = genServerUtils spec, genPackageJson spec ] + <++> ServerOpsGen.genOperations spec <++> ClientOpsGen.genOperations spec <++> genAuth spec - <++> ServerOpsGen.genOperations spec <++> genUniversalDir <++> genExternalCodeDir (AS.externalCodeFiles spec) <++> genEntitiesAndServerTypesDirs spec diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs index 2d2213c7c7..6780c72e2c 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs @@ -16,7 +16,7 @@ import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir, clientTemplatesDirInSdkTemplatesDir, makeSdkImportPath, relDirToRelFileP) import qualified Wasp.Generator.SdkGenerator.Common as C -import Wasp.Generator.SdkGenerator.ServerOpsGenerator (serverOperationsDirInSdkRootDir) +import Wasp.Generator.SdkGenerator.Server.OperationsGenerator (serverOperationsDirInSdkRootDir) import qualified Wasp.Generator.ServerGenerator as ServerGenerator import qualified Wasp.Generator.ServerGenerator.OperationsRoutesG as ServerOperationsRoutesG import Wasp.JsImport (JsImportName (JsImportField), JsImportPath (ModuleImportPath), getJsImportStmtAndIdentifier, makeJsImport) diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs index 189e1db167..3fe9336e06 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs @@ -15,6 +15,8 @@ data SdkTemplatesDir data ClientTemplatesDir +data ServerTemplatesDir + asTmplFile :: Path' (Rel d) File' -> Path' (Rel SdkTemplatesDir) File' asTmplFile = SP.castRel @@ -65,3 +67,6 @@ extCodeDirInSdkRootDir = [reldir|ext-src|] clientTemplatesDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) (Dir ClientTemplatesDir) clientTemplatesDirInSdkTemplatesDir = [reldir|client|] + +serverTemplatesDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) (Dir ServerTemplatesDir) +serverTemplatesDirInSdkTemplatesDir = [reldir|server|] diff --git a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs index 30bc7ccdf9..1d8a145fb8 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs @@ -21,7 +21,7 @@ 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.Generator.SdkGenerator.Server.OperationsGenerator (extImportToJsImport) genCrud :: AppSpec -> Generator [FileDraft] genCrud spec = diff --git a/waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs similarity index 72% rename from waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs index 63e82fb20e..b6c82e634d 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs @@ -1,12 +1,12 @@ {-# LANGUAGE TypeApplications #-} -module Wasp.Generator.SdkGenerator.ServerOpsGenerator where +module Wasp.Generator.SdkGenerator.Server.OperationsGenerator where import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson import Data.List (nub) import Data.Maybe (fromJust, fromMaybe) -import StrongPath (Dir', File', Path', Rel, reldir, relfile, ()) +import StrongPath (Dir, Dir', File', Path', Rel, reldir, relfile, ()) import qualified StrongPath as SP import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS @@ -20,25 +20,34 @@ import Wasp.Generator.Common (makeJsonWithEntityData) import Wasp.Generator.FileDraft (FileDraft) import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.Monad (Generator) -import Wasp.Generator.SdkGenerator.Common (mkTmplFdWithData) +import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir, mkTmplFdWithData, serverTemplatesDirInSdkTemplatesDir) import qualified Wasp.Generator.SdkGenerator.Common as C import Wasp.JsImport (JsImport (..), JsImportPath (..)) import qualified Wasp.JsImport as JI import Wasp.Util (toUpperFirst) +data ServerOpsTemplatesDir + +serverOpsDirInSdkTemplatesDir :: Path' (Rel SdkTemplatesDir) (Dir ServerOpsTemplatesDir) +serverOpsDirInSdkTemplatesDir = serverTemplatesDirInSdkTemplatesDir [reldir|operations|] + +genServerOpsFileCopy :: Path' (Rel ServerOpsTemplatesDir) File' -> Generator FileDraft +genServerOpsFileCopy path = return $ C.mkTmplFd $ serverOpsDirInSdkTemplatesDir path + genOperations :: AppSpec -> Generator [FileDraft] genOperations spec = sequence [ genQueryTypesFile spec, genActionTypesFile spec, genQueriesIndex spec, - genActionsIndex spec + genActionsIndex spec, + genServerOpsFileCopy [relfile|index.ts|] ] genQueriesIndex :: AppSpec -> Generator FileDraft genQueriesIndex spec = return $ mkTmplFdWithData relPath tmplData where - relPath = [relfile|server/queries/index.ts|] + relPath = serverOpsDirInSdkTemplatesDir [relfile|queries/index.ts|] tmplData = object [ "operations" .= map getQueryData (AS.getQueries spec) @@ -47,25 +56,23 @@ genQueriesIndex spec = return $ mkTmplFdWithData relPath tmplData genActionsIndex :: AppSpec -> Generator FileDraft genActionsIndex spec = return $ mkTmplFdWithData relPath tmplData where - relPath = [relfile|server/actions/index.ts|] + relPath = serverOpsDirInSdkTemplatesDir [relfile|actions/index.ts|] tmplData = object [ "operations" .= map getActionData (AS.getActions spec) ] genQueryTypesFile :: AppSpec -> Generator FileDraft -genQueryTypesFile spec = genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally +genQueryTypesFile spec = genOperationTypesFile relPath operations isAuthEnabledGlobally where - tmplFile = [relfile|server/queries/types.ts|] - dstFile = [relfile|server/queries/types.ts|] + relPath = serverOpsDirInSdkTemplatesDir [relfile|queries/types.ts|] operations = map (uncurry AS.Operation.QueryOp) $ AS.getQueries spec isAuthEnabledGlobally = isAuthEnabled spec genActionTypesFile :: AppSpec -> Generator FileDraft -genActionTypesFile spec = genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally +genActionTypesFile spec = genOperationTypesFile relPath operations isAuthEnabledGlobally where - tmplFile = [relfile|server/actions/types.ts|] - dstFile = [relfile|server/actions/types.ts|] + relPath = serverOpsDirInSdkTemplatesDir [relfile|actions/types.ts|] operations = map (uncurry AS.Operation.ActionOp) $ AS.getActions spec isAuthEnabledGlobally = isAuthEnabled spec @@ -84,12 +91,11 @@ getActionData (actionName, action) = getOperationTmplData operation genOperationTypesFile :: Path' (Rel C.SdkTemplatesDir) File' -> - Path' (Rel C.SdkRootDir) File' -> [AS.Operation.Operation] -> Bool -> Generator FileDraft -genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally = - return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) +genOperationTypesFile tmplFile operations isAuthEnabledGlobally = + return $ C.mkTmplFdWithData tmplFile tmplData where tmplData = object @@ -108,8 +114,10 @@ genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally = usesAuth = fromMaybe isAuthEnabledGlobally . AS.Operation.getAuth serverOperationsDirInSdkRootDir :: AS.Operation.Operation -> Path' (Rel C.SdkRootDir) Dir' -serverOperationsDirInSdkRootDir (AS.Operation.QueryOp _ _) = [reldir|server/queries|] -serverOperationsDirInSdkRootDir (AS.Operation.ActionOp _ _) = [reldir|server/actions|] +serverOperationsDirInSdkRootDir = + SP.castRel . (serverOpsDirInSdkTemplatesDir ) . \case + (AS.Operation.QueryOp _ _) -> [reldir|queries|] + (AS.Operation.ActionOp _ _) -> [reldir|actions|] getOperationTmplData :: AS.Operation.Operation -> Aeson.Value getOperationTmplData operation = @@ -143,18 +151,3 @@ extImportToJsImport extImport@(EI.ExtImport extImportName extImportPath) = importPath = C.makeSdkImportPath $ extCodeDirP SP.castRel extImportPath 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 --- ] --- 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" diff --git a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs index ae6758780e..d2b089ec42 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs @@ -18,7 +18,7 @@ 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.Generator.SdkGenerator.Server.OperationsGenerator (extImportToJsImport) import qualified Wasp.Generator.WebSocket as AS.WS genWebSockets :: AppSpec -> Generator [FileDraft] diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 27f6283397..4d55cd6dad 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -309,8 +309,8 @@ library Wasp.Generator.SdkGenerator.Server.CrudG Wasp.Generator.SdkGenerator.Server.EmailSenderG Wasp.Generator.SdkGenerator.Server.JobGenerator + Wasp.Generator.SdkGenerator.Server.OperationsGenerator Wasp.Generator.SdkGenerator.ServerApiG - Wasp.Generator.SdkGenerator.ServerOpsGenerator Wasp.Generator.SdkGenerator.WebSocketGenerator Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport