toiljs 0.0.85 → 0.0.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +303 -293
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +2 -0
- package/build/compiler/config.js +1 -0
- package/build/compiler/docs.js +8 -24
- package/build/compiler/generate.js +4 -2
- package/build/compiler/index.d.ts +1 -1
- package/build/compiler/index.js +69 -6
- package/build/compiler/toil-docs.generated.js +64 -22
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/analytics/index.js +7 -3
- package/build/devserver/db/database.d.ts +4 -0
- package/build/devserver/db/database.js +43 -1
- package/build/devserver/db/index.d.ts +1 -1
- package/build/devserver/db/index.js +1 -1
- package/build/devserver/db/types.d.ts +3 -0
- package/build/devserver/db/types.js +2 -0
- package/build/devserver/runtime/module.js +4 -1
- package/docs/README.md +104 -65
- package/docs/auth/README.md +102 -0
- package/docs/auth/configuration.md +94 -0
- package/docs/auth/extending.md +202 -0
- package/docs/auth/how-it-works.md +138 -0
- package/docs/auth/usage.md +188 -0
- package/docs/backend/README.md +143 -0
- package/docs/backend/data.md +351 -0
- package/docs/backend/rest.md +402 -0
- package/docs/backend/rpc.md +226 -0
- package/docs/background/README.md +114 -0
- package/docs/background/daemons.md +230 -0
- package/docs/background/derive.md +179 -0
- package/docs/cli/README.md +377 -0
- package/docs/concepts/config.md +416 -0
- package/docs/concepts/decorators.md +127 -0
- package/docs/concepts/security.md +108 -0
- package/docs/concepts/tiers.md +166 -0
- package/docs/concepts/types.md +216 -0
- package/docs/database/README.md +143 -0
- package/docs/database/capacity.md +350 -0
- package/docs/database/counters.md +174 -0
- package/docs/database/documents.md +255 -0
- package/docs/database/events.md +307 -0
- package/docs/database/membership.md +246 -0
- package/docs/database/setup.md +155 -0
- package/docs/database/unique.md +216 -0
- package/docs/database/views.md +246 -0
- package/docs/frontend/README.md +101 -0
- package/docs/frontend/data-fetching.md +243 -0
- package/docs/frontend/images.md +148 -0
- package/docs/frontend/metadata.md +344 -0
- package/docs/frontend/rendering.md +236 -0
- package/docs/frontend/routing.md +344 -0
- package/docs/frontend/scripts.md +118 -0
- package/docs/frontend/search.md +191 -0
- package/docs/frontend/styling.md +147 -0
- package/docs/getting-started/README.md +81 -0
- package/docs/getting-started/create-project.md +131 -0
- package/docs/getting-started/deploy.md +101 -0
- package/docs/getting-started/first-app.md +215 -0
- package/docs/getting-started/installation.md +106 -0
- package/docs/getting-started/migrating.md +125 -0
- package/docs/getting-started/project-structure.md +163 -0
- package/docs/introduction/README.md +41 -0
- package/docs/introduction/design-principles.md +55 -0
- package/docs/introduction/distributed.md +74 -0
- package/docs/introduction/how-it-works.md +74 -0
- package/docs/introduction/hyperscale.md +51 -0
- package/docs/introduction/modern-stack.md +36 -0
- package/docs/introduction/vs-other-frameworks.md +48 -0
- package/docs/introduction/why-toil.md +93 -0
- package/docs/llms.txt +90 -0
- package/docs/realtime/README.md +102 -0
- package/docs/realtime/channels.md +211 -0
- package/docs/realtime/streams.md +369 -0
- package/docs/services/README.md +60 -0
- package/docs/services/analytics.md +268 -0
- package/docs/services/caching.md +175 -0
- package/docs/services/cookies.md +235 -0
- package/docs/services/crypto.md +209 -0
- package/docs/services/email.md +289 -0
- package/docs/services/environment.md +141 -0
- package/docs/services/ratelimit.md +174 -0
- package/docs/services/time.md +85 -0
- package/examples/basic/client/routes/analytics.tsx +13 -12
- package/examples/basic/server/models/SiteAnalytics.ts +29 -17
- package/examples/basic/server/routes/Analytics.ts +15 -17
- package/examples/basic/server/routes/UserId.ts +22 -0
- package/package.json +15 -11
- package/scripts/gen-toil-docs.mjs +24 -35
- package/server/auth/AuthController.ts +336 -0
- package/server/auth/AuthUser.ts +23 -0
- package/server/auth/index.ts +16 -0
- package/server/globals/auth.ts +31 -0
- package/server/globals/userid.ts +107 -0
- package/src/compiler/config.ts +13 -0
- package/src/compiler/docs.ts +16 -33
- package/src/compiler/generate.ts +6 -2
- package/src/compiler/index.ts +114 -6
- package/src/compiler/toil-docs.generated.ts +64 -22
- package/src/devserver/analytics/index.ts +10 -4
- package/src/devserver/db/database.ts +67 -1
- package/src/devserver/db/index.ts +1 -0
- package/src/devserver/db/types.ts +13 -0
- package/src/devserver/runtime/module.ts +7 -0
- package/test/analytics-dev.test.ts +2 -1
- package/test/devserver-database.test.ts +113 -0
- package/docs/auth-todo.md +0 -149
- package/docs/auth.md +0 -322
- package/docs/caching.md +0 -115
- package/docs/cli.md +0 -17
- package/docs/client.md +0 -39
- package/docs/cookies.md +0 -457
- package/docs/crypto.md +0 -130
- package/docs/daemon.md +0 -123
- package/docs/data.md +0 -131
- package/docs/derive.md +0 -159
- package/docs/email.md +0 -326
- package/docs/environment.md +0 -97
- package/docs/getting-started.md +0 -128
- package/docs/index.md +0 -30
- package/docs/ratelimit.md +0 -95
- package/docs/routing.md +0 -259
- package/docs/rpc.md +0 -149
- package/docs/server.md +0 -61
- package/docs/ssr.md +0 -632
- package/docs/streams.md +0 -178
- package/docs/styling.md +0 -22
- package/docs/tiers.md +0 -133
- package/docs/time.md +0 -43
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Deploying a toiljs app
|
|
2
|
+
|
|
3
|
+
This page is an honest look at how you get a toiljs app in front of real users. There are two paths, and it helps to be clear about which one exists today.
|
|
4
|
+
|
|
5
|
+
- **Self-host it yourself.** You build the app and run it on a machine you control, with `toiljs build` then `toiljs start`. This works right now, on your laptop, a VPS, or any server that can run Node.js.
|
|
6
|
+
- **Run it on the managed Dacely edge.** This is the platform toiljs is built for: a worldwide fleet of servers that runs your compiled backend close to every user. It is the target the whole framework is designed around.
|
|
7
|
+
|
|
8
|
+
One thing up front, so you do not go looking for it: **there is no `toiljs deploy` command.** The CLI can build and self-host; pushing a build onto the managed edge is a platform step, not a CLI subcommand. The rest of this page covers what you can do today (self-hosting) and what the managed edge gives you.
|
|
9
|
+
|
|
10
|
+
## The one build powers both
|
|
11
|
+
|
|
12
|
+
Both paths serve the exact same artifacts. `toiljs build` produces them once:
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart LR
|
|
16
|
+
A["Your project"] -->|toiljs build| B["build/client/<br/>(HTML, JS, CSS, assets)"]
|
|
17
|
+
A -->|toiljs build| C["build/server/release.wasm<br/>(your backend)"]
|
|
18
|
+
B --> H["toiljs start<br/>(self-host)"]
|
|
19
|
+
C --> H
|
|
20
|
+
B --> E["managed Dacely edge<br/>(worldwide)"]
|
|
21
|
+
C --> E
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
So you never build differently for the two targets. You build once, then either run it yourself or hand the same output to the edge.
|
|
25
|
+
|
|
26
|
+
## Self-hosting with `toiljs start`
|
|
27
|
+
|
|
28
|
+
`toiljs start` runs your built app on a fast production HTTP server. It serves your static client, dispatches dynamic requests into your `release.wasm`, does server-side rendering, runs daemons, and exposes a `/_toil` websocket channel. Use it to put your app on your own machine or server instead of the managed edge.
|
|
29
|
+
|
|
30
|
+
### Step 1: build
|
|
31
|
+
|
|
32
|
+
`start` serves whatever is in `build/`, so you must build first. If there is no build, `start` exits with an error (it looks for `build/client/index.html`).
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm run build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Step 2: start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Serve on http://127.0.0.1:3000 (loopback only, one worker per CPU).
|
|
42
|
+
npx toiljs start
|
|
43
|
+
|
|
44
|
+
# Accept outside connections, on port 8080, with 4 worker processes.
|
|
45
|
+
npx toiljs start --host 0.0.0.0 --port 8080 --threads 4
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### start flags
|
|
49
|
+
|
|
50
|
+
| Flag | Meaning |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `--port <n>` | Port to listen on. Default `3000` (or `client.port` from your config). |
|
|
53
|
+
| `--host <host>` | Address to bind. Default `127.0.0.1`, which is **loopback only** (reachable just from the same machine). Pass `0.0.0.0` to accept connections from other machines. |
|
|
54
|
+
| `--threads <n>` | Number of HTTP worker processes. Default is automatic (one per available CPU). Pass `1` to disable the worker pool. `--workers` is an accepted alias, and you can also set `server.threads` in your config or the `TOILJS_THREADS` environment variable. |
|
|
55
|
+
| `--root <dir>` | Run against a project in another directory instead of the current one. |
|
|
56
|
+
|
|
57
|
+
These flags are verified against the CLI. Note that `--host` and `--threads` are `start`-only: `toiljs dev` always binds locally and does not take them. For the full command reference, see [the CLI](../cli/README.md#toiljs-start).
|
|
58
|
+
|
|
59
|
+
### A realistic self-host
|
|
60
|
+
|
|
61
|
+
On a small server (say a VPS), a minimal run looks like this:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Install and build.
|
|
65
|
+
npm ci
|
|
66
|
+
npm run build
|
|
67
|
+
|
|
68
|
+
# Bind on all interfaces so a reverse proxy or the public can reach it.
|
|
69
|
+
npx toiljs start --host 0.0.0.0 --port 8080 --threads 4
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then put a reverse proxy (nginx, Caddy) in front for TLS and a real hostname, pointing it at `http://127.0.0.1:8080`. Keep the process alive with your usual tool (systemd, pm2, a container restart policy).
|
|
73
|
+
|
|
74
|
+
### Self-host gotchas
|
|
75
|
+
|
|
76
|
+
- **`start` needs a fresh build.** It serves `build/`, not your source. After any code change, run `toiljs build` again before restarting.
|
|
77
|
+
- **The default bind is loopback.** With no `--host`, only the same machine can reach it. Set `--host 0.0.0.0` (usually behind a reverse proxy) to serve real traffic.
|
|
78
|
+
- **Self-host is one place, not the whole world.** You get one server (or however many you run yourself). The latency and worldwide reach of the managed edge is exactly the thing self-hosting does not give you.
|
|
79
|
+
- **The database is different.** Self-hosting runs the built server, but ToilDB's worldwide data layer is an edge feature. Treat self-host as a way to run and test your build, not as a substitute for the managed edge's global database.
|
|
80
|
+
|
|
81
|
+
## The managed Dacely edge
|
|
82
|
+
|
|
83
|
+
The managed **Dacely edge** is the platform toiljs targets: a fleet of servers in many cities that runs your compiled backend as close to each user as possible, backed by the worldwide **ToilDB** database. You write one project, and the build already decides which part of your code belongs to which layer of the edge.
|
|
84
|
+
|
|
85
|
+
Your backend runs across four **compute tiers**, from a per-request handler at the very edge up to a single worldwide coordinator:
|
|
86
|
+
|
|
87
|
+
| Tier | Name | Where it runs | Typical code |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| **L1** | Hot / edge | The node nearest the user, fresh per request | `@rest`, `@service` / `@remote` |
|
|
90
|
+
| **L2** | Regional | One box per connection, per region | `@stream` (Regional) |
|
|
91
|
+
| **L3** | Continental | One box per connection, per continent | `@stream` (Continental) |
|
|
92
|
+
| **L4** | Global / daemon | Exactly one leader worldwide | `@daemon`, `@scheduled` |
|
|
93
|
+
|
|
94
|
+
Because the same `toiljs build` output feeds the edge, everything you built and tested with `toiljs dev` and `toiljs start` is what runs there. The edge decides each server's role for you; you do not configure tiers by hand. For the full picture of what each tier means and how to write for it, see [Compute tiers (L1 to L4)](../concepts/tiers.md).
|
|
95
|
+
|
|
96
|
+
## Related
|
|
97
|
+
|
|
98
|
+
- [The CLI](../cli/README.md): the full reference for `build` and `start` and every flag.
|
|
99
|
+
- [Configuration (`toil.config.ts`)](../concepts/config.md): `server.threads` and the other self-host knobs.
|
|
100
|
+
- [Compute tiers (L1 to L4)](../concepts/tiers.md): how your code maps onto the managed edge.
|
|
101
|
+
- [Getting started](./README.md): the path from install to a running feature.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Your first app
|
|
2
|
+
|
|
3
|
+
Build a tiny feature end to end: a page with a "Like" button that reads and writes a real number in ToilDB, so the count survives page reloads and restarts. Along the way you meet the typed client, an HTTP route, and the database, the three pieces you use in almost every toiljs app.
|
|
4
|
+
|
|
5
|
+
## What you will build
|
|
6
|
+
|
|
7
|
+
A `/likes` page that shows a running like count and a button to add one. The count lives in ToilDB, so it is shared by everyone and it persists.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
sequenceDiagram
|
|
11
|
+
participant B as Browser (/likes page)
|
|
12
|
+
participant SH as shared/server.ts (typed client)
|
|
13
|
+
participant W as server.wasm (@rest route)
|
|
14
|
+
participant DB as ToilDB (counter)
|
|
15
|
+
B->>SH: Server.REST.likes.add()
|
|
16
|
+
SH->>W: POST /likes
|
|
17
|
+
W->>DB: counter.add(key, 1)
|
|
18
|
+
W-->>SH: { count: 43 }
|
|
19
|
+
SH-->>B: typed LikeCount
|
|
20
|
+
B->>B: show "43 likes"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Before you start
|
|
24
|
+
|
|
25
|
+
You need a project. If you do not have one yet, create one and start the dev server:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
toiljs create my-app
|
|
29
|
+
cd my-app
|
|
30
|
+
npm run dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Leave `npm run dev` running in a terminal. It rebuilds your server, regenerates the typed client, and hot-reloads the browser every time you save. The app is at `http://localhost:3000`.
|
|
34
|
+
|
|
35
|
+
## Why the database (and not a variable)
|
|
36
|
+
|
|
37
|
+
Your first instinct might be to keep the count in a normal variable on the server. That will not work, and it is worth understanding why now.
|
|
38
|
+
|
|
39
|
+
**The server runs a fresh copy of your `.wasm` for every request, and wipes its memory when the request ends.** So a variable you set while handling one request is gone by the next request. Anything that must outlive a single request, a counter, a user, a post, has to go into a store. toiljs ships one: **ToilDB**, a database built into the edge.
|
|
40
|
+
|
|
41
|
+
ToilDB offers a few specialized shapes called **families**. For a running total, the right one is a **counter**: a value you can atomically add to and read back. (Others include documents, events, and views. See [Database overview](../database/README.md).)
|
|
42
|
+
|
|
43
|
+
## Step 1: add a data type for the response
|
|
44
|
+
|
|
45
|
+
Your server and client talk in typed messages called `@data` classes. A `@data` class can cross the wire and be parsed into a real typed object on the other side. Create one to hold the count.
|
|
46
|
+
|
|
47
|
+
Create `server/models/LikeCount.ts`:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// The response our route returns: just the current like count.
|
|
51
|
+
@data
|
|
52
|
+
export class LikeCount {
|
|
53
|
+
count: i64 = 0;
|
|
54
|
+
constructor(count: i64 = 0) {
|
|
55
|
+
this.count = count;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Two things to notice:
|
|
61
|
+
|
|
62
|
+
- `@data` is a decorator that marks this class as a wire type. You use it with no import (it is a compiler built-in). See [Data types](../backend/data.md).
|
|
63
|
+
- The field type is `i64`, a 64-bit integer. The server uses precise integer types like `i64` and `u64`. On the client side these arrive as JavaScript `bigint`. More on this in [Types](../concepts/types.md).
|
|
64
|
+
|
|
65
|
+
## Step 2: add the HTTP route
|
|
66
|
+
|
|
67
|
+
Now the backend endpoint. Create `server/routes/Likes.ts`:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { RouteContext } from 'toiljs/server/runtime';
|
|
71
|
+
|
|
72
|
+
import { LikeCount } from '../models/LikeCount';
|
|
73
|
+
|
|
74
|
+
// The KEY that names one counter. A counter is a map from a key to a number,
|
|
75
|
+
// so a single fixed key ("home") gives us one global tally.
|
|
76
|
+
@data
|
|
77
|
+
class LikeKey {
|
|
78
|
+
page: string = 'home';
|
|
79
|
+
constructor(page: string = 'home') {
|
|
80
|
+
this.page = page;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// A @database declares your ToilDB collections. Each @collection is one named
|
|
85
|
+
// store. Here we declare a single counter keyed by LikeKey.
|
|
86
|
+
@database
|
|
87
|
+
class LikesDb {
|
|
88
|
+
@collection static likes: Counter<LikeKey>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A @rest controller exposes HTTP endpoints. 'likes' is the URL prefix, so the
|
|
92
|
+
// routes below live under /likes.
|
|
93
|
+
@rest('likes')
|
|
94
|
+
class Likes {
|
|
95
|
+
// GET /likes -> read the current count.
|
|
96
|
+
// A @get handler is a "query": it may read but not write. Reading one
|
|
97
|
+
// counter by its key is a point read, which queries are allowed to do.
|
|
98
|
+
@get('/')
|
|
99
|
+
public show(): LikeCount {
|
|
100
|
+
const key = new LikeKey('home');
|
|
101
|
+
return new LikeCount(LikesDb.likes.get(key));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// POST /likes -> add one, then return the new count.
|
|
105
|
+
// A @post handler is an "action": it may write. `add` bumps the counter.
|
|
106
|
+
// (A body-less POST takes the RouteContext; we do not need it here.)
|
|
107
|
+
@post('/')
|
|
108
|
+
public add(_ctx: RouteContext): LikeCount {
|
|
109
|
+
const key = new LikeKey('home');
|
|
110
|
+
LikesDb.likes.add(key, 1);
|
|
111
|
+
return new LikeCount(LikesDb.likes.get(key));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
What each decorator does:
|
|
117
|
+
|
|
118
|
+
- **`@rest('likes')`** turns the class into an HTTP controller mounted at `/likes`.
|
|
119
|
+
- **`@get('/')`** and **`@post('/')`** map a method to a verb and path. A `@get` is a read-only **query**; a `@post` is a write-capable **action**. This split is how toiljs keeps expensive or unsafe operations out of read paths.
|
|
120
|
+
- **`@database`** and **`@collection`** declare your ToilDB stores. `Counter<LikeKey>` is a counter you look up by a `LikeKey`.
|
|
121
|
+
|
|
122
|
+
The counter gives you two operations: `add(key, delta)` to change it and `get(key)` to read it. See [Counters](../database/counters.md) and [HTTP routes](../backend/rest.md) for the full API.
|
|
123
|
+
|
|
124
|
+
## Step 3: register the route
|
|
125
|
+
|
|
126
|
+
Open `server/main.ts` and add an import for your new route, next to the others:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import './routes/Likes';
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The build actually discovers decorated files under `server/` on its own, but importing them from `main.ts` is the convention: it keeps a direct `toilscript` build finding the same code. (Every `app` project already imports its demo routes this way.)
|
|
133
|
+
|
|
134
|
+
Save. Watch the terminal: `toiljs dev` recompiles the server to wasm and regenerates `shared/server.ts`. A moment later your new endpoint exists, fully typed.
|
|
135
|
+
|
|
136
|
+
## Step 4: add the client page
|
|
137
|
+
|
|
138
|
+
Now the frontend. Create `client/routes/likes.tsx`. The file name is the URL, so this page is at `/likes`.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import { useEffect, useState } from 'react';
|
|
142
|
+
|
|
143
|
+
export default function LikesPage() {
|
|
144
|
+
// The count is a bigint because the server field is i64.
|
|
145
|
+
const [count, setCount] = useState(0n);
|
|
146
|
+
|
|
147
|
+
// Read the current count once, when the page first loads.
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
Server.REST.likes.show().then((res) => setCount(res.count));
|
|
150
|
+
}, []);
|
|
151
|
+
|
|
152
|
+
// Add a like, then update the UI with the fresh count the server returns.
|
|
153
|
+
const like = async () => {
|
|
154
|
+
const res = await Server.REST.likes.add();
|
|
155
|
+
setCount(res.count);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<main>
|
|
160
|
+
<h1>{String(count)} likes</h1>
|
|
161
|
+
<button onClick={like}>Like</button>
|
|
162
|
+
</main>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The magic here is `Server.REST.likes`. You never wrote it. toiljs generated it into `shared/server.ts` from your `@rest` controller, so:
|
|
168
|
+
|
|
169
|
+
- `Server.REST.likes.show()` returns a `Promise<LikeCount>`, and `res.count` is typed for you.
|
|
170
|
+
- `Server.REST.likes.add()` sends the POST and returns the updated `LikeCount`.
|
|
171
|
+
- `Server` is a global in client code, so you do not import it. Its types come from the generated file.
|
|
172
|
+
|
|
173
|
+
If you rename the route or change its return type on the server, this client code stops type-checking until you fix it. That is the whole point: the browser and the backend cannot silently disagree.
|
|
174
|
+
|
|
175
|
+
## Step 5: try it
|
|
176
|
+
|
|
177
|
+
Open `http://localhost:3000/likes`. You should see "0 likes" and a button. Click **Like** a few times and the number climbs.
|
|
178
|
+
|
|
179
|
+
Now the important test: **reload the page.** The count is still there. Stop the dev server, start it again, reload: still there. That is ToilDB persisting your counter, exactly as it would on the real edge. The same code that ran against the local dev database runs against the worldwide database in production, with no connection string to configure.
|
|
180
|
+
|
|
181
|
+
## What just happened
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
flowchart LR
|
|
185
|
+
M["server/models/LikeCount.ts<br/>@data"] --> GEN
|
|
186
|
+
R["server/routes/Likes.ts<br/>@rest + @database"] --> GEN
|
|
187
|
+
GEN["toiljs dev<br/>compiles + generates"] --> WASM["release.wasm"]
|
|
188
|
+
GEN --> SS["shared/server.ts<br/>(typed Server.REST.likes)"]
|
|
189
|
+
SS --> P["client/routes/likes.tsx<br/>calls Server.REST.likes"]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
You wrote three small files. toiljs compiled the server to WebAssembly, generated a typed client from your route, and your React page called it with full type safety. The like count lives in ToilDB, so it persists.
|
|
193
|
+
|
|
194
|
+
## Gotchas and notes
|
|
195
|
+
|
|
196
|
+
- **`res.count` is a `bigint`, not a `number`.** That is why the page uses `useState(0n)` and `String(count)`. Server integer types map to `bigint` on the client. See [Types](../concepts/types.md).
|
|
197
|
+
- **A `@get` cannot write, and a `@post` can.** If you try to call `.add(...)` from a `@get`, the compiler rejects it. Reads that scan many rows are also blocked in handlers; a single-key `get` is fine.
|
|
198
|
+
- **You do not edit `shared/server.ts`.** It is regenerated on every server build. Change the route, and the client updates itself.
|
|
199
|
+
- **If the editor does not know `Server.REST.likes` yet**, it is because the server has not rebuilt since you added the route. Save a server file (or restart `toiljs dev`) to regenerate `shared/server.ts`.
|
|
200
|
+
- **The counter is global.** Everyone hitting `/likes` shares the same "home" tally, because we used one fixed key. Use a different key per page or per user to split the count.
|
|
201
|
+
|
|
202
|
+
## Where to go next
|
|
203
|
+
|
|
204
|
+
- Return richer objects and lists: [Data types](../backend/data.md) and [HTTP routes](../backend/rest.md).
|
|
205
|
+
- Call the server without URLs, as plain function calls: [Typed RPC](../backend/rpc.md).
|
|
206
|
+
- Store more than a number: [Documents](../database/documents.md), [Events](../database/events.md), and [Views](../database/views.md).
|
|
207
|
+
- Add login and sessions: [Auth](../auth/README.md).
|
|
208
|
+
- Style your pages: [Styling](../frontend/styling.md) and [Routing](../frontend/routing.md).
|
|
209
|
+
|
|
210
|
+
## Related
|
|
211
|
+
|
|
212
|
+
- [Project structure](./project-structure.md)
|
|
213
|
+
- [Database overview](../database/README.md)
|
|
214
|
+
- [Counters](../database/counters.md)
|
|
215
|
+
- [Backend overview](../backend/README.md)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
Install the toiljs command-line tool (CLI) so you can create and run projects. This takes a couple of minutes.
|
|
4
|
+
|
|
5
|
+
## Why and when
|
|
6
|
+
|
|
7
|
+
You need the toiljs CLI once, before you create your first project. The CLI is the single tool you use to scaffold, run, build, and check toiljs apps. After a project exists, the same CLI is also installed inside that project, so day-to-day you can run it through your package scripts (`npm run dev`) without a global install.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
You need **Node.js version 24.0.0 or newer**. Node.js is the JavaScript runtime that powers the toiljs CLI, the dev server, and the build. (Your backend does not run on Node.js, but the tools that build it do.)
|
|
12
|
+
|
|
13
|
+
Check your version:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
node --version
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If it prints `v24.0.0` or higher, you are set. If it is older or the command is not found, install a current Node.js from [nodejs.org](https://nodejs.org), or use a version manager like [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm):
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
# with nvm
|
|
23
|
+
nvm install 24
|
|
24
|
+
nvm use 24
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You also need a package manager. **npm** comes with Node.js, so you already have it. toiljs also supports **pnpm**, **yarn**, and **bun** if you prefer one of those.
|
|
28
|
+
|
|
29
|
+
## Install the CLI
|
|
30
|
+
|
|
31
|
+
The package is called `toiljs`, and it provides a command named `toiljs`. You have two ways to use it.
|
|
32
|
+
|
|
33
|
+
### Option A: run it on demand with npx (no install)
|
|
34
|
+
|
|
35
|
+
`npx` comes with npm and runs a package without installing it globally. This is the quickest way to create your first project:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npx toiljs create my-app
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Option B: install it globally
|
|
42
|
+
|
|
43
|
+
If you plan to create projects often, install it once so `toiljs` is always on your path:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm install -g toiljs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then you can run `toiljs` directly:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
toiljs create my-app
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Both options end up at the same place. The rest of these docs write `toiljs <command>`; if you did not install globally, just put `npx` in front (`npx toiljs <command>`).
|
|
56
|
+
|
|
57
|
+
## Verify it works
|
|
58
|
+
|
|
59
|
+
Check the version:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
toiljs --version
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
You should see a version number printed (for example, `0.0.86`).
|
|
66
|
+
|
|
67
|
+
To see every command and flag, run help:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
toiljs --help
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Inside a project, there is a deeper health check called **doctor**. It inspects your setup and dependencies and tells you exactly what to fix. You will use it after you create a project, but it is good to know it exists:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
toiljs doctor
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`toiljs doctor` reads your project (its `package.json`, config, routes, and build output), runs a series of checks, and prints a grouped report. One of the first checks is your Node.js version against the required `>=24.0.0`, so if your Node is too old, doctor will say so. Add `--fix` to let it repair the things it can (such as the typed-RPC wiring), or `--json` for machine-readable output in a CI pipeline.
|
|
80
|
+
|
|
81
|
+
## How the tooling fits together
|
|
82
|
+
|
|
83
|
+
The `toiljs` CLI is a friendly front end over two underlying tools. You rarely call them directly, but it helps to know they exist.
|
|
84
|
+
|
|
85
|
+
```mermaid
|
|
86
|
+
flowchart LR
|
|
87
|
+
CLI["toiljs CLI"] --> TS["toilscript<br/>(compiles server/ to wasm)"]
|
|
88
|
+
CLI --> VITE["Vite<br/>(bundles client/ React)"]
|
|
89
|
+
TS --> W["build/server/release.wasm"]
|
|
90
|
+
VITE --> B["build/client/"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
When you create a project, both `toiljs` and `toilscript` are added to it as dependencies, so everything is pinned to versions that work together. You do not install `toilscript` separately.
|
|
94
|
+
|
|
95
|
+
## Gotchas and notes
|
|
96
|
+
|
|
97
|
+
- **"command not found: toiljs"** after a global install usually means npm's global bin folder is not on your `PATH`. Either fix your `PATH` or just use `npx toiljs ...` instead.
|
|
98
|
+
- **Node too old** is the most common first-time failure. WebAssembly tooling and the build depend on features in Node 24 and up. Upgrade before creating a project.
|
|
99
|
+
- **Every command checks for updates.** On each run, the CLI quietly checks npm for a newer toiljs and prints a note if you are behind. It never blocks the command. To turn it off, set the environment variable `TOILJS_NO_UPDATE_CHECK=1`.
|
|
100
|
+
- You do **not** need to install a database, a Docker container, or any cloud account to develop locally. The dev server includes a local ToilDB so your data-backed features run out of the box.
|
|
101
|
+
|
|
102
|
+
## Related
|
|
103
|
+
|
|
104
|
+
- [Create a project](./create-project.md)
|
|
105
|
+
- [The CLI reference](../cli/README.md)
|
|
106
|
+
- [Getting started overview](./README.md)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Migrating an existing React app
|
|
2
|
+
|
|
3
|
+
How to bring a React app you already have into toiljs. The frontend usually moves over with small changes. The backend is the real work, because a toiljs server is not Node.js, and this page is honest about what that means.
|
|
4
|
+
|
|
5
|
+
## Why and when
|
|
6
|
+
|
|
7
|
+
Move to toiljs when you want your frontend and backend in one typed repo, deployed to the edge, with a built-in global database, and you are willing to rewrite your server logic against toiljs's rules. If you only want a React bundler, toiljs is more than you need. If you want the full-stack, typed, edge model, this is the payoff.
|
|
8
|
+
|
|
9
|
+
The safest approach is **not** to convert your old project in place. Instead, scaffold a fresh toiljs project and move code into it piece by piece. A fresh project comes with the required presets, config, and routing already wired, so you spend your time on your code, not on plumbing.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
toiljs create my-app
|
|
13
|
+
cd my-app
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## The big picture: what moves where
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
subgraph OLD["Your current app"]
|
|
21
|
+
FE["React components,<br/>pages, styles, assets"]
|
|
22
|
+
BE["Node/Express backend,<br/>DB access, npm libs"]
|
|
23
|
+
end
|
|
24
|
+
FE -->|"mostly copy"| C["toiljs client/"]
|
|
25
|
+
BE -->|"rewrite"| S["toiljs server/"]
|
|
26
|
+
C -.->|"generated typed client"| S
|
|
27
|
+
S --> DB[("ToilDB")]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- Your **React frontend** copies into `client/` with modest changes (routing and asset paths).
|
|
31
|
+
- Your **backend** is **rewritten** into `server/` as toilscript, because it compiles to WebAssembly, not Node.
|
|
32
|
+
|
|
33
|
+
## The frontend: what changes
|
|
34
|
+
|
|
35
|
+
`client/` is a normal Vite + React app, so most of your frontend works as is. The common adjustments:
|
|
36
|
+
|
|
37
|
+
- **Routing becomes file-based.** toiljs has no `<Routes>`/`<Route>` config. A file at `client/routes/about.tsx` is the `/about` page; `client/routes/blog/[slug].tsx` is `/blog/:slug`. Move each page component to the matching file and delete your router setup. Use `Toil.Link` for navigation instead of your router's `Link`. See [Routing](../frontend/routing.md).
|
|
38
|
+
- **Static assets move to `client/public/`.** Files there are served as is (images at `/images/...`, plus `favicon` and `robots.txt`).
|
|
39
|
+
- **Global styles move to `client/styles/`**, and are imported from `client/toil.tsx`. See [Styling](../frontend/styling.md).
|
|
40
|
+
- **Your React npm packages are fine.** The client is regular JavaScript, so component libraries, state managers, and browser APIs all work.
|
|
41
|
+
- **Data fetching changes** if you want the typed client: replace hand-written `fetch('/api/...')` calls with the generated `Server.REST.*` methods. You can keep raw `fetch` too, but you lose the type safety. See [Fetching data](../frontend/data-fetching.md).
|
|
42
|
+
|
|
43
|
+
Metadata and SEO that you used a helmet library for is built in: set a `metadata` export per route, or use `Toil.Head`. See [Metadata and SEO](../frontend/metadata.md).
|
|
44
|
+
|
|
45
|
+
## The backend: the honest part
|
|
46
|
+
|
|
47
|
+
This is where migration takes real effort. Read this section carefully before you plan the work.
|
|
48
|
+
|
|
49
|
+
Your toiljs server is compiled by **toilscript** into WebAssembly. toilscript accepts a **strict subset of TypeScript**. It looks like TypeScript, but treat it as a small, separate language that happens to share the syntax. The practical consequences:
|
|
50
|
+
|
|
51
|
+
### What is NOT available on the server
|
|
52
|
+
|
|
53
|
+
- **No arbitrary npm packages.** You cannot `import` a library from `node_modules` into `server/`. There is no `express`, no `pg`, no `stripe` SDK, no `lodash`. If your route logic leans on npm libraries, that logic has to be reworked.
|
|
54
|
+
- **No Node.js APIs.** No `fs`, `http`, `process`, `Buffer`, `path`, or other Node built-ins. The server runs in a sandbox, not in Node.
|
|
55
|
+
- **No DOM or browser APIs**, because it is not a browser either.
|
|
56
|
+
- **No connecting to your existing database.** There is no connection string and no driver. Your data moves to **ToilDB**, the built-in database.
|
|
57
|
+
|
|
58
|
+
### What you use instead
|
|
59
|
+
|
|
60
|
+
toiljs replaces the common needs with built-in globals and decorators, so you rarely miss the missing libraries:
|
|
61
|
+
|
|
62
|
+
| You used to reach for | On the toiljs server you use |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `express` routes / a router | `@rest` controllers with `@get` / `@post` ([HTTP routes](../backend/rest.md)) |
|
|
65
|
+
| A REST client between services | `@service` / `@remote` typed RPC ([RPC](../backend/rpc.md)) |
|
|
66
|
+
| Postgres / Mongo / Redis | [ToilDB](../database/README.md) families (documents, counters, events, views) |
|
|
67
|
+
| `jsonwebtoken`, session middleware | Built-in [auth](../auth/README.md) and cookies |
|
|
68
|
+
| `crypto` from Node | the `crypto` global (synchronous Web Crypto) ([Crypto](../services/crypto.md)) |
|
|
69
|
+
| `nodemailer` / an email SDK | the built-in email service ([Email](../services/email.md)) |
|
|
70
|
+
| `process.env` | `Environment.get()` / `Environment.getSecure()` ([Environment](../services/environment.md)) |
|
|
71
|
+
| `Date.now()`, timers | the `Time` global ([Time](../services/time.md)) |
|
|
72
|
+
|
|
73
|
+
### Type system differences
|
|
74
|
+
|
|
75
|
+
toilscript uses precise, explicit numeric types instead of JavaScript's single `number`. You will write `i32`, `u64`, `f64`, and even `u256`, and be explicit about integer sizes. Values that must cross the wire are `@data` classes with concrete fields, not free-form objects. There is no `any`-style duck typing to lean on. The full rules, and how server types map to `bigint` and friends on the client, are in [Types](../concepts/types.md).
|
|
76
|
+
|
|
77
|
+
### Two behaviors to design around
|
|
78
|
+
|
|
79
|
+
- **Memory resets every request.** Each request gets a fresh `.wasm` instance and its memory is wiped afterward. A module-level variable does not persist. Anything durable goes in ToilDB. (This is the same rule you met in [Your first app](./first-app.md).)
|
|
80
|
+
- **Reads and writes are split.** A `@get` is a read-only query; a `@post` is a write action. Operations that scan unbounded data are not allowed in a request handler; they move to a [`@derive`](../background/derive.md) or a background [daemon](../background/daemons.md).
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
Two config files replace the various configs you may have had:
|
|
85
|
+
|
|
86
|
+
- **`toil.config.ts`** is your client and build config: SEO defaults, image optimization, page transitions, and dev-server options. It uses `defineConfig`. See [Configuration](../concepts/config.md).
|
|
87
|
+
- **`toilconfig.json`** is the low-level server (wasm) build config. The scaffold sets sensible defaults, and you usually leave it alone.
|
|
88
|
+
|
|
89
|
+
Environment variables move from a `.env` you read with `process.env` to `.env` / `.env.secrets` files you read with `Environment.get()` / `Environment.getSecure()`. Secrets and plain vars are kept in separate buckets. See [Environment and secrets](../services/environment.md).
|
|
90
|
+
|
|
91
|
+
## Step by step
|
|
92
|
+
|
|
93
|
+
1. **Scaffold a fresh project** with `toiljs create` and get it running with `npm run dev`. Start from something that works.
|
|
94
|
+
2. **Move the frontend.** Copy components into `client/components/`, turn each page into a file under `client/routes/`, move styles into `client/styles/`, and static assets into `client/public/`. Swap your router's `Link` for `Toil.Link`. Install your client-side npm dependencies.
|
|
95
|
+
3. **Get the client rendering** against the routes, even if the data is still stubbed or points at your old backend. Fix routing and asset paths first.
|
|
96
|
+
4. **Rebuild the backend, one route at a time.** For each old endpoint, add a `@data` model in `server/models/`, a `@rest` controller in `server/routes/`, and move its data into a ToilDB family. Import each new route in `server/main.ts`. This is the bulk of the effort; go endpoint by endpoint.
|
|
97
|
+
5. **Move persistence to ToilDB.** Map each table or collection to a ToilDB family (a document store, a counter, an event log, or a view). See [Database overview](../database/README.md) for choosing a family.
|
|
98
|
+
6. **Switch the client to the typed client.** Replace `fetch('/api/...')` calls with `Server.REST.*` (or `Server.<service>.*` for RPC). Now a backend change that breaks the contract shows up as a client type error.
|
|
99
|
+
7. **Run the doctor.** `toiljs doctor` checks your wiring (routes, RPC generation, config, dependencies) and points at anything still off. Add `--fix` to let it repair what it can.
|
|
100
|
+
|
|
101
|
+
## When toiljs is not the right move
|
|
102
|
+
|
|
103
|
+
Be honest with yourself about the backend rewrite. Migration is a poor fit if:
|
|
104
|
+
|
|
105
|
+
- Your server depends heavily on npm libraries or Node-only APIs that have no toiljs equivalent, and rewriting them is not worth it.
|
|
106
|
+
- You must keep talking to an existing external database or service that toiljs cannot reach. (Daemons can make outbound HTTP calls in some cases, but general server-side networking is limited by design.)
|
|
107
|
+
- You need long-lived in-process server state that does not fit the per-request, database-backed model.
|
|
108
|
+
|
|
109
|
+
In those cases, you can still adopt toiljs for the frontend and keep your existing backend, calling it with plain `fetch`. You just give up the single-repo, end-to-end-typed benefits for that part.
|
|
110
|
+
|
|
111
|
+
## Gotchas and notes
|
|
112
|
+
|
|
113
|
+
- **Do not try to `import` server code into the client or vice versa.** They compile with different rules. The only bridge is the generated `shared/server.ts`.
|
|
114
|
+
- **One `@data` type per file** under `models/` matches the tooling's expectations.
|
|
115
|
+
- **`shared/server.ts` is generated**, so it will not exist until your first build, and you never edit it.
|
|
116
|
+
- **Plan the backend as a rewrite, not a port.** The frontend moves; the backend is re-expressed in toiljs's model. Budget for that.
|
|
117
|
+
|
|
118
|
+
## Related
|
|
119
|
+
|
|
120
|
+
- [Getting started overview](./README.md)
|
|
121
|
+
- [Backend overview](../backend/README.md)
|
|
122
|
+
- [Types](../concepts/types.md)
|
|
123
|
+
- [Database overview](../database/README.md)
|
|
124
|
+
- [The CLI reference](../cli/README.md)
|
|
125
|
+
- [Configuration](../concepts/config.md)
|