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.
Files changed (132) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +2 -2
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/index.js +303 -293
  5. package/build/compiler/.tsbuildinfo +1 -1
  6. package/build/compiler/config.d.ts +2 -0
  7. package/build/compiler/config.js +1 -0
  8. package/build/compiler/docs.js +8 -24
  9. package/build/compiler/generate.js +4 -2
  10. package/build/compiler/index.d.ts +1 -1
  11. package/build/compiler/index.js +69 -6
  12. package/build/compiler/toil-docs.generated.js +64 -22
  13. package/build/devserver/.tsbuildinfo +1 -1
  14. package/build/devserver/analytics/index.js +7 -3
  15. package/build/devserver/db/database.d.ts +4 -0
  16. package/build/devserver/db/database.js +43 -1
  17. package/build/devserver/db/index.d.ts +1 -1
  18. package/build/devserver/db/index.js +1 -1
  19. package/build/devserver/db/types.d.ts +3 -0
  20. package/build/devserver/db/types.js +2 -0
  21. package/build/devserver/runtime/module.js +4 -1
  22. package/docs/README.md +104 -65
  23. package/docs/auth/README.md +102 -0
  24. package/docs/auth/configuration.md +94 -0
  25. package/docs/auth/extending.md +202 -0
  26. package/docs/auth/how-it-works.md +138 -0
  27. package/docs/auth/usage.md +188 -0
  28. package/docs/backend/README.md +143 -0
  29. package/docs/backend/data.md +351 -0
  30. package/docs/backend/rest.md +402 -0
  31. package/docs/backend/rpc.md +226 -0
  32. package/docs/background/README.md +114 -0
  33. package/docs/background/daemons.md +230 -0
  34. package/docs/background/derive.md +179 -0
  35. package/docs/cli/README.md +377 -0
  36. package/docs/concepts/config.md +416 -0
  37. package/docs/concepts/decorators.md +127 -0
  38. package/docs/concepts/security.md +108 -0
  39. package/docs/concepts/tiers.md +166 -0
  40. package/docs/concepts/types.md +216 -0
  41. package/docs/database/README.md +143 -0
  42. package/docs/database/capacity.md +350 -0
  43. package/docs/database/counters.md +174 -0
  44. package/docs/database/documents.md +255 -0
  45. package/docs/database/events.md +307 -0
  46. package/docs/database/membership.md +246 -0
  47. package/docs/database/setup.md +155 -0
  48. package/docs/database/unique.md +216 -0
  49. package/docs/database/views.md +246 -0
  50. package/docs/frontend/README.md +101 -0
  51. package/docs/frontend/data-fetching.md +243 -0
  52. package/docs/frontend/images.md +148 -0
  53. package/docs/frontend/metadata.md +344 -0
  54. package/docs/frontend/rendering.md +236 -0
  55. package/docs/frontend/routing.md +344 -0
  56. package/docs/frontend/scripts.md +118 -0
  57. package/docs/frontend/search.md +191 -0
  58. package/docs/frontend/styling.md +147 -0
  59. package/docs/getting-started/README.md +81 -0
  60. package/docs/getting-started/create-project.md +131 -0
  61. package/docs/getting-started/deploy.md +101 -0
  62. package/docs/getting-started/first-app.md +215 -0
  63. package/docs/getting-started/installation.md +106 -0
  64. package/docs/getting-started/migrating.md +125 -0
  65. package/docs/getting-started/project-structure.md +163 -0
  66. package/docs/introduction/README.md +41 -0
  67. package/docs/introduction/design-principles.md +55 -0
  68. package/docs/introduction/distributed.md +74 -0
  69. package/docs/introduction/how-it-works.md +74 -0
  70. package/docs/introduction/hyperscale.md +51 -0
  71. package/docs/introduction/modern-stack.md +36 -0
  72. package/docs/introduction/vs-other-frameworks.md +48 -0
  73. package/docs/introduction/why-toil.md +93 -0
  74. package/docs/llms.txt +90 -0
  75. package/docs/realtime/README.md +102 -0
  76. package/docs/realtime/channels.md +211 -0
  77. package/docs/realtime/streams.md +369 -0
  78. package/docs/services/README.md +60 -0
  79. package/docs/services/analytics.md +268 -0
  80. package/docs/services/caching.md +175 -0
  81. package/docs/services/cookies.md +235 -0
  82. package/docs/services/crypto.md +209 -0
  83. package/docs/services/email.md +289 -0
  84. package/docs/services/environment.md +141 -0
  85. package/docs/services/ratelimit.md +174 -0
  86. package/docs/services/time.md +85 -0
  87. package/examples/basic/client/routes/analytics.tsx +13 -12
  88. package/examples/basic/server/models/SiteAnalytics.ts +29 -17
  89. package/examples/basic/server/routes/Analytics.ts +15 -17
  90. package/examples/basic/server/routes/UserId.ts +22 -0
  91. package/package.json +15 -11
  92. package/scripts/gen-toil-docs.mjs +24 -35
  93. package/server/auth/AuthController.ts +336 -0
  94. package/server/auth/AuthUser.ts +23 -0
  95. package/server/auth/index.ts +16 -0
  96. package/server/globals/auth.ts +31 -0
  97. package/server/globals/userid.ts +107 -0
  98. package/src/compiler/config.ts +13 -0
  99. package/src/compiler/docs.ts +16 -33
  100. package/src/compiler/generate.ts +6 -2
  101. package/src/compiler/index.ts +114 -6
  102. package/src/compiler/toil-docs.generated.ts +64 -22
  103. package/src/devserver/analytics/index.ts +10 -4
  104. package/src/devserver/db/database.ts +67 -1
  105. package/src/devserver/db/index.ts +1 -0
  106. package/src/devserver/db/types.ts +13 -0
  107. package/src/devserver/runtime/module.ts +7 -0
  108. package/test/analytics-dev.test.ts +2 -1
  109. package/test/devserver-database.test.ts +113 -0
  110. package/docs/auth-todo.md +0 -149
  111. package/docs/auth.md +0 -322
  112. package/docs/caching.md +0 -115
  113. package/docs/cli.md +0 -17
  114. package/docs/client.md +0 -39
  115. package/docs/cookies.md +0 -457
  116. package/docs/crypto.md +0 -130
  117. package/docs/daemon.md +0 -123
  118. package/docs/data.md +0 -131
  119. package/docs/derive.md +0 -159
  120. package/docs/email.md +0 -326
  121. package/docs/environment.md +0 -97
  122. package/docs/getting-started.md +0 -128
  123. package/docs/index.md +0 -30
  124. package/docs/ratelimit.md +0 -95
  125. package/docs/routing.md +0 -259
  126. package/docs/rpc.md +0 -149
  127. package/docs/server.md +0 -61
  128. package/docs/ssr.md +0 -632
  129. package/docs/streams.md +0 -178
  130. package/docs/styling.md +0 -22
  131. package/docs/tiers.md +0 -133
  132. 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)