From 24920c0bf5bc4dfee43dd3cf68db7f24723f6c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Sodi=C4=87?= Date: Fri, 12 Jul 2024 12:35:05 +0200 Subject: [PATCH] Reintroduce TypeScript docs (#2153) --- web/docs/_TypescriptServerNote.md | 11 ++ web/docs/_WaspStartNote.md | 7 + web/docs/advanced/web-sockets.md | 6 +- web/docs/data-model/backends.md | 20 ++- web/docs/data-model/crud.md | 18 +- web/docs/data-model/entities.md | 42 +++++ .../data-model/operations/_superjson-note.md | 4 +- web/docs/data-model/operations/actions.md | 79 +++++++-- web/docs/data-model/operations/queries.md | 87 ++++++++-- web/docs/general/typescript.md | 158 ++++++++++++++++++ web/docs/introduction/editor-setup.md | 3 + web/docs/tutorial/01-create.md | 37 +++- web/docs/tutorial/02-project-structure.md | 57 ++++--- web/docs/tutorial/03-pages.md | 11 +- web/sidebars.js | 2 +- 15 files changed, 469 insertions(+), 73 deletions(-) create mode 100644 web/docs/_TypescriptServerNote.md create mode 100644 web/docs/_WaspStartNote.md create mode 100644 web/docs/general/typescript.md diff --git a/web/docs/_TypescriptServerNote.md b/web/docs/_TypescriptServerNote.md new file mode 100644 index 000000000..1fee6c0d8 --- /dev/null +++ b/web/docs/_TypescriptServerNote.md @@ -0,0 +1,11 @@ +:::caution LSP Problems + +If you are using TypeScript, your editor may sometimes report type and import errors even while `wasp start` is running. + +This happens when the TypeScript Language Server gets out of sync with the current code. +If you're using VS Code, you can manually restart the language server by opening the command palette and selecting _"TypeScript: Restart TS Server."_ +Open the command pallete with: + - `Ctrl` + `Shift` + `P` if you're on Windows or Linux. + - `Cmd` + `Shift` + `P` if you're on a Mac. + +::: diff --git a/web/docs/_WaspStartNote.md b/web/docs/_WaspStartNote.md new file mode 100644 index 000000000..90fb67a95 --- /dev/null +++ b/web/docs/_WaspStartNote.md @@ -0,0 +1,7 @@ +:::tip Keep Wasp start running + +`wasp start` automatically picks up the changes you make, regenerates the code, and restarts the app. So keep it running in the background. + +It also improves your experience by tracking the working directory and ensuring the generated code/types are up to date with your changes. + +::: diff --git a/web/docs/advanced/web-sockets.md b/web/docs/advanced/web-sockets.md index 010faef59..c936c14b5 100644 --- a/web/docs/advanced/web-sockets.md +++ b/web/docs/advanced/web-sockets.md @@ -146,7 +146,7 @@ All the hooks we use are typed with the events and payloads you defined on the s ::: -### `useSocket` Hook +### The `useSocket` Hook Client access to WebSockets is provided by the `useSocket` hook. It returns: - `socket: Socket` for sending and receiving events. @@ -156,7 +156,7 @@ Client access to WebSockets is provided by the `useSocket` hook. It returns: All components using `useSocket` share the same underlying `socket`. -### `useSocketListener` Hook +### The `useSocketListener` Hook Additionally, there is a `useSocketListener: (event, callback) => void` hook which is used for registering event handlers. It takes care of unregistering the handler on unmount. @@ -222,7 +222,7 @@ export const ChatPage = () => { -Wasp's **full-stack type safety** kicks in here: all the event types and payloads are automatically inferred from the server and are available on the client 🔥 +Wasp's **full-stack type safety** kicks in here: all the event types and payloads are automatically inferred from the server and are available on the client. You can additionally use the `ClientToServerPayload` and `ServerToClientPayload` helper types to get the payload type for a specific event. diff --git a/web/docs/data-model/backends.md b/web/docs/data-model/backends.md index 75a3b418c..5181e8ca1 100644 --- a/web/docs/data-model/backends.md +++ b/web/docs/data-model/backends.md @@ -147,10 +147,10 @@ To run your Wasp app in production, you'll need to switch from SQLite to Postgre **Database seeding** is a term used for populating the database with some initial data. -Seeding is most commonly used for two following scenarios: +Seeding is most commonly used for: -1. To put the development database into a state convenient for working and testing. -2. To initialize any database (`dev`, `staging`, or `prod`) with essential data it requires to operate. +1. Getting the development database into a state convenient for working and testing. +2. Initializing any database (`dev`, `staging`, or `prod`) with essential data it requires to operate. For example, populating the Currency table with default currencies, or the Country table with all available countries. ### Writing a Seed Function @@ -244,11 +244,12 @@ async function createUser(prisma, data) { ```ts import { createTask } from './actions.js' +import { type DbSeedFn } from 'wasp/server' import { sanitizeAndSerializeProviderData } from 'wasp/server/auth' import { type AuthUser } from 'wasp/auth' import { PrismaClient } from '@prisma/client' -export const devSeedSimple = async (prisma: PrismaClient) => { +export const devSeedSimple: DbSeedFn = async (prisma) => { const user = await createUser(prisma, { username: 'RiuTheDog', password: 'bark1234', @@ -286,6 +287,17 @@ async function createUser( } ``` +Wasp exports a type called `DbSeedFn` which you can use to easily type your seeding function. +Wasp defines `DbSeedFn` like this: + +```typescript +type DbSeedFn = (prisma: PrismaClient) => Promise +``` + +Annotating the function `devSeedSimple` with this type tells TypeScript: + - The seeding function's argument (`prisma`) is of type `PrismaClient`. + - The seeding function's return value is `Promise`. + diff --git a/web/docs/data-model/crud.md b/web/docs/data-model/crud.md index 5740e9fe0..122aaca18 100644 --- a/web/docs/data-model/crud.md +++ b/web/docs/data-model/crud.md @@ -58,7 +58,7 @@ Here's what it looks like when visualized: We can now use the CRUD queries and actions we just specified in our client code. -Keep reading for an example of Automatic CRUD in action, or skip ahead for the [API Reference](#api-reference) +Keep reading for an example of Automatic CRUD in action, or skip ahead for the [API Reference](#api-reference). ## Example: A Simple TODO App @@ -149,6 +149,10 @@ We also overrode the `create` operation with a custom implementation. This means ### Our Custom `create` Operation +We need a custom `create` operation because we want to make sure that the task is connected to the user creating it. +Automatic CRUD doesn't yet support this by default. +Read more about the default implementations [here](#declaring-a-crud-with-default-options). + Here's the `src/tasks.{js,ts}` file: @@ -220,13 +224,17 @@ export const createTask: Tasks.CreateAction = async ( } ``` +Wasp automatically generates the `Tasks.CreateAction` type based on the CRUD declaration in your Wasp file. +Use it to type the CRUD action's implementation. + +The `Tasks.CreateAction` type works exactly like the types Wasp generates for [Queries](../data-model/operations/queries#type-support-for-queries) and [Actions](../data-model/operations/actions#type-support-for-actions). +In other words, annotating the action with `Tasks.CreateAction` tells TypeScript about the type of the Action's `context` object, while the two type arguments allow you to specify the Action's inputs and outputs. + +Read more about type support for CRUD overrides in the [API reference](#defining-the-overrides). + -We made a custom `create` operation because we want to make sure that the task is connected to the user that is creating it. -Automatic CRUD doesn't support this by default (yet!). -Read more about the default implementations [here](#declaring-a-crud-with-default-options). - ### Using the Generated CRUD Operations on the Client And let's use the generated operations in our client code: diff --git a/web/docs/data-model/entities.md b/web/docs/data-model/entities.md index 00693ec8d..77994e92f 100644 --- a/web/docs/data-model/entities.md +++ b/web/docs/data-model/entities.md @@ -2,6 +2,8 @@ title: Entities --- +import { ShowForTs } from '@site/src/components/TsJsHelpers' + Entities are the foundation of your app's data model. In short, an Entity defines a model in your database. Wasp uses the excellent [Prisma ORM](https://www.prisma.io/) to implement all database functionality and occasionally enhances it with a thin abstraction layer. This means that you use the `schema.prisma` file to define your database models and relationships. Wasp understands the Prisma schema file and picks up all the models you define there. You can read more about this in the [Prisma Schema File](./prisma-file.md) section of the docs. @@ -74,6 +76,46 @@ The above Prisma `model` definition tells Wasp to create a table for storing Tas - `description` - A string value for storing the task's description. - `isDone` - A boolean value indicating the task's completion status. If you don't set it when creating a new task, the database sets it to `false` by default. + + +Wasp also exposes a type for working with the created Entity. You can import and use it like this: +```ts +import { Task } from 'wasp/entities' + +const task: Task = { ... } + +// You can also define functions for working with entities +function getInfoMessage(task: Task): string { + const isDoneText = task.isDone ? "is done" : "is not done" + return `Task '${task.description}' is ${isDoneText}.` +} +``` + +Using the `Task` type in `getInfoMessageInfo`'s definition connects the argument's type with the `Task` entity. + +This coupling removes duplication and ensures the function keeps the correct signature even if you change the entity. Of course, the function might throw type errors depending on how you change it, but that's precisely what you want! + +Entity types are available everywhere, including the client code: +```ts +import { Task } from "wasp/entities" + +export function ExamplePage() {} + const task: Task = { + id: 123, + description: "Some random task", + isDone: false, + } + return
{task.description}
+} +``` + +The mentioned type safety mechanisms also apply here: changing the task entity in our `schema.prisma` file changes the imported type, which might throw a type error and warn us that our task definition is outdated. + +You'll learn even more about Entity types when you start using [them with operations](#using-entities-in-operations). + +
+ + ### Working with Entities Let's see how you can define and work with Wasp Entities: diff --git a/web/docs/data-model/operations/_superjson-note.md b/web/docs/data-model/operations/_superjson-note.md index 9890d512c..6ca5a1726 100644 --- a/web/docs/data-model/operations/_superjson-note.md +++ b/web/docs/data-model/operations/_superjson-note.md @@ -1,6 +1,6 @@ import { ShowForTs } from '@site/src/components/TsJsHelpers'; -:::tip +:::info Payload constraints Wasp uses [superjson](https://github.com/blitz-js/superjson) under the hood. This means you're not limited to only sending and receiving JSON payloads. @@ -8,7 +8,7 @@ You can send and receive any superjson-compatible payload (like Dates, Sets, Lis -As long as you're annotating your Queries with the correct automatically generated types, TypeScript ensures your payloads are valid (i.e., Wasp knows how to serialize and deserialize them). +As long as you're annotating your Operations with the correct automatically generated types, TypeScript ensures your payloads are valid (i.e., Wasp knows how to serialize and deserialize them). ::: diff --git a/web/docs/data-model/operations/actions.md b/web/docs/data-model/operations/actions.md index edae89c03..8ec0290f4 100644 --- a/web/docs/data-model/operations/actions.md +++ b/web/docs/data-model/operations/actions.md @@ -79,7 +79,11 @@ If you want to know about all supported options for the `action` declaration, ta The names of Wasp Actions and their implementations don't necessarily have to match. However, to avoid confusion, we'll keep them the same. - +:::info +You might have noticed that we told Wasp to import Action implementations that don't yet exist. Don't worry about that for now. We'll write the implementations imported from `actions.{js,ts}` in the next section. + +It's a good idea to start with the high-level concept (the Action declaration in the Wasp file) and only then deal with the implementation details (the Action's implementation in JavaScript). +::: After declaring a Wasp Action, two important things happen: @@ -182,27 +186,68 @@ export const markTaskAsDone: MarkTaskAsDone, void> = ( } ``` + + +#### Type support for Actions + Wasp automatically generates the types `CreateTask` and `MarkTaskAsDone` based on the declarations in your Wasp file: - `CreateTask` is a generic type that Wasp automatically generated based on the Action declaration for `createTask`. - `MarkTaskAsDone` is a generic type that Wasp automatically generated based on the Action declaration for `markTaskAsDone`. -You can use these types to specify the Action's input and output types. +Use these types to type the Action's implementation. +It's optional but very helpful since doing so properly types the Action's context. -The Action `createTask` expects to get an object of type `{ description: string }` and returns the newly created task (an object of type `Task`). +In this case, TypeScript will know the `context.entities` object must include the `Task` entity. +TypeScript also knows whether the `context` object includes user information (it depends on whether your Action uses auth). -The Action `markTaskAsDone`, expects an object of type `{ id: number }` and doesn't return anything (i.e., its return type is `void`). +The generated types are generic and accept two optional type arguments: `Input` and `Output`. -We've derived most of the payload types from the type `Task`. +1. `Input` - The argument (the payload) received by the Action function. +2. `Output` - The Action function's return type. -Annotating the Actions is optional, but highly recommended. Doing so enables **full-stack type safety**. We'll see what this means when calling the Action from the client. +Use these type arguments to type the Action's inputs and outputs. -:::tip -Wasp uses [superjson](https://github.com/blitz-js/superjson) under the hood. In other words, you don't need to limit yourself to only sending and receiving JSON payloads. +
+Explanation for the example above -Send and receive any superjson-compatible payload (e.g., Dates, Sets, Lists, circular references, etc.) and let Wasp take care of the (de)serialization. +The above code says that the Action `createTask` expects an object with the new task's description (its input type is `Pick`) and returns the new task (its output type is `Task`). + +On the other hand, the Action `markTaskAsDone` expects an object of type `Pick`. This type is derived from the `Task` entity type. + +If you don't care about typing the Action's inputs and outputs, you can omit both type arguments. +TypeScript will then infer the most general types (`never` for the input and `unknown` for the output). + +Specifying `Input` or `Output` is completely optional, but we highly recommended it. Doing so gives you: + - Type support for the arguments and the return value inside the implementation. + - **Full-stack type safety**. We'll explore what this means when we discuss calling the Action from the client. + +
+ +Read more about type support for implementing Actions in the [API Reference](#implementing-actions). + +:::tip Inferring the return type + +If don't want to explicitly type the Action's return value, the `satisfies` keyword tells TypeScript to infer it automatically: +```typescript +const createFoo = (async (_args, context) => { + const foo = await context.entities.Foo.create() + return { + newFoo: foo, + message: "Here's your foo!", + returnedAt: new Date(), + } +}) satisfies GetFoo +``` +From the snippet above, TypeScript knows: +1. The correct type for `context`. +2. The Action's return type is `{ newFoo: Foo, message: string, returnedAt: Date }`. + +If you don't need the context, you can skip specifying the Action's type (and arguments): +```typescript +const createFoo = () => {{ name: 'Foo', date: new Date() }} +``` -As long as you're annotating your Actions with correct automatically generated types, TypeScript ensures your payloads are valid (i.e., that Wasp knows how to serialize and deserialize them). ::: @@ -210,7 +255,7 @@ As long as you're annotating your Actions with correct automatically generated t -For a detailed explanation of the Action definition API (i.e., arguments and return values), check the [API Reference](#api-reference). +For a detailed explanation of the Action definition API (more precisely, its arguments and return values), check the [API Reference](#api-reference). @@ -247,6 +292,9 @@ const newTask = await createTask({ description: 'Keep learning TypeScript' }) await markTasAsDone({ id: 1 }) ``` +Wasp supports **automatic full-stack type safety**. +You only need to specify the Action's type in its server-side definition, and the client code will automatically know its API payload types. + @@ -641,11 +689,11 @@ It expects two (optional) type arguments: 1. `Input` - The type of the `args` object (i.e., the Action's input payload). The default value is `never`. + The type of the `args` object (the Action's input payload). The default value is `never`. 2. `Output` - The type of the Action's return value (i.e., the Action's output payload). The default value is `unknown`. + The type of the Action's return value (the Action's output payload). The default value is `unknown`. The defaults were chosen to make the type signature as permissive as possible. If don't want your Action to take/return anything, use `void` as a type argument. @@ -715,7 +763,7 @@ The `useAction` hook accepts two arguments: - `actionFn` - The Wasp Action (i.e., the client-side Action function generated by Wasp based on a Action declaration) you wish to enhance. + The Wasp Action (the client-side Action function generated by Wasp based on a Action declaration) you wish to enhance. - `actionOptions` @@ -727,7 +775,7 @@ The `useAction` hook accepts two arguments: - `getQuerySpecifier` - A function returning the Query specifier (i.e., a value used to address the Query you want to update). A Query specifier is an array specifying the query function and arguments. For example, to optimistically update the Query used with `useQuery(fetchFilteredTasks, {isDone: true }]`, your `getQuerySpecifier` function would have to return the array `[fetchFilteredTasks, { isDone: true}]`. Wasp will forward the argument you pass into the decorated Action to this function (i.e., you can use the properties of the added/changed item to address the Query). + A function returning the Query specifier (a value used to address the Query you want to update). A Query specifier is an array specifying the query function and arguments. For example, to optimistically update the Query used with `useQuery(fetchFilteredTasks, {isDone: true }]`, your `getQuerySpecifier` function would have to return the array `[fetchFilteredTasks, { isDone: true}]`. Wasp will forward the argument you pass into the decorated Action to this function (you can use the properties of the added/changed item to address the Query). - `updateQuery` @@ -815,6 +863,7 @@ type TaskPayload = Pick; const TaskPage = ({ id }: { id: number }) => { const { data: task } = useQuery(getTask, { id }); + // Typescript automatically type-checks the payload type. // highlight-start const markTaskAsDoneOptimistically = useAction(markTaskAsDone, { optimisticUpdates: [ diff --git a/web/docs/data-model/operations/queries.md b/web/docs/data-model/operations/queries.md index 38299acb7..23b96ba8b 100644 --- a/web/docs/data-model/operations/queries.md +++ b/web/docs/data-model/operations/queries.md @@ -82,7 +82,7 @@ The names of Wasp Queries and their implementations don't need to match, but we' :::info You might have noticed that we told Wasp to import Query implementations that don't yet exist. Don't worry about that for now. We'll write the implementations imported from `queries.{js,ts}` in the next section. -It's a good idea to start with the high-level concept (i.e., the Query declaration in the Wasp file) and only then deal with the implementation details (i.e., the Query's implementation in JavaScript). +It's a good idea to start with the high-level concept (the Query declaration in the Wasp file) and only then deal with the implementation details (the Query's implementation in JavaScript). ::: After declaring a Wasp Query, two important things happen: @@ -162,33 +162,87 @@ export const getFilteredTasks: GetFilteredTasks< } ``` + + +#### Type support for Queries + Wasp automatically generates the types `GetAllTasks` and `GetFilteredTasks` based on your Wasp file's declarations: - `GetAllTasks` is a generic type automatically generated by Wasp, based on the Query declaration for `getAllTasks`. - `GetFilteredTasks` is also a generic type automatically generated by Wasp, based on the Query declaration for `getFilteredTasks`. -You can utilize these types to define the input and output types for your Query. +Use these types to type the Query's implementation. +It's optional but very helpful since doing so properly types the Query's context. -For example, the Query `getAllTasks` doesn't expect any arguments (its input type is `void`), but it does return a list of tasks (its output type is `Task[]`). +In this case, TypeScript will know the `context.entities` object must include the `Task` entity. +TypeScript also knows whether the `context` object includes user information (it depends on whether your Query uses auth). -On the other hand, the Query `getFilteredTasks` expects an object of type `{ isDone: boolean }`. This type is derived from the `Task` type. +The generated types are generic and accept two optional type arguments: `Input` and `Output`. -While annotating the Queries is optional, it's highly recommended. Doing so enables **full-stack type safety**. We'll explore what this means when we discuss calling the Query from the client. +1. `Input` - The argument (the payload) received by the Query function. +2. `Output` - The Query function's return type. - +Use these type arguments to type the Query's inputs and outputs. + +
+Explanation for the example above + +The above code says that the Query `getAllTasks` doesn't expect any arguments (its input type is `void`), but it does return a list of tasks (its output type is `Task[]`). + +On the other hand, the Query `getFilteredTasks` expects an object of type `{ isDone: boolean }`. This type is derived from the `Task` entity type. + +If you don't care about typing the Query's inputs and outputs, you can omit both type arguments. TypeScript will then infer the most general types (`never` for the input and `unknown` for the output). + +Specifying `Input` or `Output` is completely optional, but we highly recommended it. Doing so gives you: + +- Type support for the arguments and the return value inside the implementation. +- **Full-stack type safety**. We'll explore what this means when we discuss calling the Query from the client. + +
+ +Read more about type support for implementing Queries in the [API Reference](#implementing-queries). + +:::tip Inferring the return type + +If don't want to explicitly type the Query's return value, the `satisfies` keyword tells TypeScript to infer it automatically: + +```typescript +const getFoo = (async (_args, context) => { + const foos = await context.entities.Foo.findMany() + return { + foos, + message: 'Here are some foos!', + queriedAt: new Date(), + } +}) satisfies GetFoo +``` + +From the snippet above, TypeScript knows: + +1. The correct type for `context`. +2. The Query's return type is `{ foos: Foo[], message: string, queriedAt: Date }`. + +If you don't need the context, you can skip specifying the Query's type (and arguments): + +```typescript +const getFoo = () => {{ name: 'Foo', date: new Date() }} +``` + +::: -For a detailed explanation of the Query definition API (i.e., arguments and return values), check the [API Reference](#api-reference). +For a detailed explanation of the Query definition API (more precisely, its arguments and return values), check the [API Reference](#api-reference). ### Using Queries #### Using Queries on the client + To call a Query on the client, you can import it from `wasp/client/operations` and call it directly. The usage doesn't change depending on whether the Query is authenticated or not. @@ -218,15 +272,20 @@ const allTasks = await getAllTasks() const doneTasks = await getFilteredTasks({ isDone: true }) ``` +Wasp supports **automatic full-stack type safety**. +You only need to specify the Query's type in its server-side definition, and the client code will automatically know its API payload types. + #### Using Queries on the server + Calling a Query on the server is similar to calling it on the client. Here's what you have to do differently: - - Import Queries from `wasp/server/operations` instead of `wasp/client/operations`. - - Make sure you pass in a context object with the user to authenticated Queries. + +- Import Queries from `wasp/server/operations` instead of `wasp/client/operations`. +- Make sure you pass in a context object with the user to authenticated Queries. @@ -260,8 +319,6 @@ const doneTasks = await getFilteredTasks({ isDone: true }, { user }) - - #### The `useQuery` hook When using Queries on the client, you can make them reactive with the `useQuery` hook. @@ -328,7 +385,7 @@ import { type Task } from 'wasp/entities' import { useQuery, getAllTasks, getFilteredTasks } from 'wasp/client/operations' const MainPage = () => { - // TypeScript will automatically infer and type-check payload types. + // TypeScript automatically infers return values and type-checks payload types. const { data: allTasks, error: error1 } = useQuery(getAllTasks) const { data: doneTasks, error: error2 } = useQuery(getFilteredTasks, { isDone: true, @@ -550,7 +607,7 @@ import { getFoo } from 'wasp/client/operations' import { getFoo } from 'wasp/server/operations' ``` -On the the client, the Query expects +On the the client, the Query expects @@ -610,11 +667,11 @@ It expects two (optional) type arguments: 1. `Input` - The type of the `args` object (i.e., the Query's input payload). The default value is `never`. + The type of the `args` object (the Query's input payload). The default value is `never`. 2. `Output` - The type of the Query's return value (i.e., the Query's output payload). The default value is `unknown`. + The type of the Query's return value (the Query's output payload). The default value is `unknown`. The defaults were chosen to make the type signature as permissive as possible. If don't want your Query to take/return anything, use `void` as a type argument. diff --git a/web/docs/general/typescript.md b/web/docs/general/typescript.md new file mode 100644 index 000000000..5bd58cfb6 --- /dev/null +++ b/web/docs/general/typescript.md @@ -0,0 +1,158 @@ +--- +title: TypeScript Support +--- + +import TypescriptServerNote from '../_TypescriptServerNote.md' + +# TypeScript support + +TypeScript is a programming language that adds static type analysis to JavaScript. +It is a superset of JavaScript, which means all JavaScript code is valid TypeScript code. +It also compiles to JavaScript before running. + +TypeScript's type system helps catch errors at build time (this reduces runtime errors), and provides type-based auto-completion in IDEs. + +Each Wasp feature includes TypeScript documentation. + +If you're starting a new project and want to use TypeScript, you don't need to do anything special. +Just follow the feature docs you are interested in, and they will tell you everything you need to know. +We recommend you start by going through [the tutorial](../tutorial/01-create.md). + +To migrate an existing Wasp project from JavaScript to TypeScript, follow this guide. + +## Migrating your project to TypeScript + +Since Wasp ships with out-of-the-box TypeScript support, migrating your project is as simple as changing file extensions and using the language. +This approach allows you to gradually migrate your project on a file-by-file basis. + +We will first show you how to migrate a single file and then help you generalize the procedure to the rest of your project. + +### Migrating a single file + +Assuming your `schema.prisma` file defines the `Task` entity: + +```prisma title="schema.prisma" +// ... + +model Task { + id Int @id @default(autoincrement()) + description String + isDone Boolean +} +``` + +And your `main.wasp` file defines the `getTaskInfo` query: + +```wasp title=main.wasp +query getTaskInfo { + fn: import { getTaskInfo } from "@src/queries", + entities: [Task] +} +``` + +We will show you how to migrate the following `queries.js` file: + +```javascript title="src/queries.js" +import HttpError from 'wasp/server' + +function getInfoMessage(task) { + const isDoneText = task.isDone ? 'is done' : 'is not done' + return `Task '${task.description}' is ${isDoneText}.` +} + +export const getTaskInfo = async ({ id }, context) => { + const Task = context.entities.Task + const task = await Task.findUnique({ where: { id } }) + if (!task) { + throw new HttpError(404) + } + return getInfoMessage(task) +} +``` + +To migrate this file to TypeScript, all you have to do is: + +1. Change the filename from `queries.js` to `queries.ts`. +2. Write some types (and optionally use some of Wasp's TypeScript features). + + + + +```javascript title="src/queries.js" +import HttpError from '@wasp/core/HttpError.js' + +function getInfoMessage(task) { + const isDoneText = task.isDone ? 'is done' : 'is not done' + return `Task '${task.description}' is ${isDoneText}.` +} + +export const getTaskInfo = async ({ id }, context) => { + const Task = context.entities.Task + const task = await Task.findUnique({ where: { id } }) + if (!task) { + throw new HttpError(404) + } + return getInfoMessage(task) +} +``` + + + + +```typescript title=src/queries.ts +import HttpError from 'wasp/server' +// highlight-next-line +import { type Task } from '@wasp/entities' +// highlight-next-line +import { type GetTaskInfo } from '@wasp/server/operations' + +// highlight-next-line +function getInfoMessage(task: Pick): string { + const isDoneText = task.isDone ? 'is done' : 'is not done' + return `Task '${task.description}' is ${isDoneText}.` +} + +// highlight-next-line +export const getTaskInfo: GetTaskInfo, string> = async ( + { id }, + context +) => { + const Task = context.entities.Task + + const task = await Task.findUnique({ where: { id } }) + if (!task) { + throw new HttpError(404) + } + + return getInfoMessage(task) +} +``` + + + + +Your code is now processed by TypeScript and uses several of Wasp's TypeScript-specific features: + +- `Task` - A type that represents the `Task` entity. Using this type connects your data to the model definitions in the `schema.prisma` file. Read more about this feature [here](../data-model/entities). +- `GetTaskInfo<...>` - A generic type Wasp automatically generates to give you type + support when implementing the Query. Thanks to this type, the compiler knows: + + - The type of the `context` object. + - The type of `args`. + - The Query's return type. + + And gives you Intellisense and type-checking. Read more about this feature [here](../data-model/operations/queries#implementing-queries). + +You don't need to change anything inside the `.wasp` file. + +### Migrating the rest of the project + +You can migrate your project gradually - on a file-by-file basis. + +When you want to migrate a file, follow the procedure outlined above: + +1. Change the file's extension. +2. Fix the type errors. +3. Read the Wasp docs and decide which TypeScript features you want to use. + + diff --git a/web/docs/introduction/editor-setup.md b/web/docs/introduction/editor-setup.md index 173b14385..c448a693f 100644 --- a/web/docs/introduction/editor-setup.md +++ b/web/docs/introduction/editor-setup.md @@ -2,6 +2,7 @@ title: Editor Setup slug: /editor-setup --- +import TypescriptServerNote from '../_TypescriptServerNote.md' :::note This page assumes you have already installed Wasp. If you do not have Wasp installed yet, check out the [Quick Start](./quick-start.md) guide. @@ -22,3 +23,5 @@ The extension enables: - go to definition and more! + + diff --git a/web/docs/tutorial/01-create.md b/web/docs/tutorial/01-create.md index 46f1ba5f9..ffc0563a2 100644 --- a/web/docs/tutorial/01-create.md +++ b/web/docs/tutorial/01-create.md @@ -36,9 +36,7 @@ $ cd TodoApp $ wasp start ``` -:::note `wasp start` will take a bit of time to start the server the first time you run it in a new project. -::: You will see log messages from the client, server, and database setting themselves up. When everything is ready, a new tab should open in your browser at `http://localhost:3000` with a simple placeholder page: @@ -50,4 +48,37 @@ style={{ border: "1px solid black" }}

-Wasp has generated for you the full front-end and back-end code the app! Next, we'll take a closer look at how the project is structured. +Wasp has generated for you the full front-end and back-end code of the app! Next, we'll take a closer look at how the project is structured. + +## A note on supported languages + +Wasp supports both JavaScript and TypeScript out of the box, but you are free to choose between or mix JavaScript and TypeScript as you see fit. + +We'll provide you with both JavaScript and TypeScript code in this tutorial. +Code blocks will have a toggle to switch between vanilla JavaScript and TypeScript. + +Try it out: + + + + + +:::note Welcome to JavaScript! + +You are now reading the JavaScript version of the docs. The site will remember your preference as you switch pages. + +You'll have a chance to change the language on every code snippet - both the snippets and the text will update accordingly. +::: + + + + +:::note Welcome to TypeScript! + +You are now reading the TypeScript version of the docs. The site will remember your preference as you switch pages. + +You'll have a chance to change the language on every code snippet - both the snippets and the text will update accordingly. +::: + + + diff --git a/web/docs/tutorial/02-project-structure.md b/web/docs/tutorial/02-project-structure.md index 17379f731..6e8e4bc7a 100644 --- a/web/docs/tutorial/02-project-structure.md +++ b/web/docs/tutorial/02-project-structure.md @@ -3,7 +3,9 @@ title: 2. Project Structure --- import useBaseUrl from '@docusaurus/useBaseUrl'; -import { ShowForTs } from '@site/src/components/TsJsHelpers'; + + + After creating a new Wasp project, you'll get a file structure that looks like this: @@ -26,14 +28,44 @@ After creating a new Wasp project, you'll get a file structure that looks like t ``` + + + +The default project uses JavaScript. To use TypeScript, you must manually rename the file +`src/MainPage.jsx` to `src/MainPage.tsx`. + +No updates to the `main.wasp` file are necessary - it stays the same regardless of the language you use. + +After creating a new Wasp project and renaming the `src/MainPage.jsx` file, your project should look like this: + +```css +. +├── .gitignore +├── main.wasp # Your Wasp code goes here. +├── schema.prisma # Your Prisma schema goes here. +├── package.json # Your dependencies and project info go here. +├── public # Your static files (e.g., images, favicon) go here. +├── src # Your source code (TS/JS/CSS/HTML) goes here. +│   ├── Main.css +// highlight-next-line +│   ├── MainPage.tsx # Renamed from MainPage.jsx +│   ├── vite-env.d.ts +│   └── waspLogo.png +├── tsconfig.json +├── vite.config.ts +├── .waspignore +└── .wasproot + +``` + + + + By _your code_, we mean the _"the code you write"_, as opposed to the code generated by Wasp. Wasp allows you to organize and structure your code however you think is best - there's no need to separate client files and server files into different directories. -:::note We'd normally recommend organizing code by features (i.e., vertically). - However, since this tutorial contains only a handful of files, there's no need for fancy organization. We'll keep it simple by placing everything in the root `src` directory. -::: Many other files (e.g., `tsconfig.json`, `vite-env.d.ts`, `.wasproot`, etc.) help Wasp and the IDE improve your development experience with autocompletion, IntelliSense, and error reporting. @@ -42,12 +74,6 @@ We won't be configuring Vite in this tutorial, so you can safely ignore the file The `schema.prisma` file is where you define your database schema using [Prisma](https://www.prisma.io/). We'll cover this a bit later in the tutorial. -:::note TypeScript Support -Wasp supports TypeScript out of the box, but you are free to choose between or mix JavaScript and TypeScript as you see fit. - -We'll provide you with both JavaScript and TypeScript code in this tutorial. Code blocks will have a toggle to switch between vanilla JavaScript and TypeScript. -::: - The most important file in the project is `main.wasp`. Wasp uses the configuration within it to perform its magic. Based on what you write, it generates a bunch of code for your database, server-client communication, React routing, and more. Let's take a closer look at `main.wasp` @@ -104,17 +130,6 @@ page MainPage {
- - -:::caution Using TypeScript -The default project uses JavaScript. To use TypeScript, you must rename the file -`src/MainPage.jsx` to `src/MainPage.tsx`. - -No updates to the `main.wasp` file are necessary - it stays the same regardless of the language you use. -::: - - - This file uses three declaration types: - **app**: Top-level configuration information about your app. diff --git a/web/docs/tutorial/03-pages.md b/web/docs/tutorial/03-pages.md index 8fb2377e5..bfd25672a 100644 --- a/web/docs/tutorial/03-pages.md +++ b/web/docs/tutorial/03-pages.md @@ -4,6 +4,8 @@ title: 3. Pages & Routes import useBaseUrl from '@docusaurus/useBaseUrl'; import { ShowForTs } from '@site/src/components/TsJsHelpers'; +import WaspStartNote from '../_WaspStartNote.md' +import TypescriptServerNote from '../_TypescriptServerNote.md' In the default `main.wasp` file created by `wasp new`, there is a **page** and a **route** declaration: @@ -73,9 +75,11 @@ This is a regular functional React component. It also uses the CSS file and a lo That is all the code you need! Wasp takes care of everything else necessary to define, build, and run the web app. -:::tip -`wasp start` automatically picks up the changes you make and restarts the app, so keep it running in the background. -::: + + + + + ## Adding a Second Page @@ -221,7 +225,6 @@ page MainPage { - Excellent work! You now have a basic understanding of Wasp and are ready to start building your TodoApp. diff --git a/web/sidebars.js b/web/sidebars.js index 4c6ee3853..48dd6ea98 100644 --- a/web/sidebars.js +++ b/web/sidebars.js @@ -135,7 +135,7 @@ module.exports = { label: 'General', collapsed: false, collapsible: true, - items: ['general/language', 'general/cli'], + items: ['general/language', 'general/cli', 'general/typescript'], }, { type: 'category',