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
package/docs/routing.md
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
# Routing
|
|
2
|
-
|
|
3
|
-
toiljs routing is decorator-driven. You write a controller class, annotate it
|
|
4
|
-
with `@rest` and its methods with verb decorators, and the ToilScript compiler
|
|
5
|
-
generates the dispatcher. Routes can take a typed body, read path params and the
|
|
6
|
-
raw request through a `RouteContext`, and return either a `Response` or a typed
|
|
7
|
-
value that is auto-encoded.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { Response, RouteContext } from 'toiljs/server/runtime';
|
|
11
|
-
|
|
12
|
-
@rest('players')
|
|
13
|
-
class Players {
|
|
14
|
-
@get('/:id')
|
|
15
|
-
public get(ctx: RouteContext): Response {
|
|
16
|
-
const id = ctx.param('id');
|
|
17
|
-
return Response.json(`{"id":"${id}"}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
@post('/')
|
|
21
|
-
public create(input: NewPlayer): Player {
|
|
22
|
-
// `input` is the decoded request body; returning a @data value JSON-encodes it
|
|
23
|
-
return Player.from(input);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## `@rest` controllers
|
|
29
|
-
|
|
30
|
-
`@rest` marks a class as a route controller and mounts it at a prefix.
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
@rest('api') // mounted at /api
|
|
34
|
-
@rest('/') // or @rest('') // mounted at the root
|
|
35
|
-
@rest({ stream: DataStream.Binary }) // root mount, binary codec by default
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
- The string argument is the mount prefix. `"api"`, `"/api"`, and `"api/"` all
|
|
39
|
-
normalize to `/api`; `""` and `"/"` mean the root.
|
|
40
|
-
- The object form sets class-wide defaults. `stream: DataStream.Binary` makes
|
|
41
|
-
every route in the class use the binary `@data` codec; the default is
|
|
42
|
-
`DataStream.JSON`. Individual routes override this with `@route`.
|
|
43
|
-
|
|
44
|
-
The compiler injects, at module init, a registration that adds the controller to
|
|
45
|
-
the global `Rest` registry. Controllers dispatch in the order their modules are
|
|
46
|
-
loaded; routes within a controller try in declaration order, first match wins.
|
|
47
|
-
|
|
48
|
-
## Verb decorators
|
|
49
|
-
|
|
50
|
-
Each HTTP method has a decorator taking a single path string:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
@get('/path') @post('/path') @put('/path') @delete('/path')
|
|
54
|
-
@patch('/path') @head('/path') @options('/path')
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The full path is the controller prefix joined with the route path
|
|
58
|
-
(`prefix="/api"`, `@get("/todos/:id")` → `/api/todos/:id`).
|
|
59
|
-
|
|
60
|
-
### `@route` (explicit form)
|
|
61
|
-
|
|
62
|
-
`@route` is the general form; use it when you need to set the stream mode per
|
|
63
|
-
route or prefer an object:
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
@route({ method: Methods.POST, path: '/upload', stream: DataStream.Binary })
|
|
67
|
-
public upload(body: FileData): FileResult { /* ... */ }
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
`method` (from the `Methods` enum) and `path` are required; `stream` is
|
|
71
|
-
optional and overrides the controller default.
|
|
72
|
-
|
|
73
|
-
## Path parameters
|
|
74
|
-
|
|
75
|
-
A `:name` segment captures that URL segment. Read it with `ctx.param("name")`:
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
@get('/todos/:id/items/:itemId')
|
|
79
|
-
public getItem(ctx: RouteContext): Response {
|
|
80
|
-
const id = ctx.param('id');
|
|
81
|
-
const itemId = ctx.param('itemId');
|
|
82
|
-
return Response.json(`{"todo":"${id}","item":"${itemId}"}`);
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Matching is segment-exact: the request path must have the same number of
|
|
87
|
-
segments, static segments must match literally, and `:param` segments capture
|
|
88
|
-
the value. The query string is stripped before matching.
|
|
89
|
-
|
|
90
|
-
## Method parameters
|
|
91
|
-
|
|
92
|
-
A route method takes zero, one, or two parameters, classified by type:
|
|
93
|
-
|
|
94
|
-
- a `RouteContext` parameter receives the match context (path params, query,
|
|
95
|
-
headers, raw body);
|
|
96
|
-
- any other type is treated as the **request body**, decoded as a `@data` value.
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
@get('/status')
|
|
100
|
-
public status(): StatusResponse { /* no body, no context */ }
|
|
101
|
-
|
|
102
|
-
@get('/user/:id')
|
|
103
|
-
public getUser(ctx: RouteContext): User { /* context only */ }
|
|
104
|
-
|
|
105
|
-
@post('/create')
|
|
106
|
-
public create(input: NewTodo): Todo { /* body only */ }
|
|
107
|
-
|
|
108
|
-
@post('/user/:id/score')
|
|
109
|
-
public addScore(input: ScoreDelta, ctx: RouteContext): Player {
|
|
110
|
-
const id = ctx.param('id'); /* body AND context */
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
The body is decoded per the route's stream mode: in JSON mode from
|
|
115
|
-
`JSON.parse(ctx.text())`, in Binary mode from `Body.decode(req.body)`. See
|
|
116
|
-
[Data codec](./data.md).
|
|
117
|
-
|
|
118
|
-
## Return types
|
|
119
|
-
|
|
120
|
-
The compiler encodes the return value by its type:
|
|
121
|
-
|
|
122
|
-
| Return type | Result |
|
|
123
|
-
| --- | --- |
|
|
124
|
-
| `Response` | Returned as-is. Full control over status, headers, body. |
|
|
125
|
-
| `void` | `204 No Content`. |
|
|
126
|
-
| a `@data` type, JSON stream | `Response.json(value.toJSON().toString())`. |
|
|
127
|
-
| a `@data` type, Binary stream | `Response.bytes(value.encode())`. |
|
|
128
|
-
|
|
129
|
-
Returning a `Response` lets you set status, headers, cookies, and caching
|
|
130
|
-
directly; returning a typed value is the terse path when you just want the data
|
|
131
|
-
serialized.
|
|
132
|
-
|
|
133
|
-
## Data streams
|
|
134
|
-
|
|
135
|
-
Each route is either **JSON** (default) or **Binary**:
|
|
136
|
-
|
|
137
|
-
- **JSON**, the body is `JSON.parse`d and revived via the `@data` type's
|
|
138
|
-
`fromJSON`; the response is the type's `toJSON()`. 64-bit-and-larger integers
|
|
139
|
-
cross the wire as decimal strings (exact at any size). Best for endpoints a
|
|
140
|
-
browser or third party calls directly.
|
|
141
|
-
- **Binary**, the body is `Body.decode(bytes)` and the response is
|
|
142
|
-
`value.encode()`, using the deterministic `DataWriter`/`DataReader` codec. No
|
|
143
|
-
precision loss, smaller, faster. Best for app-to-app and anything
|
|
144
|
-
security-sensitive.
|
|
145
|
-
|
|
146
|
-
Set the mode on the controller (`@rest({ stream: DataStream.Binary })`) or per
|
|
147
|
-
route (`@route({ ..., stream: DataStream.Binary })`).
|
|
148
|
-
|
|
149
|
-
## Dispatch and the 404 fallback
|
|
150
|
-
|
|
151
|
-
At runtime the global `Rest` registry tries each controller in order:
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
const hit = Rest.dispatch(req); // Response | null
|
|
155
|
-
if (hit != null) return hit; // first matching route's Response
|
|
156
|
-
return Response.unhandled(); // no route matched
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
`RestHandler` is a ready-made handler that does exactly this, so a REST-only app
|
|
160
|
-
needs no custom handler:
|
|
161
|
-
|
|
162
|
-
```ts
|
|
163
|
-
import { RestHandler } from 'toiljs/server/runtime';
|
|
164
|
-
Server.handler = () => new RestHandler();
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
`Response.unhandled()` is a `404` carrying the `x-toil-unhandled` marker header.
|
|
168
|
-
On the dev server and edge that marker means "no route matched here" and lets
|
|
169
|
-
the request fall through to the next layer (Vite in dev, static/SSR on the
|
|
170
|
-
edge). A deliberate `Response.notFound()` does **not** carry the marker and is
|
|
171
|
-
sent to the client verbatim.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## `Request`
|
|
176
|
-
|
|
177
|
-
The decoded incoming request (`server/runtime/request.ts`).
|
|
178
|
-
|
|
179
|
-
### Fields
|
|
180
|
-
|
|
181
|
-
| Field | Type | Notes |
|
|
182
|
-
| --- | --- | --- |
|
|
183
|
-
| `method` | `Method` | `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `UNKNOWN`. |
|
|
184
|
-
| `path` | `string` | Path including the query string. |
|
|
185
|
-
| `headers` | `Array<Header>` | Ordered; a `Header` is `{ name, value }`. |
|
|
186
|
-
| `body` | `Uint8Array` | Raw request body bytes. |
|
|
187
|
-
|
|
188
|
-
### Methods
|
|
189
|
-
|
|
190
|
-
| Method | Signature | Notes |
|
|
191
|
-
| --- | --- | --- |
|
|
192
|
-
| `header` | `header(name: string): string \| null` | Case-insensitive lookup, `null` if absent. |
|
|
193
|
-
| `cookies` | `cookies(): CookieMap` | Parses the `Cookie` header (percent-decoded values); cached for the request. |
|
|
194
|
-
| `cookie` | `cookie(name: string): string \| null` | A single cookie value, or `null`. |
|
|
195
|
-
|
|
196
|
-
The `Method` enum and `Header` class are exported from
|
|
197
|
-
`toiljs/server/runtime`.
|
|
198
|
-
|
|
199
|
-
## `RouteContext`
|
|
200
|
-
|
|
201
|
-
Passed to any route method that declares a `RouteContext` parameter
|
|
202
|
-
(`server/runtime/rest/RouteContext.ts`).
|
|
203
|
-
|
|
204
|
-
| Member | Signature | Notes |
|
|
205
|
-
| --- | --- | --- |
|
|
206
|
-
| `request` | `Request` | The raw incoming request. |
|
|
207
|
-
| `param` | `param(name: string): string` | Captured path param; `""` if absent. |
|
|
208
|
-
| `query` | `query(name: string): string` | Query-string value; `""` if absent. Not URL-decoded in v1. |
|
|
209
|
-
| `header` | `header(name: string): string \| null` | Case-insensitive request header. |
|
|
210
|
-
| `text` | `text(): string` | The request body decoded as UTF-8. |
|
|
211
|
-
|
|
212
|
-
## `Response`
|
|
213
|
-
|
|
214
|
-
The outgoing response builder (`server/runtime/response.ts`). Construct one with
|
|
215
|
-
a static factory, then chain instance methods (each returns the same `Response`).
|
|
216
|
-
|
|
217
|
-
### Constructor
|
|
218
|
-
|
|
219
|
-
```ts
|
|
220
|
-
new Response(status: u16, body: Uint8Array, headers: Array<Header> | null = null)
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Static factories
|
|
224
|
-
|
|
225
|
-
| Factory | Signature | Status | Content-Type |
|
|
226
|
-
| --- | --- | --- | --- |
|
|
227
|
-
| `Response.text` | `text(body: string, status: u16 = 200)` | 200 | `text/plain; charset=utf-8` |
|
|
228
|
-
| `Response.html` | `html(body: string, status: u16 = 200)` | 200 | `text/html; charset=utf-8` |
|
|
229
|
-
| `Response.json` | `json(body: string, status: u16 = 200)` | 200 | `application/json; charset=utf-8` |
|
|
230
|
-
| `Response.bytes` | `bytes(body: Uint8Array, status: u16 = 200)` | 200 | `application/octet-stream` |
|
|
231
|
-
| `Response.empty` | `empty(status: u16)` | custom | (none) |
|
|
232
|
-
| `Response.notFound` | `notFound()` | 404 | text |
|
|
233
|
-
| `Response.badRequest` | `badRequest(msg = 'bad request')` | 400 | text |
|
|
234
|
-
| `Response.internalError` | `internalError(msg = 'internal error')` | 500 | text |
|
|
235
|
-
| `Response.unhandled` | `unhandled()` | 404 | text + `x-toil-unhandled` marker |
|
|
236
|
-
|
|
237
|
-
`json` takes an already-serialized string; build it with `DataWriter`-free JSON
|
|
238
|
-
or a `@data` type's `toJSON().toString()`. For binary, prefer `bytes`.
|
|
239
|
-
|
|
240
|
-
### Instance methods
|
|
241
|
-
|
|
242
|
-
| Method | Signature | Notes |
|
|
243
|
-
| --- | --- | --- |
|
|
244
|
-
| `setHeader` | `setHeader(name: string, value: string): Response` | Appends a header (repeatable). |
|
|
245
|
-
| `setCookie` | `setCookie(cookie: Cookie): Response` | Appends a `Set-Cookie`. Call again for more. |
|
|
246
|
-
| `setCookieKV` | `setCookieKV(name: string, value: string): Response` | Shorthand for `setCookie(new Cookie(name, value))`. |
|
|
247
|
-
| `clearCookie` | `clearCookie(name: string, path = '/', domain = ''): Response` | Emits a deletion `Set-Cookie` (empty value, `Max-Age=0`). |
|
|
248
|
-
| `cache` | `cache(edgeTtlMinutes: u16, browserTtlSeconds: u32 = 0, privateScope: bool = false, allowAuth: bool = false): Response` | Marks the response cacheable. See [Caching](./caching.md). |
|
|
249
|
-
| `cacheFor` | `cacheFor(minutes: u16): Response` | Shorthand for `cache(minutes)` (edge only). |
|
|
250
|
-
|
|
251
|
-
```ts
|
|
252
|
-
return Response.json('{"id":42}')
|
|
253
|
-
.setHeader('x-trace', traceId)
|
|
254
|
-
.setCookie(Cookie.create('sid', token).httpOnly().secure())
|
|
255
|
-
.cacheFor(5);
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
See [Cookies](./cookies.md) for the cookie builder, and [Caching](./caching.md)
|
|
259
|
-
for the cache directives.
|
package/docs/rpc.md
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# RPC and the generated client
|
|
2
|
-
|
|
3
|
-
The server build with `--rpcModule shared/server.ts` scans your decorated
|
|
4
|
-
surface (`@data`, `@user`, `@service`/`@remote`, `@rest`) and emits one
|
|
5
|
-
TypeScript module: a typed `Server` proxy, the `@data` codec classes, the REST
|
|
6
|
-
fetch client, and the `getUser()` accessor. The client imports that file and
|
|
7
|
-
calls the server with full type-safety and editor autocomplete. The file is
|
|
8
|
-
regenerated on every server build, so it never drifts from the server.
|
|
9
|
-
|
|
10
|
-
```sh
|
|
11
|
-
toilscript --target release --rpcModule shared/server.ts
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## `@service` and `@remote`
|
|
15
|
-
|
|
16
|
-
A `@service` class exposes its `@remote` methods as callable RPC. A top-level
|
|
17
|
-
`@remote` function is exposed directly.
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
@service
|
|
21
|
-
class Stats {
|
|
22
|
-
@remote
|
|
23
|
-
public playerCount(): i32 { return store.size; }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@remote
|
|
27
|
-
function ping(n: i32): i32 { return n + 1; }
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
The generated client surfaces these on the global `Server` proxy. A service is
|
|
31
|
-
keyed by its class name with the first letter lowercased (`Stats` → `stats`):
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
await Server.stats.playerCount(); // Promise<number>
|
|
35
|
-
await Server.ping(42); // Promise<number>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Arguments and return values are `@data`-typed; scalars map to TS as below.
|
|
39
|
-
|
|
40
|
-
## The generated `Server` surface
|
|
41
|
-
|
|
42
|
-
`shared/server.ts` declares a global `Server` whose shape is, schematically:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
declare global {
|
|
46
|
-
const Server: {
|
|
47
|
-
// top-level @remote functions
|
|
48
|
-
ping(n: number): Promise<number>;
|
|
49
|
-
|
|
50
|
-
// @service classes (keyed by lowercased name)
|
|
51
|
-
readonly stats: {
|
|
52
|
-
playerCount(): Promise<number>;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// @rest controllers, under REST
|
|
56
|
-
readonly REST: {
|
|
57
|
-
readonly players: {
|
|
58
|
-
get(args: { params: { id: string | number | bigint }; query?: …; headers?: … }): Promise<Response>;
|
|
59
|
-
create(args: { body: NewPlayer; query?: …; headers?: … }): Promise<Player>;
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Type mapping (ToilScript → TypeScript)
|
|
67
|
-
|
|
68
|
-
| ToilScript | TypeScript |
|
|
69
|
-
| --- | --- |
|
|
70
|
-
| `u8`,`u16`,`u32`,`i8`,`i16`,`i32`,`f32`,`f64` | `number` |
|
|
71
|
-
| `u64`,`i64`,`u128`,`i128`,`u256`,`i256` | `bigint` |
|
|
72
|
-
| `bool` | `boolean` |
|
|
73
|
-
| `string` | `string` |
|
|
74
|
-
| a `@data` class `T` | `T` (the emitted class) |
|
|
75
|
-
| `T[]` | `T[]` |
|
|
76
|
-
|
|
77
|
-
64-bit-and-larger integers are `bigint` on the client and travel as decimal
|
|
78
|
-
strings on the JSON wire, so they are exact at any magnitude.
|
|
79
|
-
|
|
80
|
-
### Emitted `@data` classes
|
|
81
|
-
|
|
82
|
-
Each `@data` (and `@user`) class becomes an exported TS class with the fields, a
|
|
83
|
-
defaulted constructor, and the matching codec:
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
export class Player {
|
|
87
|
-
constructor(public username = '', public admin = false, public score = 0n) {}
|
|
88
|
-
encodeInto(w: DataWriter): void { /* … */ }
|
|
89
|
-
encode(): Uint8Array { /* dataId prefix + fields */ }
|
|
90
|
-
static decodeFrom(r: DataReader): Player { /* … */ }
|
|
91
|
-
static decode(buf: Uint8Array): Player { /* … */ }
|
|
92
|
-
static dataId(): number { /* FNV-1a of "Player" */ }
|
|
93
|
-
static fromJSONValue(v: any): Player { /* revive, 64-bit from strings */ }
|
|
94
|
-
toJSONValue(): any { /* 64-bit as decimal strings */ }
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The codec is byte-compatible with the server's `@data` codec, so binary bodies
|
|
99
|
-
round-trip exactly between client and wasm.
|
|
100
|
-
|
|
101
|
-
## The REST fetch client
|
|
102
|
-
|
|
103
|
-
Every `@rest` route also gets a typed fetch wrapper under `Server.REST.<key>`,
|
|
104
|
-
keyed by the controller name lowercased. The call argument is an object:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
Server.REST.players.create({
|
|
108
|
-
body: new NewPlayer('alice'), // present iff the route takes a body
|
|
109
|
-
// params: { id: 7 }, // present iff the path has :params
|
|
110
|
-
query: { ref: 'home' }, // optional
|
|
111
|
-
headers: { 'x-trace': traceId }, // optional
|
|
112
|
-
});
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
- If the route has no params and no body, the whole argument is optional
|
|
116
|
-
(`args?`).
|
|
117
|
-
- The wrapper builds the URL (substituting `:params`, appending `query`),
|
|
118
|
-
`fetch`es with `credentials` as configured, throws on a non-2xx status, and
|
|
119
|
-
decodes the response into the route's return type.
|
|
120
|
-
- A route declared to return `Response` resolves to the raw `fetch` `Response`,
|
|
121
|
-
so you can stream or inspect headers yourself.
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
const player = await Server.REST.players.create({ body: new NewPlayer('alice') });
|
|
125
|
-
// ^? Player
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## `getUser()`
|
|
129
|
-
|
|
130
|
-
When the server declares a `@user` class, the generated module also exports a
|
|
131
|
-
typed, no-argument `getUser()` that reads the readable companion cookie and
|
|
132
|
-
decodes it with the generated codec:
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
import { getUser } from './shared/server';
|
|
136
|
-
|
|
137
|
-
const user = getUser(); // Account | null, fully typed
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
This is **display-only**: the server re-verifies the signed session on every
|
|
141
|
-
`@auth` request. See [Auth](./auth.md) for the full picture.
|
|
142
|
-
|
|
143
|
-
## Notes
|
|
144
|
-
|
|
145
|
-
- `shared/server.ts` is generated; never edit it by hand. Re-run the server
|
|
146
|
-
build (or `toiljs dev`, which does it on save) to refresh it.
|
|
147
|
-
- The `Server` proxy is declared as an ambient global on the client; the runtime
|
|
148
|
-
implementation is provided by toiljs. The REST client and `getUser` are real
|
|
149
|
-
exported values in the generated module.
|
package/docs/server.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Server (toilscript → WebAssembly)
|
|
2
|
-
|
|
3
|
-
`server/` is the toilscript source, compiled to WebAssembly by `toilscript`.
|
|
4
|
-
|
|
5
|
-
- `server/main.ts`, the `@main` entry, exported as the WASM `main`.
|
|
6
|
-
- `server/index.ts`, your functions.
|
|
7
|
-
- `server/tsconfig.json`, extends `toilscript/std/assembly.json` (AssemblyScript/toilscript
|
|
8
|
-
globals like `i32`, not the DOM), so editors resolve server types correctly.
|
|
9
|
-
- `npm run build:server` (or `npm run build`) emits `build/server/release.wasm` and
|
|
10
|
-
regenerates `shared/server.ts` (the typed client RPC module).
|
|
11
|
-
|
|
12
|
-
## Typed RPC (`@data` / `@remote` / `@service`)
|
|
13
|
-
|
|
14
|
-
Tag server code and the build generates a typed client `Server` surface:
|
|
15
|
-
|
|
16
|
-
- `@data class X {}`, a serializable struct. Generates a client class with the same fields
|
|
17
|
-
plus `encode`/`decode`; construct it on the client: `import { X } from "shared/server"`.
|
|
18
|
-
- `@remote function f(a: T): R`, a client-callable endpoint, becomes `Server.f(a)`.
|
|
19
|
-
- `@service class S { @remote m(...) {} }`, namespaces methods: `Server.s.m(...)`.
|
|
20
|
-
|
|
21
|
-
On the client, `Server` is a global (no import) and fully typed; every call is async
|
|
22
|
-
(`Promise<R>`). Inputs/outputs are scalars, arrays, or `@data` classes, both directions.
|
|
23
|
-
|
|
24
|
-
Note: the client↔server transport is not wired yet, so calling a `Server` method throws
|
|
25
|
-
until it lands; the typed surface + codec are generated and ready.
|
|
26
|
-
|
|
27
|
-
## HTTP REST (`@rest` / `@route`)
|
|
28
|
-
|
|
29
|
-
Tag a class `@rest` and its methods with a verb to expose a real HTTP API. Unlike RPC,
|
|
30
|
-
the generated client is working `fetch` code (it is just HTTP).
|
|
31
|
-
|
|
32
|
-
- `@rest("api") class Todos {}`, mounts the controller at `/api` (bare `@rest` → `/`).
|
|
33
|
-
- `@get("/todos/:id")` / `@post` / `@del` / `@put` / `@patch` / `@head` / `@options`, verb
|
|
34
|
-
shortcuts; or `@route({ method: Methods.GET, path: "/todos", stream: DataStream.JSON })`.
|
|
35
|
-
- A method takes an optional `@data` body + an optional `ctx: RouteContext` (path params via
|
|
36
|
-
`ctx.param("id")`, `ctx.query(...)`, `ctx.header(...)`). It returns either a `@data` type,
|
|
37
|
-
which the compiler encodes per `stream` (`DataStream.JSON` default, or `DataStream.Binary`,
|
|
38
|
-
lossless for large `u64`/bignum), or a `Response` for full control - custom status and
|
|
39
|
-
headers, e.g. `Response.json(value.toJSON().toString()).setHeader("cache-control", "no-store")`
|
|
40
|
-
or `Response.notFound()`. (The editor sees the compiler-injected `@data` `toJSON`/`encode`
|
|
41
|
-
members via the toilscript plugin, so serializing into a `Response` is editor-clean.)
|
|
42
|
-
|
|
43
|
-
Each `@rest` class self-registers; dispatch them from your handler - it composes, it never
|
|
44
|
-
takes over `handle()`:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { ToilHandler, Request, Response, Rest } from "toiljs/server/runtime";
|
|
48
|
-
export class App extends ToilHandler {
|
|
49
|
-
public handle(req: Request): Response {
|
|
50
|
-
const hit = Rest.dispatch(req); // try every @rest controller
|
|
51
|
-
if (hit != null) return hit;
|
|
52
|
-
return Response.notFound(); // your own logic / static fallback
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
For a REST-only project, `Server.handler = () => new RestHandler()` does the same with no
|
|
58
|
-
boilerplate. On the client: `Server.REST.todos.getTodo({ params: { id } })` (see [client.md](./client.md)).
|
|
59
|
-
|
|
60
|
-
For the full reference (`@rest`/verb decorators, `RouteContext`, `Request`, `Response`,
|
|
61
|
-
dispatch + the 404 fallback) see [routing.md](./routing.md).
|