create-acrylblends 0.0.0-stage → 0.1.0
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/bin/create-acrylblends.js +4 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -0
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/agents/index.md +24 -0
- package/docs/framework/api/acryl.md +259 -0
- package/docs/framework/api/context.md +527 -0
- package/docs/framework/api/exception.md +80 -0
- package/docs/framework/api/index.md +12 -0
- package/docs/framework/api/presets.md +65 -0
- package/docs/framework/api/request.md +396 -0
- package/docs/framework/api/routing.md +303 -0
- package/docs/framework/api/summary.md +140 -0
- package/docs/framework/concepts/benchmarks.md +26 -0
- package/docs/framework/concepts/developer-experience.md +5 -0
- package/docs/framework/concepts/middleware.md +21 -0
- package/docs/framework/concepts/motivation.md +14 -0
- package/docs/framework/concepts/routers.md +63 -0
- package/docs/framework/concepts/stacks.md +235 -0
- package/docs/framework/concepts/web-standard.md +37 -0
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
- package/docs/framework/cordis/concepts.md +105 -0
- package/docs/framework/cordis/middleware.md +8 -0
- package/docs/framework/cordis/migration-from-0-3.md +111 -0
- package/docs/framework/cordis/migration-from-0-4.md +14 -0
- package/docs/framework/cordis/migration-from-dsh-webserver.md +103 -0
- package/docs/framework/cordis/mounting.md +85 -0
- package/docs/framework/cordis/node-server.md +75 -0
- package/docs/framework/cordis/plugin-lifecycle.md +111 -0
- package/docs/framework/cordis/surfaces.md +71 -0
- package/docs/framework/cordis/transport-and-cancellation.md +57 -0
- package/docs/framework/cordis/validation.md +135 -0
- package/docs/framework/getting-started/basic.md +196 -0
- package/docs/framework/getting-started/bun.md +221 -0
- package/docs/framework/getting-started/nodejs.md +344 -0
- package/docs/framework/getting-started/other-runtimes.md +23 -0
- package/docs/framework/guides/best-practices.md +231 -0
- package/docs/framework/guides/create-acrylblends.md +79 -0
- package/docs/framework/guides/examples.md +3 -0
- package/docs/framework/guides/faq.md +19 -0
- package/docs/framework/guides/helpers.md +33 -0
- package/docs/framework/guides/jsx-dom.md +299 -0
- package/docs/framework/guides/jsx.md +440 -0
- package/docs/framework/guides/middleware.md +253 -0
- package/docs/framework/guides/rpc.md +886 -0
- package/docs/framework/guides/testing.md +133 -0
- package/docs/framework/guides/validation.md +383 -0
- package/docs/framework/helpers/accepts.md +60 -0
- package/docs/framework/helpers/adapter.md +87 -0
- package/docs/framework/helpers/conninfo.md +112 -0
- package/docs/framework/helpers/cookie.md +232 -0
- package/docs/framework/helpers/css.md +285 -0
- package/docs/framework/helpers/dev.md +67 -0
- package/docs/framework/helpers/factory.md +180 -0
- package/docs/framework/helpers/html.md +123 -0
- package/docs/framework/helpers/jwt.md +184 -0
- package/docs/framework/helpers/proxy.md +118 -0
- package/docs/framework/helpers/route.md +143 -0
- package/docs/framework/helpers/ssg.md +393 -0
- package/docs/framework/helpers/streaming.md +119 -0
- package/docs/framework/helpers/testing.md +101 -0
- package/docs/framework/helpers/websocket.md +215 -0
- package/docs/framework/middleware/builtin/basic-auth.md +165 -0
- package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
- package/docs/framework/middleware/builtin/body-limit.md +71 -0
- package/docs/framework/middleware/builtin/cache.md +122 -0
- package/docs/framework/middleware/builtin/combine.md +94 -0
- package/docs/framework/middleware/builtin/compress.md +59 -0
- package/docs/framework/middleware/builtin/context-storage.md +78 -0
- package/docs/framework/middleware/builtin/cors.md +135 -0
- package/docs/framework/middleware/builtin/csrf.md +105 -0
- package/docs/framework/middleware/builtin/etag.md +54 -0
- package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
- package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
- package/docs/framework/middleware/builtin/jwk.md +192 -0
- package/docs/framework/middleware/builtin/jwt.md +145 -0
- package/docs/framework/middleware/builtin/language.md +232 -0
- package/docs/framework/middleware/builtin/logger.md +74 -0
- package/docs/framework/middleware/builtin/loopback-only.md +31 -0
- package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
- package/docs/framework/middleware/builtin/method-override.md +80 -0
- package/docs/framework/middleware/builtin/mount.md +106 -0
- package/docs/framework/middleware/builtin/pretty-json.md +53 -0
- package/docs/framework/middleware/builtin/request-id.md +94 -0
- package/docs/framework/middleware/builtin/secure-headers.md +247 -0
- package/docs/framework/middleware/builtin/timeout.md +98 -0
- package/docs/framework/middleware/builtin/timing.md +101 -0
- package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
- package/docs/framework/middleware/third-party.md +4 -0
- package/docs/framework/upstream.json +9 -0
- package/package.json +47 -3
- package/templates/bun/gitignore +1 -0
- package/templates/bun/package.json +19 -0
- package/templates/bun/src/index.ts +7 -0
- package/templates/bun/test/app.test.ts +8 -0
- package/templates/bun/tsconfig.json +15 -0
- package/templates/nodejs/gitignore +1 -0
- package/templates/nodejs/package.json +22 -0
- package/templates/nodejs/src/app.ts +7 -0
- package/templates/nodejs/src/index.ts +9 -0
- package/templates/nodejs/test/app.test.ts +9 -0
- package/templates/nodejs/tsconfig.json +16 -0
- package/README.md +0 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
[Vitest]: https://vitest.dev/
|
|
4
|
+
|
|
5
|
+
Testing is important.
|
|
6
|
+
In actuality, it is easy to test Acryl's applications.
|
|
7
|
+
The way to create a test environment differs from each runtime, but the basic steps are the same.
|
|
8
|
+
In this section, let's test with [Vitest] on Node.js. The same tests run on Bun with its own test runner, because an app is a function from `Request` to `Response`.
|
|
9
|
+
|
|
10
|
+
## Request and Response
|
|
11
|
+
|
|
12
|
+
All you need to do is create a Request and pass it to the Acryl application to validate the Response. You can then use the useful `app.request` method.
|
|
13
|
+
|
|
14
|
+
> **Tip.** For a typed test client see the [testing helper](../helpers/testing.md).
|
|
15
|
+
|
|
16
|
+
For example, consider an application that provides the following REST API.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
app.get('/posts', (c) => {
|
|
20
|
+
return c.text('Many posts')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
app.post('/posts', (c) => {
|
|
24
|
+
return c.json(
|
|
25
|
+
{
|
|
26
|
+
message: 'Created',
|
|
27
|
+
},
|
|
28
|
+
201,
|
|
29
|
+
{
|
|
30
|
+
'X-Custom': 'Thank you',
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Make a request to `GET /posts` and test the response.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
describe('Example', () => {
|
|
40
|
+
test('GET /posts', async () => {
|
|
41
|
+
const res = await app.request('/posts')
|
|
42
|
+
expect(res.status).toBe(200)
|
|
43
|
+
expect(await res.text()).toBe('Many posts')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
To make a request to `POST /posts`, do the following.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
test('POST /posts', async () => {
|
|
52
|
+
const res = await app.request('/posts', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
})
|
|
55
|
+
expect(res.status).toBe(201)
|
|
56
|
+
expect(res.headers.get('X-Custom')).toBe('Thank you')
|
|
57
|
+
expect(await res.json()).toEqual({
|
|
58
|
+
message: 'Created',
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To make a request to `POST /posts` with `JSON` data, do the following.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
test('POST /posts', async () => {
|
|
67
|
+
const res = await app.request('/posts', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
body: JSON.stringify({ message: 'hello acrylblends' }),
|
|
70
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
71
|
+
})
|
|
72
|
+
expect(res.status).toBe(201)
|
|
73
|
+
expect(res.headers.get('X-Custom')).toBe('Thank you')
|
|
74
|
+
expect(await res.json()).toEqual({
|
|
75
|
+
message: 'Created',
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
To make a request to `POST /posts` with `multipart/form-data` data, do the following.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
test('POST /posts', async () => {
|
|
84
|
+
const formData = new FormData()
|
|
85
|
+
formData.append('message', 'hello')
|
|
86
|
+
const res = await app.request('/posts', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
body: formData,
|
|
89
|
+
})
|
|
90
|
+
expect(res.status).toBe(201)
|
|
91
|
+
expect(res.headers.get('X-Custom')).toBe('Thank you')
|
|
92
|
+
expect(await res.json()).toEqual({
|
|
93
|
+
message: 'Created',
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
You can also pass an instance of the Request class.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
test('POST /posts', async () => {
|
|
102
|
+
const req = new Request('http://localhost/posts', {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
})
|
|
105
|
+
const res = await app.request(req)
|
|
106
|
+
expect(res.status).toBe(201)
|
|
107
|
+
expect(res.headers.get('X-Custom')).toBe('Thank you')
|
|
108
|
+
expect(await res.json()).toEqual({
|
|
109
|
+
message: 'Created',
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
In this way, you can test it as like an End-to-End.
|
|
115
|
+
|
|
116
|
+
## Env
|
|
117
|
+
|
|
118
|
+
To set `c.env` for testing, you can pass it as the 3rd parameter to `app.request`. This is useful for mocking values like platform bindings:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const MOCK_ENV = {
|
|
122
|
+
API_HOST: 'example.com',
|
|
123
|
+
DB: {
|
|
124
|
+
prepare: () => {
|
|
125
|
+
/* mocked D1 */
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
test('GET /posts', async () => {
|
|
131
|
+
const res = await app.request('/posts', {}, MOCK_ENV)
|
|
132
|
+
})
|
|
133
|
+
```
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# Validation
|
|
2
|
+
|
|
3
|
+
Acryl provides only a very thin Validator.
|
|
4
|
+
However, it can be powerful when combined with a third-party Validator.
|
|
5
|
+
In addition, the RPC feature allows you to share API specifications with your clients through types.
|
|
6
|
+
|
|
7
|
+
## Manual validator
|
|
8
|
+
|
|
9
|
+
First, introduce a way to validate incoming values without using the third-party Validator.
|
|
10
|
+
|
|
11
|
+
Import `validator` from `acrylblends/validator`.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { validator } from 'acrylblends/validator'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
To validate form data, specify `form` as the first argument and a callback as the second argument.
|
|
18
|
+
In the callback, validates the value and return the validated values at the end.
|
|
19
|
+
The `validator` can be used as middleware.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
app.post(
|
|
23
|
+
'/posts',
|
|
24
|
+
validator('form', (value, c) => {
|
|
25
|
+
const body = value['body']
|
|
26
|
+
if (!body || typeof body !== 'string') {
|
|
27
|
+
return c.text('Invalid!', 400)
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
body: body,
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
//...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Within the handler, you can get the validated value with `c.req.valid('form')`.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
, (c) => {
|
|
40
|
+
const { body } = c.req.valid('form')
|
|
41
|
+
// ... do something
|
|
42
|
+
return c.json(
|
|
43
|
+
{
|
|
44
|
+
message: 'Created!',
|
|
45
|
+
},
|
|
46
|
+
201
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Validation targets include `json`, `query`, `header`, `param` and `cookie` in addition to `form`.
|
|
52
|
+
|
|
53
|
+
**Warning.**
|
|
54
|
+
|
|
55
|
+
When you validate `json` or `form`, the request _must_ contain a matching `content-type` header (e.g. `Content-Type: application/json` for `json`). Otherwise, the request body will not be parsed and you will receive an empty object (`{}`) as value in the callback.
|
|
56
|
+
|
|
57
|
+
It is important to set the `content-type` header when testing using
|
|
58
|
+
[`app.request()`](../api/request.md).
|
|
59
|
+
|
|
60
|
+
Given an application like this.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const app = new Acryl()
|
|
64
|
+
app.post(
|
|
65
|
+
'/testing',
|
|
66
|
+
validator('json', (value, c) => {
|
|
67
|
+
// pass-through validator
|
|
68
|
+
return value
|
|
69
|
+
}),
|
|
70
|
+
(c) => {
|
|
71
|
+
const body = c.req.valid('json')
|
|
72
|
+
return c.json(body)
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Your tests can be written like this.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// ❌ this will not work
|
|
81
|
+
const res = await app.request('/testing', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
body: JSON.stringify({ key: 'value' }),
|
|
84
|
+
})
|
|
85
|
+
const data = await res.json()
|
|
86
|
+
console.log(data) // {}
|
|
87
|
+
|
|
88
|
+
// ✅ this will work
|
|
89
|
+
const res = await app.request('/testing', {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
body: JSON.stringify({ key: 'value' }),
|
|
92
|
+
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
93
|
+
})
|
|
94
|
+
const data = await res.json()
|
|
95
|
+
console.log(data) // { key: 'value' }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Warning.**
|
|
99
|
+
|
|
100
|
+
When you validate `header`, you need to use **lowercase** name as the key.
|
|
101
|
+
|
|
102
|
+
If you want to validate the `Idempotency-Key` header, you need to use `idempotency-key` as the key.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// ❌ this will not work
|
|
106
|
+
app.post(
|
|
107
|
+
'/api',
|
|
108
|
+
validator('header', (value, c) => {
|
|
109
|
+
// idempotencyKey is always undefined
|
|
110
|
+
// so this middleware always return 400 as not expected
|
|
111
|
+
const idempotencyKey = value['Idempotency-Key']
|
|
112
|
+
|
|
113
|
+
if (idempotencyKey == undefined || idempotencyKey === '') {
|
|
114
|
+
throw new HTTPException(400, {
|
|
115
|
+
message: 'Idempotency-Key is required',
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return { idempotencyKey }
|
|
119
|
+
}),
|
|
120
|
+
(c) => {
|
|
121
|
+
const { idempotencyKey } = c.req.valid('header')
|
|
122
|
+
// ...
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
// ✅ this will work
|
|
127
|
+
app.post(
|
|
128
|
+
'/api',
|
|
129
|
+
validator('header', (value, c) => {
|
|
130
|
+
// can retrieve the value of the header as expected
|
|
131
|
+
const idempotencyKey = value['idempotency-key']
|
|
132
|
+
|
|
133
|
+
if (idempotencyKey == undefined || idempotencyKey === '') {
|
|
134
|
+
throw new HTTPException(400, {
|
|
135
|
+
message: 'Idempotency-Key is required',
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
return { idempotencyKey }
|
|
139
|
+
}),
|
|
140
|
+
(c) => {
|
|
141
|
+
const { idempotencyKey } = c.req.valid('header')
|
|
142
|
+
// ...
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Multiple validators
|
|
148
|
+
|
|
149
|
+
You can also include multiple validators to validate different parts of request:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
app.post(
|
|
153
|
+
'/posts/:id',
|
|
154
|
+
validator('param', ...),
|
|
155
|
+
validator('query', ...),
|
|
156
|
+
validator('json', ...),
|
|
157
|
+
(c) => {
|
|
158
|
+
//...
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## With Zod
|
|
164
|
+
|
|
165
|
+
You can use [Zod](https://zod.dev), one of third-party validators.
|
|
166
|
+
We recommend using a third-party validator.
|
|
167
|
+
|
|
168
|
+
Install from the Npm registry.
|
|
169
|
+
|
|
170
|
+
**npm**
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
npm i zod
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**yarn**
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
yarn add zod
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**pnpm**
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
pnpm add zod
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**bun**
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
bun add zod
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Import `z` from `zod`.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import * as z from 'zod'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Write your schema.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const schema = z.object({
|
|
204
|
+
body: z.string(),
|
|
205
|
+
})
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
You can use the schema in the callback function for validation and return the validated value.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const route = app.post(
|
|
212
|
+
'/posts',
|
|
213
|
+
validator('form', (value, c) => {
|
|
214
|
+
const parsed = schema.safeParse(value)
|
|
215
|
+
if (!parsed.success) {
|
|
216
|
+
return c.text('Invalid!', 401)
|
|
217
|
+
}
|
|
218
|
+
return parsed.data
|
|
219
|
+
}),
|
|
220
|
+
(c) => {
|
|
221
|
+
const { body } = c.req.valid('form')
|
|
222
|
+
// ... do something
|
|
223
|
+
return c.json(
|
|
224
|
+
{
|
|
225
|
+
message: 'Created!',
|
|
226
|
+
},
|
|
227
|
+
201
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Standard Schema validator
|
|
234
|
+
|
|
235
|
+
[Standard Schema](https://standardschema.dev/) is a specification that gives TypeScript validation libraries a common interface, so a tool can accept Zod, Valibot, ArkType and
|
|
236
|
+
others without an adapter for each.
|
|
237
|
+
|
|
238
|
+
`schemaValidator` is part of acrylblends: it validates a request target with any Standard Schema and makes the parsed value available as `c.req.valid(target)`.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { schemaValidator } from 'acrylblends/schema-validator'
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
A failed validation answers 400 with `{ "error": "invalid <target>", "issues": [{ "message", "path" }] }`. The failure shapes, the JSON `Content-Type` rule and a body that
|
|
245
|
+
is not JSON are described in [Validation with Standard Schema](../cordis/validation.md).
|
|
246
|
+
|
|
247
|
+
### With Zod
|
|
248
|
+
|
|
249
|
+
You can use Zod with the schema validator:
|
|
250
|
+
|
|
251
|
+
**npm**
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
npm i zod
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**yarn**
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
yarn add zod
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**pnpm**
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
pnpm add zod
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**bun**
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
bun add zod
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import * as z from 'zod'
|
|
277
|
+
import { schemaValidator } from 'acrylblends/schema-validator'
|
|
278
|
+
|
|
279
|
+
const schema = z.object({
|
|
280
|
+
name: z.string(),
|
|
281
|
+
age: z.number(),
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
app.post('/author', schemaValidator('json', schema), (c) => {
|
|
285
|
+
const data = c.req.valid('json')
|
|
286
|
+
return c.json({
|
|
287
|
+
success: true,
|
|
288
|
+
message: `${data.name} is ${data.age}`,
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### With Valibot
|
|
294
|
+
|
|
295
|
+
[Valibot](https://valibot.dev/) is a lightweight alternative to Zod with a modular design:
|
|
296
|
+
|
|
297
|
+
**npm**
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
npm i valibot
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**yarn**
|
|
304
|
+
|
|
305
|
+
```sh
|
|
306
|
+
yarn add valibot
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**pnpm**
|
|
310
|
+
|
|
311
|
+
```sh
|
|
312
|
+
pnpm add valibot
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**bun**
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
bun add valibot
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import * as v from 'valibot'
|
|
323
|
+
import { schemaValidator } from 'acrylblends/schema-validator'
|
|
324
|
+
|
|
325
|
+
const schema = v.object({
|
|
326
|
+
name: v.string(),
|
|
327
|
+
age: v.number(),
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
app.post('/author', schemaValidator('json', schema), (c) => {
|
|
331
|
+
const data = c.req.valid('json')
|
|
332
|
+
return c.json({
|
|
333
|
+
success: true,
|
|
334
|
+
message: `${data.name} is ${data.age}`,
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### With ArkType
|
|
340
|
+
|
|
341
|
+
[ArkType](https://arktype.io/) offers TypeScript-native syntax for runtime validation:
|
|
342
|
+
|
|
343
|
+
**npm**
|
|
344
|
+
|
|
345
|
+
```sh
|
|
346
|
+
npm i arktype
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**yarn**
|
|
350
|
+
|
|
351
|
+
```sh
|
|
352
|
+
yarn add arktype
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**pnpm**
|
|
356
|
+
|
|
357
|
+
```sh
|
|
358
|
+
pnpm add arktype
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**bun**
|
|
362
|
+
|
|
363
|
+
```sh
|
|
364
|
+
bun add arktype
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { type } from 'arktype'
|
|
369
|
+
import { schemaValidator } from 'acrylblends/schema-validator'
|
|
370
|
+
|
|
371
|
+
const schema = type({
|
|
372
|
+
name: 'string',
|
|
373
|
+
age: 'number',
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
app.post('/author', schemaValidator('json', schema), (c) => {
|
|
377
|
+
const data = c.req.valid('json')
|
|
378
|
+
return c.json({
|
|
379
|
+
success: true,
|
|
380
|
+
message: `${data.name} is ${data.age}`,
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Accepts Helper
|
|
2
|
+
|
|
3
|
+
Accepts Helper helps to handle Accept headers in the Requests.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { accepts } from 'acrylblends/accepts'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## `accepts()`
|
|
13
|
+
|
|
14
|
+
The `accepts()` function looks at the Accept header, such as Accept-Encoding and Accept-Language, and returns the proper value.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { accepts } from 'acrylblends/accepts'
|
|
18
|
+
|
|
19
|
+
app.get('/', (c) => {
|
|
20
|
+
const accept = accepts(c, {
|
|
21
|
+
header: 'Accept-Language',
|
|
22
|
+
supports: ['en', 'ja', 'zh'],
|
|
23
|
+
default: 'en',
|
|
24
|
+
})
|
|
25
|
+
return c.json({ lang: accept })
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `AcceptHeader` type
|
|
30
|
+
|
|
31
|
+
The definition of the `AcceptHeader` type is as follows.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export type AcceptHeader =
|
|
35
|
+
| 'Accept'
|
|
36
|
+
| 'Accept-Charset'
|
|
37
|
+
| 'Accept-Encoding'
|
|
38
|
+
| 'Accept-Language'
|
|
39
|
+
| 'Accept-Patch'
|
|
40
|
+
| 'Accept-Post'
|
|
41
|
+
| 'Accept-Ranges'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
### **(required)** header: `AcceptHeader`
|
|
47
|
+
|
|
48
|
+
The target accept header.
|
|
49
|
+
|
|
50
|
+
### **(required)** supports: `string[]`
|
|
51
|
+
|
|
52
|
+
The header values which your application supports.
|
|
53
|
+
|
|
54
|
+
### **(required)** default: `string`
|
|
55
|
+
|
|
56
|
+
The default values.
|
|
57
|
+
|
|
58
|
+
### *(optional)* match: `(accepts: Accept[], config: acceptsConfig) => string`
|
|
59
|
+
|
|
60
|
+
The custom match function.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Adapter Helper
|
|
2
|
+
|
|
3
|
+
The Adapter Helper provides a seamless way to interact with various platforms through a unified interface.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { env, getRuntimeKey } from 'acrylblends/adapter'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## `env()`
|
|
13
|
+
|
|
14
|
+
The `env()` function facilitates retrieving environment variables across different runtimes, extending beyond just Cloudflare Workers' Bindings. The value that can be retrieved with `env(c)` may be different for each runtime.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { env } from 'acrylblends/adapter'
|
|
18
|
+
|
|
19
|
+
app.get('/env', (c) => {
|
|
20
|
+
// NAME is process.env.NAME on Node.js or Bun
|
|
21
|
+
// NAME is the value written in `wrangler.toml` on Cloudflare
|
|
22
|
+
const { NAME } = env<{ NAME: string }>(c)
|
|
23
|
+
return c.text(NAME)
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Supported Runtimes, Serverless Platforms and Cloud Services:
|
|
28
|
+
|
|
29
|
+
- Cloudflare Workers
|
|
30
|
+
- `wrangler.toml`
|
|
31
|
+
- `wrangler.jsonc`
|
|
32
|
+
- Deno
|
|
33
|
+
- [`Deno.env`](https://docs.deno.com/runtime/manual/basics/env_variables)
|
|
34
|
+
- `.env` file
|
|
35
|
+
- Bun
|
|
36
|
+
- [`Bun.env`](https://bun.com/guides/runtime/set-env)
|
|
37
|
+
- `process.env`
|
|
38
|
+
- Node.js
|
|
39
|
+
- `process.env`
|
|
40
|
+
- Vercel
|
|
41
|
+
- [Environment Variables on Vercel](https://vercel.com/docs/projects/environment-variables)
|
|
42
|
+
- AWS Lambda
|
|
43
|
+
- [Environment Variables on AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/samples-blank.html#samples-blank-architecture)
|
|
44
|
+
- Lambda@Edge\
|
|
45
|
+
Environment Variables on Lambda are [not supported](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/add-origin-custom-headers.html) by Lambda@Edge, you need to use [Lambda@Edge event](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html) as an alternative.
|
|
46
|
+
- Fastly Compute\
|
|
47
|
+
On Fastly Compute, you can use the ConfigStore to manage user-defined data.
|
|
48
|
+
- Netlify\
|
|
49
|
+
On Netlify, you can use the [Netlify Contexts](https://docs.netlify.com/site-deploys/overview/#deploy-contexts) to manage user-defined data.
|
|
50
|
+
|
|
51
|
+
### Specify the runtime
|
|
52
|
+
|
|
53
|
+
You can specify the runtime to get environment variables by passing the runtime key as the second argument.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
app.get('/env', (c) => {
|
|
57
|
+
const { NAME } = env<{ NAME: string }>(c, 'workerd')
|
|
58
|
+
return c.text(NAME)
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## `getRuntimeKey()`
|
|
63
|
+
|
|
64
|
+
The `getRuntimeKey()` function returns the identifier of the current runtime.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
app.get('/', (c) => {
|
|
68
|
+
if (getRuntimeKey() === 'workerd') {
|
|
69
|
+
return c.text('You are on Cloudflare')
|
|
70
|
+
} else if (getRuntimeKey() === 'bun') {
|
|
71
|
+
return c.text('You are on Bun')
|
|
72
|
+
}
|
|
73
|
+
...
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Available Runtimes Keys
|
|
78
|
+
|
|
79
|
+
Here are the available runtimes keys, unavailable runtime key runtimes may be supported and labeled as `other`, with some being inspired by [WinterCG's Runtime Keys](https://runtime-keys.proposal.wintercg.org/):
|
|
80
|
+
|
|
81
|
+
- `workerd` - Cloudflare Workers
|
|
82
|
+
- `deno`
|
|
83
|
+
- `bun`
|
|
84
|
+
- `node`
|
|
85
|
+
- `edge-light` - Vercel Edge Functions
|
|
86
|
+
- `fastly` - Fastly Compute
|
|
87
|
+
- `other` - Other unknown runtimes keys
|