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,118 @@
|
|
|
1
|
+
# Scripts
|
|
2
|
+
|
|
3
|
+
`Toil.Script` loads an external or inline `<script>` for you, with control over *when* it runs and a guarantee it runs only *once* across your whole app. Use it instead of a hand-written `<script>` tag for third-party snippets (analytics, chat widgets, embeds). It is the toiljs analog of Next.js `next/script`.
|
|
4
|
+
|
|
5
|
+
## Why not just write a `<script>` tag?
|
|
6
|
+
|
|
7
|
+
Dropping a raw `<script>` into your JSX is unreliable in a single-page app (an app where the browser loads one HTML shell and JavaScript swaps pages in place, with no full reload). Two problems:
|
|
8
|
+
|
|
9
|
+
1. **It may not execute.** When React inserts a `<script>` element into the page, the browser does not always run it the way it runs scripts present in the original HTML.
|
|
10
|
+
2. **It runs too often.** As the user navigates between routes that both render that script, React can mount it more than once, so an analytics library or a widget initialises twice.
|
|
11
|
+
|
|
12
|
+
`Toil.Script` fixes both: it injects a real `<script>` into `<head>` so the browser runs it, and it **deduplicates** by a key so a given script executes at most once for the whole life of the app, even across client-side navigations. It renders nothing into your layout.
|
|
13
|
+
|
|
14
|
+
## The simplest usage: an external script
|
|
15
|
+
|
|
16
|
+
Give it a `src`. By default it loads once the app is interactive, which is right for most third-party scripts:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// client/layout.tsx
|
|
20
|
+
import { type ReactNode } from 'react';
|
|
21
|
+
|
|
22
|
+
export default function Layout({ children }: { children?: ReactNode }) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="app">
|
|
25
|
+
<Toil.Script src="https://cdn.example-analytics.com/analytics.js" />
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Putting it in the root layout means it loads once for the whole app and stays loaded as the user navigates. For an external script, the dedup key defaults to its `src`, so you never need an `id`.
|
|
33
|
+
|
|
34
|
+
## Load strategies
|
|
35
|
+
|
|
36
|
+
The `strategy` prop decides *when* the script is injected, relative to your app becoming interactive:
|
|
37
|
+
|
|
38
|
+
| Strategy | When it runs | Use it for |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `afterInteractive` (default) | On mount, once the app is running. | Analytics, chat widgets, most third-party scripts. |
|
|
41
|
+
| `lazyOnload` | Deferred until the browser is idle, after the page's `load` event. | Low-priority extras (a feedback button, a social embed) that should not compete with the initial render. |
|
|
42
|
+
| `beforeInteractive` | As early as possible: injected immediately on first mount. | A script other code depends on immediately. |
|
|
43
|
+
|
|
44
|
+
One honest caveat about `beforeInteractive`: a toiljs frontend is a client-only single-page app, so there is no server-rendered `<script>` to run before hydration. `beforeInteractive` therefore still runs *after* hydration, just as early and eagerly as possible on the first mount. It is a priority hint, not a true "before the page is interactive" guarantee.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Toil.Script
|
|
48
|
+
src="https://widget.example.com/embed.js"
|
|
49
|
+
strategy="lazyOnload"
|
|
50
|
+
/>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Inline scripts
|
|
54
|
+
|
|
55
|
+
To run a snippet of code instead of loading a URL, put the code in `children` (as a **string**) and give the script an `id`. An inline script has no `src`, so the `id` is what identifies it for dedup, and it is required:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<Toil.Script id="init-theme" strategy="beforeInteractive">
|
|
59
|
+
{`document.documentElement.dataset.theme =
|
|
60
|
+
localStorage.getItem('theme') ?? 'light';`}
|
|
61
|
+
</Toil.Script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Without an `id` (and no `src`), an inline script has nothing to dedup on, so `Toil.Script` does nothing at all. This is a deliberate no-op, not an error, so remember the `id`.
|
|
65
|
+
|
|
66
|
+
## Reacting to load
|
|
67
|
+
|
|
68
|
+
Three optional callbacks let you run code around the script's lifecycle:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<Toil.Script
|
|
72
|
+
src="https://widget.example.com/embed.js"
|
|
73
|
+
strategy="lazyOnload"
|
|
74
|
+
onReady={() => {
|
|
75
|
+
// The global the script defines is now available.
|
|
76
|
+
window.MyWidget?.init();
|
|
77
|
+
}}
|
|
78
|
+
onError={(err) => {
|
|
79
|
+
console.warn('widget failed to load', err);
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- `onLoad` fires **once**, when an external script finishes loading (or an inline script is inserted).
|
|
85
|
+
- `onReady` fires after load **and on every later mount** once the script is already loaded. So if a route that renders the `Toil.Script` is left and revisited, `onReady` runs again, which is the right place to re-initialise a widget.
|
|
86
|
+
- `onError` fires if an **external** script fails to load. After an error the script's key is cleared, so a later remount retries the load.
|
|
87
|
+
|
|
88
|
+
## All props
|
|
89
|
+
|
|
90
|
+
Read them straight from the source (`src/client/components/Script.tsx`):
|
|
91
|
+
|
|
92
|
+
| Prop | Type | Default | What it does |
|
|
93
|
+
| --- | --- | --- | --- |
|
|
94
|
+
| `src` | `string` | (none) | URL of an external script. Omit it when you provide an inline body via `children`. |
|
|
95
|
+
| `children` | `string` | (none) | Inline script body. Mutually exclusive with `src`. |
|
|
96
|
+
| `strategy` | `'beforeInteractive' \| 'afterInteractive' \| 'lazyOnload'` | `'afterInteractive'` | When to inject the script (see above). |
|
|
97
|
+
| `id` | `string` | `src` for external scripts | Stable identity for dedup. **Required** for inline scripts. |
|
|
98
|
+
| `type` | `string` | (none) | The `type` attribute, e.g. `'module'` or `'application/json'`. |
|
|
99
|
+
| `onLoad` | `() => void` | (none) | Fired once the script has loaded (external) or been inserted (inline). |
|
|
100
|
+
| `onReady` | `() => void` | (none) | Fired after load, and on every later mount once the script is already loaded. |
|
|
101
|
+
| `onError` | `(error: unknown) => void` | (none) | Fired if an external script fails to load. |
|
|
102
|
+
|
|
103
|
+
External scripts are injected with `async` set, so they never block other work while downloading.
|
|
104
|
+
|
|
105
|
+
## Gotchas
|
|
106
|
+
|
|
107
|
+
- **Inline scripts need an `id`.** No `id` and no `src` means nothing to dedup on, so the component quietly does nothing.
|
|
108
|
+
- **Dedup is app-wide and survives navigation.** The same `Toil.Script` rendered on two different routes runs a total of once, keyed by `id` (or `src`). This is the point, but it means you cannot use two copies of the same key to run something twice.
|
|
109
|
+
- **Props are read at injection time, and the script re-injects only if its key or strategy changes.** Changing a handler or an inline body *without* changing the `id` will not re-run the script. If you truly need to re-inject a changed inline script, change its `id`.
|
|
110
|
+
- **`onReady` versus `onLoad`.** Use `onLoad` for one-time setup that must happen exactly once; use `onReady` for setup that should also run each time a component remounts against an already-loaded script.
|
|
111
|
+
- **`beforeInteractive` is not truly before hydration** in a client-only app (see the caveat above). Do not rely on it running before your React tree mounts.
|
|
112
|
+
- **`Toil.Script` renders nothing.** It returns `null`, so you can place it anywhere in your component tree; a layout is the usual home for app-wide scripts.
|
|
113
|
+
|
|
114
|
+
## Related
|
|
115
|
+
|
|
116
|
+
- [Rendering and SSR](./rendering.md): how the client-only app and its first paint fit together.
|
|
117
|
+
- [Metadata and SEO](./metadata.md): for `<head>` tags (title, meta, Open Graph), which is a different job from loading scripts.
|
|
118
|
+
- [Frontend overview](./README.md): the `Toil` global and the rest of the client API.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Page search
|
|
2
|
+
|
|
3
|
+
toiljs ships a built-in search over your own pages: at build time it indexes every route's metadata (title, description, keywords, Open Graph), and at runtime you query that index to build a search box or a command palette that jumps straight to a page. It is entirely static (no server, no search service), and there is no equivalent in Next.js, so this page explains it from scratch.
|
|
4
|
+
|
|
5
|
+
## What it is
|
|
6
|
+
|
|
7
|
+
Every route in your app can export `metadata` (its title, description, and so on, see [Metadata and SEO](./metadata.md)). The toiljs compiler reads that metadata from all your routes at build time and bakes a small **page index** into your bundle: a list of `{ path, title, description, keywords, ... }` for every page. When your app starts, that index is registered in the browser. You can then search it instantly, offline, with zero network calls, and turn a match into a navigation.
|
|
8
|
+
|
|
9
|
+
This is what powers a "jump to any page" box or a `Cmd+K` command palette without you maintaining a list of pages or standing up a search backend.
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TD
|
|
13
|
+
A["Your routes<br/>each with export const metadata"] -->|build: scan + extract| B["Static page index<br/>{ path, dynamic, metadata } per page"]
|
|
14
|
+
B -->|baked into the bundle| C["App startup: Toil.registerPages(index)"]
|
|
15
|
+
C --> D["Browser: usePageSearch / searchPages query the index"]
|
|
16
|
+
D --> E["Ranked results -> goTo() navigates"]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Only **statically-known** metadata is indexed. A route whose `<head>` comes from a dynamic `generateMetadata` (a per-request title) has nothing to index by default; to include it anyway, export `searchHints` (see below).
|
|
20
|
+
|
|
21
|
+
## The `usePageSearch` hook
|
|
22
|
+
|
|
23
|
+
`usePageSearch` is the React way in. Give it the current query string, and it hands back ranked results plus a helper to navigate:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
const { results, pages, goTo } = Toil.usePageSearch(query);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
It returns an object with three fields:
|
|
30
|
+
|
|
31
|
+
| Field | Type | What it is |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `results` | `readonly PageSearchResult[]` | The ranked matches for `query`, best first. Empty when the query is blank. |
|
|
34
|
+
| `pages` | `readonly PageMeta[]` | The full page index, handy for showing an "all pages" listing. |
|
|
35
|
+
| `goTo` | `(target, options?) => void` | Navigates to a result, a page, or a raw path string. A stable reference (safe to pass to a child or destructure). |
|
|
36
|
+
|
|
37
|
+
Each item in `results` is a `PageSearchResult`:
|
|
38
|
+
|
|
39
|
+
- `page`: the matched `PageMeta`, which is `{ path, dynamic, metadata }`. `path` is the route URL (`'/about'`), `dynamic` says whether it has `:param` segments, and `metadata` is the indexed title/description/etc.
|
|
40
|
+
- `score`: a relevance number (higher is better; always above zero for a returned result).
|
|
41
|
+
- `matches`: which fields matched, for example `['title', 'keywords']`.
|
|
42
|
+
|
|
43
|
+
The results are memoised, so they recompute only when the query (or options) change, not on every render.
|
|
44
|
+
|
|
45
|
+
### A full search box
|
|
46
|
+
|
|
47
|
+
Here is a complete search page: an input, a ranked result list, and a click that navigates to the chosen page.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// client/routes/search.tsx
|
|
51
|
+
import { useState } from 'react';
|
|
52
|
+
|
|
53
|
+
export const metadata: Toil.Metadata = {
|
|
54
|
+
title: 'Search',
|
|
55
|
+
description: 'Find any page and jump straight to it.',
|
|
56
|
+
keywords: ['search', 'find', 'pages'],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default function SearchPage() {
|
|
60
|
+
const [query, setQuery] = useState('');
|
|
61
|
+
const { results, pages, goTo } = Toil.usePageSearch(query);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<main>
|
|
65
|
+
<h1>Search</h1>
|
|
66
|
+
<input
|
|
67
|
+
type="search"
|
|
68
|
+
value={query}
|
|
69
|
+
onChange={(e) => {
|
|
70
|
+
setQuery(e.target.value);
|
|
71
|
+
}}
|
|
72
|
+
placeholder={`Search ${pages.length} pages...`}
|
|
73
|
+
aria-label="Search pages"
|
|
74
|
+
autoFocus
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
{query.trim() !== '' && (
|
|
78
|
+
<ul>
|
|
79
|
+
{results.length === 0 && <li>No pages match "{query}".</li>}
|
|
80
|
+
{results.map((r) => (
|
|
81
|
+
<li key={r.page.path}>
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
onClick={() => {
|
|
85
|
+
goTo(r);
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<strong>{r.page.metadata.title ?? r.page.path}</strong>{' '}
|
|
89
|
+
<code>{r.page.path}</code>
|
|
90
|
+
{r.page.metadata.description !== undefined && (
|
|
91
|
+
<p>{r.page.metadata.description}</p>
|
|
92
|
+
)}
|
|
93
|
+
</button>
|
|
94
|
+
</li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
)}
|
|
98
|
+
</main>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`goTo` accepts a result (as above), a `PageMeta`, or a plain path string, and takes the same options as `navigate` (see [Routing](./routing.md)). Passing a result is the common case.
|
|
104
|
+
|
|
105
|
+
### Options
|
|
106
|
+
|
|
107
|
+
`usePageSearch` takes a second argument to tune the search:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const { results } = Toil.usePageSearch(query, {
|
|
111
|
+
limit: 8, // cap the number of results (after ranking)
|
|
112
|
+
includeDynamic: true, // include :param routes (see below); default false
|
|
113
|
+
fields: ['title', 'keywords'], // only match these fields; default all fields
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Option | Type | Default | Effect |
|
|
118
|
+
| --- | --- | --- | --- |
|
|
119
|
+
| `limit` | `number` | no cap | Keep only the top N results after ranking. |
|
|
120
|
+
| `includeDynamic` | `boolean` | `false` | Include dynamic (`:param` / `*catch-all`) routes. Off by default because you cannot navigate to them without filling in the params. |
|
|
121
|
+
| `fields` | array of field names | all fields | Restrict matching to a subset of `'title'`, `'description'`, `'keywords'`, `'path'`, `'openGraph'`. |
|
|
122
|
+
|
|
123
|
+
## Making dynamic routes searchable with `searchHints`
|
|
124
|
+
|
|
125
|
+
A dynamic route like `client/routes/blog/[id].tsx` usually produces its `<head>` with a per-post `generateMetadata`, so there is nothing static to index, and the route is missing from search. To surface it anyway, export `searchHints`: a small static object the compiler merges into the index for that route (winning ties against any static `metadata`).
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// client/routes/blog/[id].tsx
|
|
129
|
+
|
|
130
|
+
// The per-post <title> is dynamic, so it cannot be indexed. These static hints
|
|
131
|
+
// put the blog into the search index so it shows up when someone searches "blog".
|
|
132
|
+
export const searchHints: Toil.SearchHints = {
|
|
133
|
+
title: 'Blog',
|
|
134
|
+
description: 'Articles and updates.',
|
|
135
|
+
keywords: ['blog', 'posts', 'articles'],
|
|
136
|
+
};
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`SearchHints` has three optional fields: `title`, `description`, and `keywords` (a string or an array of strings).
|
|
140
|
+
|
|
141
|
+
Two things to keep in mind. First, `searchHints` only affects the index; the route is still dynamic, so it appears in results only when you pass `includeDynamic: true`. Second, you cannot `goTo` a dynamic page directly (it needs concrete params), so `goTo` is a no-op for one unless you hand it a real, filled-in path string. In practice you use `searchHints` to make a *section* discoverable (typing "blog" surfaces the blog), and point the user at a concrete landing URL.
|
|
142
|
+
|
|
143
|
+
## The lower-level API
|
|
144
|
+
|
|
145
|
+
The hook is a thin wrapper over a small, framework-agnostic core you can use directly (outside React, in tests, or to build your own UI). All of these are on the `Toil` global:
|
|
146
|
+
|
|
147
|
+
- `Toil.searchPages(query, options?)`: the pure ranking function behind the hook. Returns `PageSearchResult[]`. Same options as the hook.
|
|
148
|
+
- `Toil.getPages()`: the full registered index (`readonly PageMeta[]`), including dynamic routes. Empty before registration.
|
|
149
|
+
- `Toil.pagePath(target)`: normalises a result, a page, or a raw string down to its path string.
|
|
150
|
+
- `Toil.registerPages(pages)`: replaces the live index. toiljs calls this for you at startup with the compiler-built index, so you rarely call it yourself. It exists for tests and advanced setups that build a custom index.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { searchPages } from 'toiljs/client';
|
|
154
|
+
|
|
155
|
+
// Outside a component: the top 5 title/keyword matches for "billing".
|
|
156
|
+
const hits = searchPages('billing', { limit: 5, fields: ['title', 'keywords'] });
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## How ranking works
|
|
160
|
+
|
|
161
|
+
The matcher is simple and predictable:
|
|
162
|
+
|
|
163
|
+
- The query is lower-cased and split on whitespace into terms. **Every** term must match somewhere (AND semantics), or the page is dropped. A blank query returns nothing.
|
|
164
|
+
- Matching is case-insensitive and substring-based, with bonuses: an exact field match ranks highest, then a match at the start of the field, then a match at the start of a word inside the field, then a plain mid-word substring.
|
|
165
|
+
- Each field carries a weight, so a hit in the title counts for much more than a hit in the description:
|
|
166
|
+
|
|
167
|
+
| Field | Weight |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `title` | 10 |
|
|
170
|
+
| `path` | 6 |
|
|
171
|
+
| `keywords` | 5 |
|
|
172
|
+
| `description` | 3 |
|
|
173
|
+
| `openGraph` | 2 |
|
|
174
|
+
|
|
175
|
+
- The `path` field is made word-searchable: `/get-started` matches "get" or "started".
|
|
176
|
+
- Results are sorted by score (highest first), ties broken alphabetically by path for a stable order, then cut to `limit` if you set one.
|
|
177
|
+
|
|
178
|
+
## Gotchas
|
|
179
|
+
|
|
180
|
+
- **Only static metadata is indexed.** A route whose title comes from `generateMetadata` is invisible to search until you add `searchHints`.
|
|
181
|
+
- **Dynamic routes are excluded by default.** They need `includeDynamic: true` to appear, and even then `goTo` cannot navigate to one without concrete params.
|
|
182
|
+
- **The index is built, not live.** It reflects the metadata at build time. Add a route or change a title and you must rebuild for search to see it.
|
|
183
|
+
- **A blank query returns no results**, not every page. If you want an "all pages" listing for an empty box, render `pages` yourself.
|
|
184
|
+
- **The type names** `PageSearchResult`, `PageSearchOptions`, and `SearchField` are importable from `'toiljs/client'` if you need to annotate them; `PageMeta` and `SearchHints` are also available as `Toil.PageMeta` / `Toil.SearchHints`.
|
|
185
|
+
|
|
186
|
+
## Related
|
|
187
|
+
|
|
188
|
+
- [Metadata and SEO](./metadata.md): the `metadata` the search index is built from.
|
|
189
|
+
- [Routing](./routing.md): route paths, dynamic segments, and `navigate` (what `goTo` calls).
|
|
190
|
+
- [Rendering and SSR](./rendering.md): where `searchHints` fits alongside `generateStaticParams` for dynamic routes.
|
|
191
|
+
- [Frontend overview](./README.md): the `Toil` global and the rest of the client API.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
toiljs does not force a styling system on you. It builds with Vite, so you style your app the way you would any Vite + React project: plain CSS, CSS Modules, a preprocessor like Sass, or Tailwind. This page shows the practical options.
|
|
4
|
+
|
|
5
|
+
## The one import that matters
|
|
6
|
+
|
|
7
|
+
Your app pulls in global styles from the entry file, `client/toil.tsx`:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// client/toil.tsx
|
|
11
|
+
import { routes, layout, notFound, globalError, slots } from 'toiljs/routes';
|
|
12
|
+
import './styles/main.css'; // <- your global stylesheet
|
|
13
|
+
|
|
14
|
+
Toil.mount(routes, layout, notFound, globalError, slots);
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That one import is where your global CSS (resets, CSS variables, base element styles) lives. A typical `client/styles/main.css` sets up theme variables and base styles:
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
/* client/styles/main.css */
|
|
21
|
+
:root {
|
|
22
|
+
--accent: #2563ff;
|
|
23
|
+
--bg: #080d11;
|
|
24
|
+
--text: #f5f6fa;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
*, *::before, *::after {
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
background: var(--bg);
|
|
34
|
+
color: var(--text);
|
|
35
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
36
|
+
line-height: 1.6;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Importing CSS anywhere
|
|
41
|
+
|
|
42
|
+
You are not limited to the one global sheet. Any component can import its own CSS, and Vite bundles it in:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// client/components/Card.tsx
|
|
46
|
+
import './card.css';
|
|
47
|
+
|
|
48
|
+
export default function Card() {
|
|
49
|
+
return <div className="card">...</div>;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The class names in a plain `.css` file are global (they apply everywhere), so name them carefully to avoid clashes, or reach for CSS Modules.
|
|
54
|
+
|
|
55
|
+
## CSS Modules (scoped class names)
|
|
56
|
+
|
|
57
|
+
If you name a file `*.module.css`, Vite treats it as a **CSS Module**: the class names are locally scoped to the component that imports them, so two components can both use `.title` without colliding. You import the generated names as an object:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// client/components/Card.tsx
|
|
61
|
+
import styles from './card.module.css';
|
|
62
|
+
|
|
63
|
+
export default function Card() {
|
|
64
|
+
return <div className={styles.card}>...</div>;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
/* client/components/card.module.css */
|
|
70
|
+
.card {
|
|
71
|
+
padding: 1rem;
|
|
72
|
+
border: 1px solid var(--border);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is the simplest way to get component-scoped styles with no extra tooling.
|
|
77
|
+
|
|
78
|
+
## Preprocessors: Sass, Less, Stylus
|
|
79
|
+
|
|
80
|
+
When you create a project (`toiljs create`) you can pick a CSS preprocessor, or add one later to an existing project with the `configure` command:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
toiljs configure # interactive: choose preprocessor + Tailwind
|
|
84
|
+
toiljs configure --style sass # switch the preprocessor to Sass
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`configure` installs the right packages and rewrites your style imports for you. After that, `.scss` / `.sass` / `.less` / `.styl` files import exactly like `.css` files, and Vite compiles them.
|
|
88
|
+
|
|
89
|
+
## Tailwind
|
|
90
|
+
|
|
91
|
+
To use Tailwind (a utility-class CSS framework), turn it on at create time or add it later:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
toiljs configure --tailwind
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Tailwind lives in its own stylesheet, `client/styles/tailwind.css`, which is just:
|
|
98
|
+
|
|
99
|
+
```css
|
|
100
|
+
@import "tailwindcss";
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`configure` wires that import in for you. From then on you use Tailwind's utility classes directly in your JSX:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
export default function Cta() {
|
|
107
|
+
return (
|
|
108
|
+
<button className="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white">
|
|
109
|
+
Get started
|
|
110
|
+
</button>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Inline styles and the `style` prop
|
|
116
|
+
|
|
117
|
+
Standard React inline styles work as always, and are handy for one-off, dynamic values:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<div style={{ padding: 24, background: 'var(--surface)' }}>...</div>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Prefer classes for anything reused; keep inline styles for the truly per-instance case (a computed width, a dynamic color).
|
|
124
|
+
|
|
125
|
+
## How types work for style and asset imports
|
|
126
|
+
|
|
127
|
+
toiljs generates a `toil-env.d.ts` in your project that declares the import types, so TypeScript is happy importing stylesheets and images. It covers `.css`, `.scss`, `.sass`, `.less`, `.styl` (and friends), plus image formats (`.svg`, `.png`, `.jpg`, `.webp`, and so on), which import as a URL string:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import logoUrl from './logo.svg'; // logoUrl is a string (a hashed URL)
|
|
131
|
+
|
|
132
|
+
<img src={logoUrl} alt="Logo" />
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For images specifically, prefer the `Toil.Image` component and its `?toil` import, which also handle layout shift and blur placeholders (see [Images](./images.md)).
|
|
136
|
+
|
|
137
|
+
## Gotchas
|
|
138
|
+
|
|
139
|
+
- **Global CSS is global.** A plain `.css` import puts its class names in one shared namespace. If two files both define `.button`, the last one loaded wins. Use `*.module.css` (CSS Modules) or unique prefixes to scope styles.
|
|
140
|
+
- **Do not hand-edit `toil-env.d.ts`.** It is generated. If a new file type is not recognized, re-run the build so it regenerates, rather than editing it.
|
|
141
|
+
- **Preprocessor packages are managed by `configure`.** Add or switch preprocessors through `toiljs configure` so the packages and imports stay in sync; do not install them by hand.
|
|
142
|
+
|
|
143
|
+
## Related
|
|
144
|
+
|
|
145
|
+
- [Images](./images.md): the `Toil.Image` component and image imports.
|
|
146
|
+
- [The CLI](../cli/README.md): `toiljs configure` and every flag.
|
|
147
|
+
- [Frontend overview](./README.md): where styles fit in the `client/` folder.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
toiljs is a full-stack web framework: you write a React frontend and a TypeScript backend in one project, and toiljs ships both together. This section takes you from "nothing installed" to "a small feature running end to end."
|
|
4
|
+
|
|
5
|
+
## What toiljs is
|
|
6
|
+
|
|
7
|
+
Think of a normal web app as two programs that have to agree with each other:
|
|
8
|
+
|
|
9
|
+
- A **frontend**: the React code that runs in your user's browser.
|
|
10
|
+
- A **backend**: the code that runs on a server, answers requests, and talks to a database.
|
|
11
|
+
|
|
12
|
+
Normally these live in separate projects, speak to each other over hand-written HTTP calls, and drift apart until something breaks at runtime. toiljs puts both in one repository and wires them together with types, so a change on one side shows up as a compile error on the other side instead of a bug in production.
|
|
13
|
+
|
|
14
|
+
The twist is what your backend becomes. You write it in TypeScript, but toiljs does not run it in Node.js. Instead, a compiler called **toilscript** turns your backend into **WebAssembly** (often shortened to "Wasm"): a small, fast, sandboxed program that runs at the **edge**. "Edge" just means servers spread all over the world, close to your users, so requests do not have to travel to one far-away data center. A worldwide database called **ToilDB** is built in, so you can store and read data without setting up or connecting to a database yourself.
|
|
15
|
+
|
|
16
|
+
New terms, defined once:
|
|
17
|
+
|
|
18
|
+
- **WebAssembly / Wasm**: a compact binary format that runs code in a locked-down sandbox at near-native speed. Your backend compiles to a single `.wasm` file.
|
|
19
|
+
- **toilscript**: the compiler that turns your TypeScript backend into that `.wasm` file. It accepts a strict subset of TypeScript (more on that below).
|
|
20
|
+
- **Dacely edge**: the global network of servers that runs your `.wasm` backend.
|
|
21
|
+
- **ToilDB**: the built-in database that lives on the edge next to your code.
|
|
22
|
+
|
|
23
|
+
## The client / server / shared mental model
|
|
24
|
+
|
|
25
|
+
Every toiljs project is organized into three folders. The most important thing to learn first is **where each piece of code actually runs**.
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart LR
|
|
29
|
+
A["Your project<br/>(one repo)"] -->|toiljs build| B["client bundle<br/>React + JS"]
|
|
30
|
+
A -->|toilscript compiles| C["server.wasm<br/>your backend"]
|
|
31
|
+
B --> U["User's browser"]
|
|
32
|
+
C --> E["Dacely edge<br/>(worldwide servers)"]
|
|
33
|
+
E --> D[("ToilDB<br/>global database")]
|
|
34
|
+
U <-->|"HTTP / realtime"| E
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **`client/`** is your React app: pages, components, and styles. It is bundled by [Vite](https://vitejs.dev) and runs **in the browser**.
|
|
38
|
+
- **`server/`** is your backend: HTTP routes, database access, auth. It is compiled by toilscript to `build/server/release.wasm` and runs **on the edge** (and locally when you run the dev server).
|
|
39
|
+
- **`shared/`** holds a file that toiljs **generates for you** (`shared/server.ts`). It is a fully typed client: the browser calls your backend through a `Server` object, and TypeScript checks every call. You do not write this file by hand.
|
|
40
|
+
|
|
41
|
+
Here is the loop in one picture:
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
flowchart TD
|
|
45
|
+
W["client/routes/page.tsx<br/>(browser)"] -->|"Server.REST.likes.like()"| G["shared/server.ts<br/>(generated typed client)"]
|
|
46
|
+
G -->|"HTTP request"| S["server/routes/Likes.ts<br/>(your @rest route, in wasm)"]
|
|
47
|
+
S -->|"read / write"| DB[("ToilDB")]
|
|
48
|
+
S -->|"typed response"| W
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You write both ends in TypeScript, and the generated `shared/server.ts` in the middle keeps them in sync.
|
|
52
|
+
|
|
53
|
+
## Two rules to keep in mind
|
|
54
|
+
|
|
55
|
+
These two facts explain most of how toiljs backends behave. They are covered in depth later, but it helps to meet them now.
|
|
56
|
+
|
|
57
|
+
1. **The server runs one fresh instance per request.** Every request gets a brand-new copy of your `.wasm`, and its memory is wiped when the request ends. So a normal variable you set in one request is gone by the next request. Anything that must survive (accounts, counters, posts) has to go into **ToilDB** or another store. Nothing in a plain module-level variable persists.
|
|
58
|
+
|
|
59
|
+
2. **The server is not Node.js.** toilscript compiles a strict subset of TypeScript, so you cannot `import` an arbitrary npm package into `server/` or use Node APIs like `fs`. Instead, toiljs gives you built-in globals for the common needs: `crypto`, cookies, email, the database, and more. See [Types](../concepts/types.md) and [Decorators](../concepts/decorators.md) for the details.
|
|
60
|
+
|
|
61
|
+
The client side, by contrast, is normal React. You can use React libraries and browser APIs there as usual.
|
|
62
|
+
|
|
63
|
+
## The getting-started path
|
|
64
|
+
|
|
65
|
+
Work through these pages in order:
|
|
66
|
+
|
|
67
|
+
1. **[Installation](./installation.md)**: check your Node.js version and install the toiljs command-line tool.
|
|
68
|
+
2. **[Create a project](./create-project.md)**: scaffold a new app and see what you get.
|
|
69
|
+
3. **[Project structure](./project-structure.md)**: a tour of every folder and file, and where each one runs.
|
|
70
|
+
4. **[Your first app](./first-app.md)**: build a tiny feature end to end, a page that calls a backend route and reads and writes one piece of ToilDB data.
|
|
71
|
+
5. **[Migrating an existing app](./migrating.md)**: bring a React app you already have into toiljs.
|
|
72
|
+
6. **[Deploy](./deploy.md)**: build for production and self-host it, and how the managed edge fits in.
|
|
73
|
+
|
|
74
|
+
## Related
|
|
75
|
+
|
|
76
|
+
- [Documentation home](../README.md)
|
|
77
|
+
- [The CLI reference](../cli/README.md)
|
|
78
|
+
- [Frontend overview](../frontend/README.md)
|
|
79
|
+
- [Backend overview](../backend/README.md)
|
|
80
|
+
- [Database overview](../database/README.md)
|
|
81
|
+
- [Compute tiers (where code runs)](../concepts/tiers.md)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Create a project
|
|
2
|
+
|
|
3
|
+
Scaffold a brand-new toiljs app with one command. The CLI asks a few questions, writes the files, and (optionally) installs dependencies for you.
|
|
4
|
+
|
|
5
|
+
## Why and when
|
|
6
|
+
|
|
7
|
+
`toiljs create` is how every project starts. It wires up the enforced toiljs presets (TypeScript, ESLint, and Prettier config), the file-based routing, and a working client and server, so you get a project that builds and runs on the first try. Use it for a new app. To bring an **existing** React app into toiljs instead, see [Migrating](./migrating.md).
|
|
8
|
+
|
|
9
|
+
## The command
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
toiljs create my-app
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Replace `my-app` with your project name (it becomes the folder name). If you leave the name off, the wizard asks for one.
|
|
16
|
+
|
|
17
|
+
By default this runs an interactive wizard. To skip every question and accept the defaults (handy for scripts and CI), add `--yes`:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
toiljs create my-app --yes
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What the wizard asks
|
|
24
|
+
|
|
25
|
+
Running `toiljs create` walks you through these prompts. Each one has a flag you can pass instead, so you can answer some or all of them up front.
|
|
26
|
+
|
|
27
|
+
| Prompt | What it decides | Flag | Default |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| Project name | The folder and package name | (the first argument) | `my-toil-app` |
|
|
30
|
+
| Which template? | How much starter code you get | `-t, --template <app\|minimal>` | `app` |
|
|
31
|
+
| Styling | The CSS flavor for `client/` | `--style <css\|sass\|less\|stylus>` | `css` |
|
|
32
|
+
| Add Tailwind CSS? | Adds Tailwind on top of the styling | `--tailwind` / `--no-tailwind` | off |
|
|
33
|
+
| AI assistant files | Editor hint files for Claude, Cursor, Codex, Copilot | `--no-ai` (to skip) | all |
|
|
34
|
+
| Optimize images at build time? | Resize and compress imported images | `--images` / `--no-images` | on |
|
|
35
|
+
| Initialize a git repository? | Runs `git init` and stages the files | `--git` / `--no-git` | on |
|
|
36
|
+
| Install dependencies now? | Runs your package manager's install | `--install` / `--no-install` | on |
|
|
37
|
+
|
|
38
|
+
Two more flags do not have a prompt:
|
|
39
|
+
|
|
40
|
+
- `--pm <npm\|pnpm\|yarn\|bun>` picks the package manager to install with (default `npm`).
|
|
41
|
+
- `-y, --yes` accepts every default and runs without any prompts.
|
|
42
|
+
|
|
43
|
+
### The two templates
|
|
44
|
+
|
|
45
|
+
- **`app`** (default) is the full starter: a landing page, a shared layout, styles, and a set of demo routes that show off HTTP routes, typed RPC, cookies, auth, and a ToilDB-backed guestbook. Great for learning by reading real, working code.
|
|
46
|
+
- **`minimal`** is the bare minimum: a layout, a single home page, and a tiny server handler with one example endpoint. Great when you want a clean slate.
|
|
47
|
+
|
|
48
|
+
A fully non-interactive example:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
toiljs create my-app --yes --template minimal --style css --no-tailwind --pm pnpm
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## What gets scaffolded
|
|
55
|
+
|
|
56
|
+
Here is the shape of a new **`app`** project. The exact set of demo routes may grow over time, so this is trimmed for readability.
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
my-app/
|
|
60
|
+
package.json scripts + dependencies (toiljs, react, toilscript, ...)
|
|
61
|
+
toil.config.ts client/build config (SEO, images, page transitions)
|
|
62
|
+
toilconfig.json server (wasm) build config for toilscript
|
|
63
|
+
tsconfig.json TypeScript config for the client
|
|
64
|
+
eslint.config.js linting preset
|
|
65
|
+
.prettierrc formatting preset
|
|
66
|
+
.gitignore ignores build output, generated files, and .env files
|
|
67
|
+
toil-env.d.ts generated editor types for client globals (Toil.*)
|
|
68
|
+
toil-routes.d.ts generated typed-route names (filled in on first build)
|
|
69
|
+
README.md
|
|
70
|
+
CLAUDE.md / AGENTS.md AI assistant hint files (if you kept them)
|
|
71
|
+
|
|
72
|
+
client/ your React app (runs in the browser)
|
|
73
|
+
toil.tsx the client entry: mounts routes + layout
|
|
74
|
+
layout.tsx the root layout that wraps every page
|
|
75
|
+
404.tsx the not-found page
|
|
76
|
+
global-error.tsx the top-level error page
|
|
77
|
+
routes/ file-based pages (index.tsx = "/", about.tsx = "/about", ...)
|
|
78
|
+
components/ shared React components
|
|
79
|
+
styles/main.css global styles
|
|
80
|
+
public/ static files served as-is (favicon, robots.txt, images)
|
|
81
|
+
|
|
82
|
+
server/ your backend (compiled to wasm, runs on the edge)
|
|
83
|
+
main.ts the entry: wires the handler + imports your surface modules
|
|
84
|
+
tsconfig.json server-only TS config (loads the toilscript editor plugin)
|
|
85
|
+
toil-server-env.d.ts generated editor types for server globals (Cookie, crypto, ...)
|
|
86
|
+
core/ your top-level request handler and shared logic
|
|
87
|
+
models/ @data classes (the typed wire types)
|
|
88
|
+
routes/ @rest controllers (HTTP endpoints)
|
|
89
|
+
services/ @service classes and @remote functions (typed RPC)
|
|
90
|
+
migrations/ ToilDB schema migrations (README explains the convention)
|
|
91
|
+
scheduled/ reserved for scheduled tasks
|
|
92
|
+
|
|
93
|
+
shared/ (created by the build)
|
|
94
|
+
server.ts GENERATED typed client: the Server proxy + @data codecs
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The **`minimal`** template is the same layout with far fewer files: `client/` has just `layout.tsx`, `routes/index.tsx`, and `styles/main.css`; `server/` has `main.ts` and `core/AppHandler.ts` with a single example endpoint.
|
|
98
|
+
|
|
99
|
+
A few files are worth calling out now, and the next page ([Project structure](./project-structure.md)) walks through all of them:
|
|
100
|
+
|
|
101
|
+
- **`shared/server.ts` does not exist yet** in a fresh project. It is generated the first time you run `toiljs dev` or `toiljs build`. That is normal and expected.
|
|
102
|
+
- **`toil-routes.d.ts`** starts as a stub and gets filled in with your real route names on the first build, which is what makes `Toil.Link` route names type-check.
|
|
103
|
+
- **`.env` and `.env.secrets` are not created** for you. You add them yourself when you need local environment variables or secrets. They are already listed in `.gitignore` so you never commit them. See [Environment and secrets](../services/environment.md).
|
|
104
|
+
|
|
105
|
+
## Run it
|
|
106
|
+
|
|
107
|
+
Once scaffolding (and install) finishes, the CLI prints your next steps:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
cd my-app
|
|
111
|
+
npm run dev
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`npm run dev` runs `toiljs dev`, which builds your server to wasm, generates `shared/server.ts`, and starts the dev server with hot reload. Open the printed URL (by default `http://localhost:3000`) and you have a live app.
|
|
115
|
+
|
|
116
|
+
If you told the wizard **not** to install dependencies, run `npm install` first.
|
|
117
|
+
|
|
118
|
+
## Gotchas and notes
|
|
119
|
+
|
|
120
|
+
- **Scaffolding into a non-empty folder** asks for confirmation in interactive mode, and fails in `--yes` mode. Create into a fresh, empty directory.
|
|
121
|
+
- **The project name must be a valid package name** and must stay inside the current directory (no `..`, no absolute paths).
|
|
122
|
+
- **Git init is best-effort.** If `git` is not installed, the CLI skips that step and keeps going.
|
|
123
|
+
- **You do not run `toilscript` yourself.** It is added as a dependency and driven by `toiljs dev` / `toiljs build`.
|
|
124
|
+
|
|
125
|
+
## Related
|
|
126
|
+
|
|
127
|
+
- [Project structure](./project-structure.md)
|
|
128
|
+
- [Your first app](./first-app.md)
|
|
129
|
+
- [The CLI reference](../cli/README.md)
|
|
130
|
+
- [Configuration](../concepts/config.md)
|
|
131
|
+
- [Styling](../frontend/styling.md)
|