create-acrylblends 0.0.0-stage → 0.1.1

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 (106) hide show
  1. package/README.md +61 -2
  2. package/bin/create-acrylblends.js +4 -0
  3. package/dist/index.d.ts +56 -0
  4. package/dist/index.js +274 -0
  5. package/dist/index.js.map +1 -0
  6. package/docs/framework/LICENSE +23 -0
  7. package/docs/framework/README.md +63 -0
  8. package/docs/framework/UPSTREAM.md +3 -0
  9. package/docs/framework/agents/index.md +24 -0
  10. package/docs/framework/api/acryl.md +259 -0
  11. package/docs/framework/api/context.md +527 -0
  12. package/docs/framework/api/exception.md +80 -0
  13. package/docs/framework/api/index.md +12 -0
  14. package/docs/framework/api/presets.md +65 -0
  15. package/docs/framework/api/request.md +396 -0
  16. package/docs/framework/api/routing.md +303 -0
  17. package/docs/framework/api/summary.md +140 -0
  18. package/docs/framework/concepts/benchmarks.md +26 -0
  19. package/docs/framework/concepts/developer-experience.md +5 -0
  20. package/docs/framework/concepts/middleware.md +21 -0
  21. package/docs/framework/concepts/motivation.md +14 -0
  22. package/docs/framework/concepts/routers.md +63 -0
  23. package/docs/framework/concepts/stacks.md +235 -0
  24. package/docs/framework/concepts/web-standard.md +37 -0
  25. package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
  26. package/docs/framework/cordis/concepts.md +105 -0
  27. package/docs/framework/cordis/middleware.md +8 -0
  28. package/docs/framework/cordis/migration-from-0-3.md +111 -0
  29. package/docs/framework/cordis/migration-from-0-4.md +14 -0
  30. package/docs/framework/cordis/migration-from-dsh-webserver.md +109 -0
  31. package/docs/framework/cordis/mounting.md +85 -0
  32. package/docs/framework/cordis/node-server.md +75 -0
  33. package/docs/framework/cordis/plugin-lifecycle.md +111 -0
  34. package/docs/framework/cordis/surfaces.md +71 -0
  35. package/docs/framework/cordis/transport-and-cancellation.md +57 -0
  36. package/docs/framework/cordis/validation.md +135 -0
  37. package/docs/framework/getting-started/basic.md +196 -0
  38. package/docs/framework/getting-started/bun.md +221 -0
  39. package/docs/framework/getting-started/nodejs.md +344 -0
  40. package/docs/framework/getting-started/other-runtimes.md +23 -0
  41. package/docs/framework/guides/best-practices.md +231 -0
  42. package/docs/framework/guides/create-acrylblends.md +79 -0
  43. package/docs/framework/guides/examples.md +3 -0
  44. package/docs/framework/guides/faq.md +19 -0
  45. package/docs/framework/guides/helpers.md +33 -0
  46. package/docs/framework/guides/jsx-dom.md +299 -0
  47. package/docs/framework/guides/jsx.md +440 -0
  48. package/docs/framework/guides/middleware.md +253 -0
  49. package/docs/framework/guides/rpc.md +886 -0
  50. package/docs/framework/guides/testing.md +133 -0
  51. package/docs/framework/guides/validation.md +383 -0
  52. package/docs/framework/helpers/accepts.md +60 -0
  53. package/docs/framework/helpers/adapter.md +87 -0
  54. package/docs/framework/helpers/conninfo.md +112 -0
  55. package/docs/framework/helpers/cookie.md +232 -0
  56. package/docs/framework/helpers/css.md +285 -0
  57. package/docs/framework/helpers/dev.md +67 -0
  58. package/docs/framework/helpers/factory.md +180 -0
  59. package/docs/framework/helpers/html.md +123 -0
  60. package/docs/framework/helpers/jwt.md +184 -0
  61. package/docs/framework/helpers/proxy.md +118 -0
  62. package/docs/framework/helpers/route.md +143 -0
  63. package/docs/framework/helpers/ssg.md +393 -0
  64. package/docs/framework/helpers/streaming.md +119 -0
  65. package/docs/framework/helpers/testing.md +101 -0
  66. package/docs/framework/helpers/websocket.md +215 -0
  67. package/docs/framework/middleware/builtin/basic-auth.md +165 -0
  68. package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
  69. package/docs/framework/middleware/builtin/body-limit.md +71 -0
  70. package/docs/framework/middleware/builtin/cache.md +122 -0
  71. package/docs/framework/middleware/builtin/combine.md +94 -0
  72. package/docs/framework/middleware/builtin/compress.md +59 -0
  73. package/docs/framework/middleware/builtin/context-storage.md +78 -0
  74. package/docs/framework/middleware/builtin/cors.md +135 -0
  75. package/docs/framework/middleware/builtin/csrf.md +105 -0
  76. package/docs/framework/middleware/builtin/etag.md +54 -0
  77. package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
  78. package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
  79. package/docs/framework/middleware/builtin/jwk.md +192 -0
  80. package/docs/framework/middleware/builtin/jwt.md +145 -0
  81. package/docs/framework/middleware/builtin/language.md +232 -0
  82. package/docs/framework/middleware/builtin/logger.md +74 -0
  83. package/docs/framework/middleware/builtin/loopback-only.md +31 -0
  84. package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
  85. package/docs/framework/middleware/builtin/method-override.md +80 -0
  86. package/docs/framework/middleware/builtin/mount.md +106 -0
  87. package/docs/framework/middleware/builtin/pretty-json.md +53 -0
  88. package/docs/framework/middleware/builtin/request-id.md +94 -0
  89. package/docs/framework/middleware/builtin/secure-headers.md +247 -0
  90. package/docs/framework/middleware/builtin/timeout.md +98 -0
  91. package/docs/framework/middleware/builtin/timing.md +101 -0
  92. package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
  93. package/docs/framework/middleware/third-party.md +4 -0
  94. package/docs/framework/upstream.json +9 -0
  95. package/package.json +47 -3
  96. package/templates/bun/gitignore +1 -0
  97. package/templates/bun/package.json +19 -0
  98. package/templates/bun/src/index.ts +7 -0
  99. package/templates/bun/test/app.test.ts +8 -0
  100. package/templates/bun/tsconfig.json +15 -0
  101. package/templates/nodejs/gitignore +1 -0
  102. package/templates/nodejs/package.json +22 -0
  103. package/templates/nodejs/src/app.ts +7 -0
  104. package/templates/nodejs/src/index.ts +9 -0
  105. package/templates/nodejs/test/app.test.ts +9 -0
  106. package/templates/nodejs/tsconfig.json +16 -0
@@ -0,0 +1,140 @@
1
+ # API summary
2
+
3
+ One page with the calls an ordinary app uses, each linked to the page that explains it. Imports are from `acryl-router` unless noted.
4
+
5
+ ## A plugin that serves an app
6
+
7
+ ```ts
8
+ import type { Context } from '@deepseek-ai/cordis'
9
+ import { Acryl } from 'acrylblends'
10
+ import { trimTrailingSlash } from 'acrylblends/trailing-slash'
11
+ import { schemaValidator, type StandardSchema } from 'acrylblends/schema-validator'
12
+ import { streamSSE } from 'acrylblends/streaming'
13
+ import type {} from 'acryl-state'
14
+
15
+ export const name = 'orders' // the package name and the Loader row id
16
+ export const inject = ['router', 'state'] // the plugin waits (PENDING) until both exist
17
+
18
+ interface Order { id: string, item: string }
19
+ const NewOrder: StandardSchema<{ item: string }> = {
20
+ '~standard': {
21
+ validate: value => {
22
+ const item = (value as { item?: unknown } | null)?.item
23
+ return typeof item === 'string' && item !== '' ? { value: { item } } : { issues: [{ message: 'item is required', path: ['item'] }] }
24
+ },
25
+ },
26
+ }
27
+
28
+ export function apply(ctx: Context): void {
29
+ const orders = ctx.state.cell<Order[]>('orders.list', [])
30
+ const app = new Acryl()
31
+ .use(trimTrailingSlash())
32
+ .get('/', c => c.json(orders.get()))
33
+ .post('/', schemaValidator('json', NewOrder), c => {
34
+ const order = { id: crypto.randomUUID(), ...c.req.valid('json') }
35
+ orders.update(list => [...list, order])
36
+ return c.json(order, 201)
37
+ })
38
+ .get('/events', c => streamSSE(c, async stream => {
39
+ const ended = new Promise<void>(resolve => { stream.onAbort(resolve) })
40
+ const stop = orders.subscribe(list => { void stream.writeSSE({ data: JSON.stringify(list) }) })
41
+ await stream.writeSSE({ data: JSON.stringify(orders.get()) })
42
+ try { await ended } finally { stop() }
43
+ }))
44
+ ctx.router.route('/orders', app)
45
+ ctx.inject(['routerHttp'], inner => { inner.routerHttp.mount('/orders') })
46
+ }
47
+ ```
48
+
49
+ ## `Acryl` ([Routing](../api/routing.md))
50
+
51
+ | Call | Does |
52
+ | --- | --- |
53
+ | `new Acryl()` | an application: a route table plus middleware |
54
+ | `.get / .post / .put / .patch / .delete / .options (path, ...handlers)` | a route; handlers before the last are middleware for that route |
55
+ | `.all(path, ...)`, `.on(methods, path, ...)` | any method, or a list of methods |
56
+ | `.use([path], ...middleware)` | middleware for every route below `path` (all routes without it), registered before the routes it wraps |
57
+ | `.route(path, otherApp)` | copy another app's routes below `path` |
58
+ | `.basePath(path)` | the same app with a prefix on every route registered through it |
59
+ | `.onError((err, c) => ...)`, `.notFound(c => ...)` | the app's error and not-found answers |
60
+ | `.request(pathOrRequest, init?)` | call the app in process (tests) |
61
+ | `.fetch(request)` | the app as a fetch handler |
62
+
63
+ Paths: `/posts/:id` (parameter), `/posts/:id?` (optional), `/files/*` (wildcard), `/:name{[a-z]+}` (pattern). `/a` and `/a/` are different
64
+ paths: see [Trailing slashes](../api/routing.md#trailing-slashes).
65
+
66
+ ## The handler context `c` ([Context](../api/context.md))
67
+
68
+ | Call | Does |
69
+ | --- | --- |
70
+ | `c.json(value, status?, headers?)` | a JSON answer |
71
+ | `c.text(text, status?)`, `c.html(html, status?)` | a text or HTML answer |
72
+ | `c.body(data, status?, headers?)` | a raw answer (`c.body(null, 204)`) |
73
+ | `c.redirect(location, status = 302)` | a redirect |
74
+ | `c.notFound()` | the app's not-found answer |
75
+ | `c.header(name, value)`, `c.status(code)` | set a header or the status (also after `await next()` in middleware) |
76
+ | `c.set(key, value)`, `c.get(key)`, `c.var.key` | values for this request only, passed from middleware to handlers |
77
+ | `c.req.raw.signal` | aborts when the caller leaves, the serving plugin unloads, or the router is disposed |
78
+ | `c.executionCtx.waitUntil(promise)` | keep the request in flight until background work settles |
79
+ | `connectionOf(c)` / `getConnInfo(c)` | the caller's address and the local port, when a transport supplied them |
80
+ | `throw new HTTPException(status, { message })` | stop with that status from anywhere in the chain |
81
+
82
+ ## The request `c.req` ([Request](../api/request.md))
83
+
84
+ | Call | Gives |
85
+ | --- | --- |
86
+ | `c.req.param('id')`, `c.req.param()` | route parameters |
87
+ | `c.req.query('page')`, `c.req.query()`, `c.req.queries('tag')` | query string values (`queries` for repeated keys) |
88
+ | `c.req.header('x-name')`, `c.req.header()` | request headers |
89
+ | `await c.req.json()`, `c.req.text()`, `c.req.formData()`, `c.req.parseBody()`, `c.req.arrayBuffer()`, `c.req.blob()` | the body, once |
90
+ | `c.req.valid(target)` | the value a validator checked, typed by its schema |
91
+ | `c.req.method`, `c.req.path`, `c.req.url`, `c.req.raw` | the method, path, full URL and the Web `Request` |
92
+
93
+ ## Validation ([Validation](../cordis/validation.md))
94
+
95
+ ```ts
96
+ import { schemaValidator } from 'acrylblends/schema-validator'
97
+ import { validator } from 'acrylblends/validator'
98
+ app.post('/', schemaValidator('json', Schema), c => c.json(c.req.valid('json')))
99
+ ```
100
+
101
+ - Targets: `json`, `form`, `query`, `param`, `header`, `cookie`. `Schema` is any Standard Schema (Schemastery, Zod, Valibot, or an
102
+ object with `'~standard': { validate }`).
103
+ - A failure is 400 `{ error: 'invalid <target>', issues: [{ message, path }] }`; `path` is dotted, and `""` when the issue has none.
104
+ - The `json` target reads the body only with a JSON `Content-Type`; otherwise the schema sees `{}`.
105
+ - `validator(target, (value, c) => valueOrResponse)` is the same middleware with a function instead of a schema.
106
+ - Put `bodyLimit({ maxSize })` from `acrylblends/body-limit` in front on routes open to untrusted callers.
107
+
108
+ ## Streaming ([Helpers](../guides/helpers.md))
109
+
110
+ | Call | Does |
111
+ | --- | --- |
112
+ | `streamSSE(c, async stream => { ... })` | a server-sent event answer |
113
+ | `stream.writeSSE({ data, event?, id?, retry? })` | send one event (`data` is a string) |
114
+ | `stream.write(text)`, `stream.writeln(text)` | raw bytes or a line (a comment line such as `: keep-alive\n\n` keeps a proxy from closing the stream) |
115
+ | `stream.onAbort(fn)`, `stream.aborted` | the client left or the plugin unloaded: stop work and release subscriptions |
116
+ | `stream(c, ...)`, `streamText(c, ...)` | a byte stream or a text stream instead of events |
117
+
118
+ Do not start timers inside a handler for keep-alives; let the plugin own one timer and write to the open streams.
119
+
120
+ ## Cordis services
121
+
122
+ | Call | Service | Does |
123
+ | --- | --- | --- |
124
+ | `ctx.router.route(path, app)` | `router` (`acryl-router`) | serve `app` below `path` for as long as the calling plugin is loaded; returns a function that removes it sooner |
125
+ | `ctx.router.request(path, init?)`, `ctx.router.fetch(request)` | `router` | call the mounted routes in process (terminal UI, tests, other plugins) |
126
+ | `ctx.router.onError(handler)`, `ctx.router.notFound(handler)` | `router` | router-wide error and not-found answers, owned by the caller |
127
+ | `ctx.routerHttp.mount(prefix)` | `routerHttp` (`acryl-router-http`) | inside the ACRYL Host: serve a router prefix on the Host's web server (Web and Desktop). Use it through `ctx.inject(['routerHttp'], ...)` so the app also runs where there is no Host |
128
+ | `ctx.nodeServer.url`, `ctx.nodeServer.port` | `nodeServer` (`acryl-node-server`) | without the Host: one Node server for the whole router; options `hostname` (`127.0.0.1`), `port` (`0`), `scan` (`20`). See [Node server](../cordis/node-server.md) |
129
+ | `ctx.state.cell(key, initial)` | `state` (`acryl-state`) | a writable cell owned by the plugin: `get()`, `set(value)`, `update(fn)`, `subscribe(fn)` returning an unsubscribe |
130
+ | `ctx.state.derive(key, [cells], fn)` | `state` | a read-only cell computed from others |
131
+ | `GET /api/state/:key`, `GET /api/state/:key/events` | `state` | each cell over the router, read-only; `useCell(cell)` from `acryl-state/react` in a React page |
132
+
133
+ Everything a plugin registers this way (mounts, cells, handlers) goes away when the plugin unloads, and comes back once when it reloads:
134
+ see [Plugin lifecycle and disposal](../cordis/plugin-lifecycle.md).
135
+
136
+ ## Middleware ([Middleware](../concepts/middleware.md))
137
+
138
+ Each from its own subpath (`acrylblends/body-limit`, `acrylblends/cors`, ...): `bodyLimit`, `cors`, `csrf`, `secureHeaders`, `etag`, `compress`, `logger`, `requestId`, `timeout`,
139
+ `bearerAuth`, `basicAuth`, `jwt`, `jwk`, `ipRestriction`, `methodNotAllowed`, `trimTrailingSlash`, `appendTrailingSlash`, `some` /
140
+ `every` / `except`, and ACRYL's `loopbackOnly` for private loopback routes.
@@ -0,0 +1,26 @@
1
+ # Benchmarks
2
+
3
+ > **Not yet measured.** The routers in the framework are designed for different situations (see [Routers](routers.md)), but benchmark results for Acryl itself have not been
4
+ > produced yet, and this page does not quote anyone else's numbers as ours. When Acryl has its own reproducible benchmark suite, its scripts and results will be here.
5
+
6
+ To compare routers on your own workload today, register your routes on an `Acryl` app with each router and time `app.request()` for the paths you care about:
7
+
8
+ ```ts twoslash
9
+ import { Acryl } from 'acrylblends'
10
+ import { RegExpRouter } from 'acrylblends/router/reg-exp-router'
11
+ import { TrieRouter } from 'acrylblends/router/trie-router'
12
+
13
+ const routes = ['/', '/users/:id', '/posts/:id/comments']
14
+
15
+ const apps = {
16
+ RegExpRouter: new Acryl({ router: new RegExpRouter() }),
17
+ TrieRouter: new Acryl({ router: new TrieRouter() }),
18
+ }
19
+
20
+ for (const [name, app] of Object.entries(apps)) {
21
+ for (const path of routes) app.get(path, (c) => c.text('ok'))
22
+ const start = performance.now()
23
+ for (let i = 0; i < 100_000; i++) await app.request('/users/42')
24
+ console.log(name, Math.round(performance.now() - start), 'ms')
25
+ }
26
+ ```
@@ -0,0 +1,5 @@
1
+ # Developer Experience
2
+
3
+ To create a great application, we need great development experience.
4
+ Fortunately, we can write applications for Node.js and Bun in TypeScript without having the need to transpile it to JavaScript.
5
+ Acryl is written in TypeScript and can make applications type-safe.
@@ -0,0 +1,21 @@
1
+ # Middleware
2
+
3
+ We call the primitive that returns `Response` as "Handler".
4
+ "Middleware" is executed before and after the Handler and handles the `Request` and `Response`.
5
+ It's like an onion structure.
6
+
7
+ For example, we can write the middleware to add the "X-Response-Time" header as follows.
8
+
9
+ ```ts twoslash
10
+ import { Acryl } from 'acrylblends'
11
+ const app = new Acryl()
12
+ // ---cut---
13
+ app.use(async (c, next) => {
14
+ const start = performance.now()
15
+ await next()
16
+ const end = performance.now()
17
+ c.res.headers.set('X-Response-Time', `${end - start}`)
18
+ })
19
+ ```
20
+
21
+ With this simple method, we can write our own custom middleware and we can use the built-in or third party middleware.
@@ -0,0 +1,14 @@
1
+ # Philosophy
2
+
3
+ ## Motivation
4
+
5
+ A web application should not be written twice because it runs in two places. Acryl builds on the Web Standard APIs (`Request`, `Response`, `fetch`, `URL`, streams), so a
6
+ handler written once runs on Node.js (and experimentally Bun), in a test with no socket, and inside a Cordis app.
7
+
8
+ Three ideas follow from that:
9
+
10
+ - **The handler is a function over Web Standards.** There is no runtime object to import, so there is nothing to replace when the runtime changes. See
11
+ [Web Standards](web-standard.md).
12
+ - **A plugin owns what it mounts.** Inside a Cordis app an application is mounted by a plugin and disappears when that plugin unloads, which is what lets an agent add,
13
+ update and remove features while the app runs. See [Plugin lifecycle](../cordis/plugin-lifecycle.md).
14
+ - **The documentation travels with the app.** A generated app contains these pages, so a person or a coding agent working in the folder reads the API from the folder.
@@ -0,0 +1,63 @@
1
+ # Routers
2
+
3
+ The router decides which handler answers a request. Acryl has five routers behind one API, and an `Acryl` app uses `SmartRouter` unless you choose another.
4
+
5
+ | Router | Import | Built for |
6
+ | --- | --- | --- |
7
+ | `SmartRouter` | `acrylblends/router/smart-router` | the default: it picks from the routers it is given, by the routes registered |
8
+ | `RegExpRouter` | `acrylblends/router/reg-exp-router` | matching all routes with one regular expression built before dispatch |
9
+ | `TrieRouter` | `acrylblends/router/trie-router` | all route patterns, with a trie tree and no linear loop |
10
+ | `LinearRouter` | `acrylblends/router/linear-router` | applications that register routes at every start, because registration compiles nothing |
11
+ | `PatternRouter` | `acrylblends/router/pattern-router` | the smallest implementation |
12
+
13
+ ## RegExpRouter
14
+
15
+ **RegExpRouter** turns the route patterns into one large regular expression and gets the result with one match. It is not the kind of "RegExp router" that tries each
16
+ route's expression in a loop, so matching does not repeat for every route.
17
+
18
+ It does not support every routing pattern, so it is normally combined with a router that does, through `SmartRouter`.
19
+
20
+ ## TrieRouter
21
+
22
+ **TrieRouter** uses a trie tree, and like RegExpRouter it does not loop over the routes. It supports all patterns.
23
+
24
+ ## SmartRouter
25
+
26
+ **SmartRouter** is for using several routers. It infers, from the routes registered, which of the routers it was given can serve them, and keeps using that one. An `Acryl`
27
+ app defaults to:
28
+
29
+ ```ts twoslash
30
+ import { Acryl } from 'acrylblends'
31
+ import { RegExpRouter } from 'acrylblends/router/reg-exp-router'
32
+ import { SmartRouter } from 'acrylblends/router/smart-router'
33
+ import { TrieRouter } from 'acrylblends/router/trie-router'
34
+
35
+ const app = new Acryl({
36
+ router: new SmartRouter({ routers: [new RegExpRouter(), new TrieRouter()] }),
37
+ })
38
+ ```
39
+
40
+ ## LinearRouter
41
+
42
+ **LinearRouter** registers a route without compiling anything, by keeping the routes in a list and matching them in order. Registration is cheap and matching is linear in
43
+ the number of routes, so it suits an environment that builds the application at every start and serves few requests.
44
+
45
+ ```ts twoslash
46
+ import { Acryl } from 'acrylblends'
47
+ import { LinearRouter } from 'acrylblends/router/linear-router'
48
+
49
+ const app = new Acryl({ router: new LinearRouter() })
50
+ ```
51
+
52
+ ## PatternRouter
53
+
54
+ **PatternRouter** is the smallest of the routers: it adds a pattern and matches it, and nothing else. Choose it when size matters more than anything else.
55
+
56
+ ```ts twoslash
57
+ import { Acryl } from 'acrylblends'
58
+ import { PatternRouter } from 'acrylblends/router/pattern-router'
59
+
60
+ const app = new Acryl({ router: new PatternRouter() })
61
+ ```
62
+
63
+ How the routers compare on your workload is for you to measure; see [Benchmarks](benchmarks.md).
@@ -0,0 +1,235 @@
1
+ # Acryl Stacks
2
+
3
+ Acryl makes easy things easy and hard things easy.
4
+ It is suitable for not just only returning JSON, but it's also great for building the full-stack application including REST API servers and the client.
5
+
6
+ ## RPC
7
+
8
+ Acryl's RPC feature allows you to share API specs with little change to your code.
9
+ The client generated by `hc` will read the spec and access the endpoint type-safety.
10
+
11
+ The following libraries make it possible.
12
+
13
+ - Acryl - API Server
14
+ - [Zod](https://zod.dev) - Validator
15
+ - Zod Validator Middleware
16
+ - `hc` - HTTP Client
17
+
18
+ We can call the set of these components the **Acryl Stack**.
19
+ Now let's create an API server and a client with it.
20
+
21
+ ## Writing API
22
+
23
+ First, write an endpoint that receives a GET request and returns JSON.
24
+
25
+ ```ts twoslash
26
+ import { Acryl } from 'acrylblends'
27
+
28
+ const app = new Acryl()
29
+
30
+ app.get('/hello', (c) => {
31
+ return c.json({
32
+ message: `Hello!`,
33
+ })
34
+ })
35
+ ```
36
+
37
+ ## Validation with Zod
38
+
39
+ Validate with Zod to receive the value of the query parameter.
40
+
41
+ ```ts
42
+ import { schemaValidator } from 'acrylblends/schema-validator'
43
+ import * as z from 'zod'
44
+
45
+ app.get(
46
+ '/hello',
47
+ schemaValidator(
48
+ 'query',
49
+ z.object({
50
+ name: z.string(),
51
+ })
52
+ ),
53
+ (c) => {
54
+ const { name } = c.req.valid('query')
55
+ return c.json({
56
+ message: `Hello! ${name}`,
57
+ })
58
+ }
59
+ )
60
+ ```
61
+
62
+ ## Sharing the Types
63
+
64
+ To emit an endpoint specification, export its type.
65
+
66
+ > **Warning.**
67
+ > For the RPC to infer routes correctly, all included methods must be chained, and the endpoint or app type must be inferred from a declared variable. For more, see [Best Practices for RPC](../guides/best-practices.md#if-you-want-to-use-rpc-features).
68
+ >
69
+
70
+ ```ts
71
+ const route = app.get(
72
+ '/hello',
73
+ schemaValidator(
74
+ 'query',
75
+ z.object({
76
+ name: z.string(),
77
+ })
78
+ ),
79
+ (c) => {
80
+ const { name } = c.req.valid('query')
81
+ return c.json({
82
+ message: `Hello! ${name}`,
83
+ })
84
+ }
85
+ )
86
+
87
+ export type AppType = typeof route
88
+ ```
89
+
90
+ ## Client
91
+
92
+ Next, The client-side implementation.
93
+ Create a client object by passing the `AppType` type to `hc` as generics.
94
+ Then, magically, completion works and the endpoint path and request type are suggested.
95
+
96
+ ```ts
97
+ import type { AppType } from './server'
98
+ import { hc } from 'acrylblends/client'
99
+
100
+ const client = hc<AppType>('/api')
101
+ const res = await client.hello.$get({
102
+ query: {
103
+ name: 'Acryl',
104
+ },
105
+ })
106
+ ```
107
+
108
+ The `Response` is compatible with the fetch API, but the data that can be retrieved with `json()` has a type.
109
+
110
+ ```ts
111
+ const data = await res.json()
112
+ console.log(`${data.message}`)
113
+ ```
114
+
115
+ Sharing API specifications means that you can be aware of server-side changes.
116
+
117
+ ## With React
118
+
119
+ You can create an application with a React front-end using the typed client.
120
+
121
+ The API server.
122
+
123
+ ```ts
124
+ // src/index.ts
125
+ import { Acryl } from 'acrylblends'
126
+ import * as z from 'zod'
127
+ import { schemaValidator } from 'acrylblends/schema-validator'
128
+
129
+ const schema = z.object({
130
+ id: z.string(),
131
+ title: z.string(),
132
+ })
133
+
134
+ type Todo = z.infer<typeof schema>
135
+
136
+ const todos: Todo[] = []
137
+
138
+ const api = new Acryl()
139
+ .post('/todo', schemaValidator('form', schema), (c) => {
140
+ const todo = c.req.valid('form')
141
+ todos.push(todo)
142
+ return c.json({
143
+ message: 'created!',
144
+ })
145
+ })
146
+ .get('/todo', (c) => {
147
+ return c.json({
148
+ todos,
149
+ })
150
+ })
151
+
152
+ export type AppType = typeof api
153
+
154
+ const app = new Acryl()
155
+ app.route('/api', api)
156
+
157
+ export default app
158
+ ```
159
+
160
+ The client with React and React Query.
161
+
162
+ ```tsx
163
+ // src/App.tsx
164
+ import {
165
+ useQuery,
166
+ useMutation,
167
+ QueryClient,
168
+ QueryClientProvider,
169
+ } from '@tanstack/react-query'
170
+ import type { AppType } from '../functions/api/[[route]]'
171
+ import { hc, InferResponseType, InferRequestType } from 'acrylblends/client'
172
+
173
+ const queryClient = new QueryClient()
174
+ const client = hc<AppType>('/api')
175
+
176
+ export default function App() {
177
+ return (
178
+ <QueryClientProvider client={queryClient}>
179
+ <Todos />
180
+ </QueryClientProvider>
181
+ )
182
+ }
183
+
184
+ const Todos = () => {
185
+ const query = useQuery({
186
+ queryKey: ['todos'],
187
+ queryFn: async () => {
188
+ const res = await client.todo.$get()
189
+ return await res.json()
190
+ },
191
+ })
192
+
193
+ const $post = client.todo.$post
194
+
195
+ const mutation = useMutation<
196
+ InferResponseType<typeof $post>,
197
+ Error,
198
+ InferRequestType<typeof $post>['form']
199
+ >({
200
+ mutationFn: async (todo) => {
201
+ const res = await $post({
202
+ form: todo,
203
+ })
204
+ return await res.json()
205
+ },
206
+ onSuccess: async () => {
207
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
208
+ },
209
+ onError: (error) => {
210
+ console.log(error)
211
+ },
212
+ })
213
+
214
+ return (
215
+ <div>
216
+ <button
217
+ onClick={() => {
218
+ mutation.mutate({
219
+ id: Date.now().toString(),
220
+ title: 'Write code',
221
+ })
222
+ }}
223
+ >
224
+ Add Todo
225
+ </button>
226
+
227
+ <ul>
228
+ {query.data?.todos.map((todo) => (
229
+ <li key={todo.id}>{todo.title}</li>
230
+ ))}
231
+ </ul>
232
+ </div>
233
+ )
234
+ }
235
+ ```
@@ -0,0 +1,37 @@
1
+ # Web Standards
2
+
3
+ Acryl uses only **Web Standards** like Fetch.
4
+ They were originally used in the `fetch` function and consist of basic objects that handle HTTP requests and responses.
5
+ In addition to `Requests` and `Responses`, there are `URL`, `URLSearchParam`, `Headers` and others.
6
+
7
+ Cloudflare Workers, Deno, and Bun also are built upon Web Standards.
8
+ For example, a server that returns "Hello World" could be written as below. This could run on Cloudflare Workers and Bun.
9
+
10
+ ```ts twoslash
11
+ export default {
12
+ async fetch() {
13
+ return new Response('Hello World')
14
+ },
15
+ }
16
+ ```
17
+
18
+ Acryl uses only Web Standards, which means that Acryl can run on any runtime that supports them.
19
+ In addition, we have a Node.js adapter. Acryl runs on these runtimes:
20
+
21
+ - Cloudflare Workers (`workerd`)
22
+ - Deno
23
+ - Bun
24
+ - Fastly Compute
25
+ - AWS Lambda
26
+ - Node.js
27
+ - Vercel (edge-light)
28
+ - WebAssembly (w/ [WebAssembly System Interface (WASI)][wasi] via [`wasi:http`][wasi-http])
29
+
30
+ It also works on Netlify and other platforms.
31
+ The same code runs on all platforms.
32
+
33
+ Cloudflare Workers, Deno, Shopify, and others launched [WinterCG](https://wintercg.org) to discuss the possibility of using the Web Standards to enable "web-interoperability".
34
+ Acryl will follow their steps and go for **the Standard of the Web Standards**.
35
+
36
+ [wasi]: https://github.com/WebAssembly/wasi
37
+ [wasi-http]: https://github.com/WebAssembly/wasi-http