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.
- package/README.md +61 -2
- 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 +109 -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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Language Middleware
|
|
2
|
+
|
|
3
|
+
The Language Detector middleware automatically determines a user's preferred language (locale) from various sources and makes it available via `c.get('language')`. Detection strategies include query parameters, cookies, headers, and URL path segments. Perfect for internationalization (i18n) and locale-specific content.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { languageDetector } from 'acrylblends/language'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Basic Usage
|
|
13
|
+
|
|
14
|
+
Detect language from query string, cookie, and header (default order), with fallback to English:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
const app = new Acryl()
|
|
18
|
+
|
|
19
|
+
app.use(
|
|
20
|
+
languageDetector({
|
|
21
|
+
supportedLanguages: ['en', 'ar', 'ja'], // Must include fallback
|
|
22
|
+
fallbackLanguage: 'en', // Required
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
app.get('/', (c) => {
|
|
27
|
+
const lang = c.get('language')
|
|
28
|
+
return c.text(`Hello! Your language is ${lang}`)
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Client Examples
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
# Via path
|
|
36
|
+
curl http://localhost:8787/ar/home
|
|
37
|
+
|
|
38
|
+
# Via query parameter
|
|
39
|
+
curl http://localhost:8787/?lang=ar
|
|
40
|
+
|
|
41
|
+
# Via cookie
|
|
42
|
+
curl -H 'Cookie: language=ja' http://localhost:8787/
|
|
43
|
+
|
|
44
|
+
# Via header
|
|
45
|
+
curl -H 'Accept-Language: ar,en;q=0.9' http://localhost:8787/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Default Configuration
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export const DEFAULT_OPTIONS: DetectorOptions = {
|
|
52
|
+
order: ['querystring', 'cookie', 'header'],
|
|
53
|
+
lookupQueryString: 'lang',
|
|
54
|
+
lookupCookie: 'language',
|
|
55
|
+
lookupFromHeaderKey: 'accept-language',
|
|
56
|
+
lookupFromPathIndex: 0,
|
|
57
|
+
caches: ['cookie'],
|
|
58
|
+
ignoreCase: true,
|
|
59
|
+
fallbackLanguage: 'en',
|
|
60
|
+
supportedLanguages: ['en'],
|
|
61
|
+
cookieOptions: {
|
|
62
|
+
sameSite: 'Strict',
|
|
63
|
+
secure: true,
|
|
64
|
+
maxAge: 365 * 24 * 60 * 60,
|
|
65
|
+
httpOnly: true,
|
|
66
|
+
},
|
|
67
|
+
debug: false,
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Key Behaviors
|
|
72
|
+
|
|
73
|
+
### Detection Workflow
|
|
74
|
+
|
|
75
|
+
1. **Order**: Checks sources in this sequence by default:
|
|
76
|
+
- Query parameter (?lang=ar)
|
|
77
|
+
- Cookie (language=ar)
|
|
78
|
+
- Accept-Language header
|
|
79
|
+
|
|
80
|
+
2. **Caching**: Stores detected language in a cookie (1 year by default)
|
|
81
|
+
|
|
82
|
+
3. **Fallback**: Uses `fallbackLanguage` if no valid detection (must be in `supportedLanguages`)
|
|
83
|
+
|
|
84
|
+
## Advanced Configuration
|
|
85
|
+
|
|
86
|
+
### Custom Detection Order
|
|
87
|
+
|
|
88
|
+
Prioritize URL path detection (e.g., /en/about):
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
app.use(
|
|
92
|
+
languageDetector({
|
|
93
|
+
order: ['path', 'cookie', 'querystring', 'header'],
|
|
94
|
+
lookupFromPathIndex: 0, // /en/profile → index 0 = 'en'
|
|
95
|
+
supportedLanguages: ['en', 'ar'],
|
|
96
|
+
fallbackLanguage: 'en',
|
|
97
|
+
})
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Progressive Locale Matching
|
|
102
|
+
|
|
103
|
+
When a detected locale code like `ja-JP` is not in `supportedLanguages`, the middleware progressively truncates subtags to find a match. For example, `zh-Hant-CN` will try `zh-Hant`, then `zh`. An exact match is always preferred.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
app.use(
|
|
107
|
+
languageDetector({
|
|
108
|
+
supportedLanguages: ['en', 'ja', 'zh-Hant'],
|
|
109
|
+
fallbackLanguage: 'en',
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// Accept-Language: ja-JP → matches 'ja'
|
|
114
|
+
// Accept-Language: zh-Hant-CN → matches 'zh-Hant'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Language Code Transformation
|
|
118
|
+
|
|
119
|
+
Normalize complex codes (e.g., en-US → en):
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
app.use(
|
|
123
|
+
languageDetector({
|
|
124
|
+
convertDetectedLanguage: (lang) => lang.split('-')[0],
|
|
125
|
+
supportedLanguages: ['en', 'ja'],
|
|
126
|
+
fallbackLanguage: 'en',
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Cookie Configuration
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
app.use(
|
|
135
|
+
languageDetector({
|
|
136
|
+
lookupCookie: 'app_lang',
|
|
137
|
+
caches: ['cookie'],
|
|
138
|
+
cookieOptions: {
|
|
139
|
+
path: '/', // Cookie path
|
|
140
|
+
sameSite: 'Lax', // Cookie same-site policy
|
|
141
|
+
secure: true, // Only send over HTTPS
|
|
142
|
+
maxAge: 86400 * 365, // 1 year expiration
|
|
143
|
+
httpOnly: true, // Not accessible via JavaScript
|
|
144
|
+
domain: '.example.com', // Optional: specific domain
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
To disable cookie caching:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
languageDetector({
|
|
154
|
+
caches: false,
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Debugging
|
|
159
|
+
|
|
160
|
+
Log detection steps:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
languageDetector({
|
|
164
|
+
debug: true, // Shows: "Detected from querystring: ar"
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Options Reference
|
|
169
|
+
|
|
170
|
+
### Basic Options
|
|
171
|
+
|
|
172
|
+
| Option | Type | Default | Required | Description |
|
|
173
|
+
| :------------------- | :--------------- | :------------------------------------ | :------- | :--------------------- |
|
|
174
|
+
| `supportedLanguages` | `string[]` | `['en']` | Yes | Allowed language codes |
|
|
175
|
+
| `fallbackLanguage` | `string` | `'en'` | Yes | Default language |
|
|
176
|
+
| `order` | `DetectorType[]` | `['querystring', 'cookie', 'header']` | No | Detection sequence |
|
|
177
|
+
| `debug` | `boolean` | `false` | No | Enable logging |
|
|
178
|
+
|
|
179
|
+
### Detection Options
|
|
180
|
+
|
|
181
|
+
| Option | Type | Default | Description |
|
|
182
|
+
| :-------------------- | :------- | :------------------ | :------------------- |
|
|
183
|
+
| `lookupQueryString` | `string` | `'lang'` | Query parameter name |
|
|
184
|
+
| `lookupCookie` | `string` | `'language'` | Cookie name |
|
|
185
|
+
| `lookupFromHeaderKey` | `string` | `'accept-language'` | Header name |
|
|
186
|
+
| `lookupFromPathIndex` | `number` | `0` | Path segment index |
|
|
187
|
+
|
|
188
|
+
### Cookie Options
|
|
189
|
+
|
|
190
|
+
| Option | Type | Default | Description |
|
|
191
|
+
| :----------------------- | :---------------------------- | :----------- | :------------------- |
|
|
192
|
+
| `caches` | `CacheType[] \| false` | `['cookie']` | Cache settings |
|
|
193
|
+
| `cookieOptions.path` | `string` | `'/'` | Cookie path |
|
|
194
|
+
| `cookieOptions.sameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Strict'` | SameSite policy |
|
|
195
|
+
| `cookieOptions.secure` | `boolean` | `true` | HTTPS only |
|
|
196
|
+
| `cookieOptions.maxAge` | `number` | `31536000` | Expiration (seconds) |
|
|
197
|
+
| `cookieOptions.httpOnly` | `boolean` | `true` | JS accessibility |
|
|
198
|
+
| `cookieOptions.domain` | `string` | `undefined` | Cookie domain |
|
|
199
|
+
|
|
200
|
+
### Advanced Options
|
|
201
|
+
|
|
202
|
+
| Option | Type | Default | Description |
|
|
203
|
+
| :------------------------ | :------------------------- | :---------- | :------------------------ |
|
|
204
|
+
| `ignoreCase` | `boolean` | `true` | Case-insensitive matching |
|
|
205
|
+
| `convertDetectedLanguage` | `(lang: string) => string` | `undefined` | Language code transformer |
|
|
206
|
+
|
|
207
|
+
## Validation & Error Handling
|
|
208
|
+
|
|
209
|
+
- `fallbackLanguage` must be in `supportedLanguages` (throws error during setup)
|
|
210
|
+
- `lookupFromPathIndex` must be ≥ 0
|
|
211
|
+
- Invalid configurations throw errors during middleware initialization
|
|
212
|
+
- Failed detections silently use `fallbackLanguage`
|
|
213
|
+
|
|
214
|
+
## Common Recipes
|
|
215
|
+
|
|
216
|
+
### Path-Based Routing
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
app.get('/:lang/home', (c) => {
|
|
220
|
+
const lang = c.get('language') // 'en', 'ar', etc.
|
|
221
|
+
return c.json({ message: getLocalizedContent(lang) })
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Multiple Supported Languages
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
languageDetector({
|
|
229
|
+
supportedLanguages: ['en', 'en-GB', 'ar', 'ar-EG'],
|
|
230
|
+
convertDetectedLanguage: (lang) => lang.replace('_', '-'), // Normalize
|
|
231
|
+
})
|
|
232
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Logger Middleware
|
|
2
|
+
|
|
3
|
+
It's a simple logger.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { logger } from 'acrylblends/logger'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const app = new Acryl()
|
|
16
|
+
|
|
17
|
+
app.use(logger())
|
|
18
|
+
app.get('/', (c) => c.text('Hello Acryl!'))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Logging Details
|
|
22
|
+
|
|
23
|
+
The Logger Middleware logs the following details for each request:
|
|
24
|
+
|
|
25
|
+
- **Incoming Request**: Logs the HTTP method, request path, and incoming request.
|
|
26
|
+
- **Outgoing Response**: Logs the HTTP method, request path, response status code, and request/response times.
|
|
27
|
+
- **Status Code Coloring**: Response status codes are color-coded for better visibility and quick identification of status categories. Different status code categories are represented by different colors.
|
|
28
|
+
- **Elapsed Time**: The time taken for the request/response cycle is logged in a human-readable format, either in milliseconds (ms) or seconds (s).
|
|
29
|
+
|
|
30
|
+
By using the Logger Middleware, you can easily monitor the flow of requests and responses in your Acryl application and quickly identify any issues or performance bottlenecks.
|
|
31
|
+
|
|
32
|
+
You can also extend the middleware further by providing your own `PrintFunc` function for tailored logging behavior.
|
|
33
|
+
|
|
34
|
+
> **Tip.**
|
|
35
|
+
> To disable _status code coloring_, you can set a `NO_COLOR` environment variable. This is a common way to disable ANSI color escape codes in logging libraries, and is described at <https://no-color.org/>. Note that Cloudflare Workers do not have a `process.env` object, so will default to plaintext log output.
|
|
36
|
+
|
|
37
|
+
## PrintFunc
|
|
38
|
+
|
|
39
|
+
The Logger Middleware accepts an optional `PrintFunc` function as a parameter. This function allows you to customize the logger and add additional logs.
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
### *(optional)* fn: `PrintFunc(str: string, ...rest: string[])`
|
|
44
|
+
|
|
45
|
+
- `str`: Passed by the logger.
|
|
46
|
+
- `...rest`: Additional string props to be printed to console.
|
|
47
|
+
|
|
48
|
+
### Example
|
|
49
|
+
|
|
50
|
+
Setting up a custom `PrintFunc` function to the Logger Middleware:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export const customLogger = (message: string, ...rest: string[]) => {
|
|
54
|
+
console.log(message, ...rest)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
app.use(logger(customLogger))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Setting up the custom logger in a route:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
app.post('/blog', (c) => {
|
|
64
|
+
// Routing logic
|
|
65
|
+
|
|
66
|
+
customLogger('Blog saved:', `Path: ${blog.url},`, `ID: ${blog.id}`)
|
|
67
|
+
// Output
|
|
68
|
+
// <-- POST /blog
|
|
69
|
+
// Blog saved: Path: /blog/example, ID: 1
|
|
70
|
+
// --> POST /blog 201 93ms
|
|
71
|
+
|
|
72
|
+
// Return Context
|
|
73
|
+
})
|
|
74
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Loopback Only Middleware
|
|
2
|
+
|
|
3
|
+
The guard for a local app's own routes, such as a control API a desktop window calls:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { loopbackOnly } from 'acryl-router/middleware'
|
|
7
|
+
|
|
8
|
+
app.use('/control/*', loopbackOnly())
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
A request passes only when:
|
|
12
|
+
|
|
13
|
+
- the peer is a loopback address and arrived on the real local port, both read from the transport, never from a header;
|
|
14
|
+
- the `Host` header names a loopback hostname (`127.0.0.1`, `localhost`, `[::1]`, or `hostnames`) with that port, which stops DNS
|
|
15
|
+
rebinding;
|
|
16
|
+
- for a request that changes things (not `GET`, `HEAD`, `OPTIONS`), any `Origin` is this same authority (`mutations: 'none'` turns this
|
|
17
|
+
off).
|
|
18
|
+
|
|
19
|
+
An in-process call has no socket and is refused unless `inProcess: 'allow'` says the host's own code may call it. Everything refused gets a
|
|
20
|
+
403.
|
|
21
|
+
|
|
22
|
+
A private route that no other program on the machine may call either, such as a workspace's file and terminal routes, takes
|
|
23
|
+
`callers: 'own-page'`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
app.use('/api/workspace/*', loopbackOnly({ callers: 'own-page', hostnames: ['127.0.0.1'] }))
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then every request must come from a page this app served: an `Origin` that is exactly this authority (with `Sec-Fetch-Site` absent or
|
|
30
|
+
`same-origin`), or, for a read, which browsers often send without `Origin`, `Sec-Fetch-Site: same-origin` and a `Referer` from this
|
|
31
|
+
authority. A script with no `Origin` is refused even for a read. `fromOwnPage(c, authority)` is the same check for middleware of your own.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Method Not Allowed Middleware
|
|
2
|
+
|
|
3
|
+
The Method Not Allowed middleware returns a `405 Method Not Allowed` response with an `Allow` header when the request path matches a registered route but the request method is not supported. Without this middleware, Acryl returns a `404 Not Found` in that case.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { methodNotAllowed } from 'acrylblends/method-not-allowed'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const app = new Acryl()
|
|
16
|
+
|
|
17
|
+
app.use(methodNotAllowed({ app }))
|
|
18
|
+
|
|
19
|
+
app.get('/hello', (c) => c.text('Hello!'))
|
|
20
|
+
app.post('/hello', (c) => c.text('Posted!'))
|
|
21
|
+
|
|
22
|
+
// PUT /hello -> 405 Method Not Allowed
|
|
23
|
+
// Allow: GET, HEAD, POST
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can customize the response with the `onMethodNotAllowed` option:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
app.use(
|
|
30
|
+
methodNotAllowed({
|
|
31
|
+
app,
|
|
32
|
+
onMethodNotAllowed: (c, methods) =>
|
|
33
|
+
c.json({ error: 'Method Not Allowed' }, 405, {
|
|
34
|
+
Allow: methods.join(', '),
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Options
|
|
41
|
+
|
|
42
|
+
### **(required)** app: `Acryl`
|
|
43
|
+
|
|
44
|
+
The Acryl instance used by the application. The middleware collects the allowed methods for each path from its registered routes.
|
|
45
|
+
|
|
46
|
+
### *(optional)* onMethodNotAllowed: `(c: Context, allowedMethods: string[]) => Response | Promise<Response>`
|
|
47
|
+
|
|
48
|
+
Generates the response, including its `Allow` header. By default, the middleware returns a `405 Method Not Allowed` response with the `Allow` header set to the allowed methods.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Method Override Middleware
|
|
2
|
+
|
|
3
|
+
This middleware executes the handler of the specified method, which is different from the actual method of the request, depending on the value of the form, header, or query, and returns its response.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { methodOverride } from 'acrylblends/method-override'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const app = new Acryl()
|
|
16
|
+
|
|
17
|
+
// If no options are specified, the value of `_method` in the form,
|
|
18
|
+
// e.g. DELETE, is used as the method.
|
|
19
|
+
app.use('/posts', methodOverride({ app }))
|
|
20
|
+
|
|
21
|
+
app.delete('/posts', (c) => {
|
|
22
|
+
// ....
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## For example
|
|
27
|
+
|
|
28
|
+
Since HTML forms cannot send a DELETE method, you can put the value `DELETE` in the property named `_method` and send it. And the handler for `app.delete()` will be executed.
|
|
29
|
+
|
|
30
|
+
The HTML form:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<form action="/posts" method="POST">
|
|
34
|
+
<input type="hidden" name="_method" value="DELETE" />
|
|
35
|
+
<input type="text" name="id" />
|
|
36
|
+
</form>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The application:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { methodOverride } from 'acrylblends/method-override'
|
|
43
|
+
|
|
44
|
+
const app = new Acryl()
|
|
45
|
+
app.use('/posts', methodOverride({ app }))
|
|
46
|
+
|
|
47
|
+
app.delete('/posts', () => {
|
|
48
|
+
// ...
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
You can change the default values or use the header value and query value:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
app.use('/posts', methodOverride({ app, form: '_custom_name' }))
|
|
56
|
+
app.use(
|
|
57
|
+
'/posts',
|
|
58
|
+
methodOverride({ app, header: 'X-METHOD-OVERRIDE' })
|
|
59
|
+
)
|
|
60
|
+
app.use('/posts', methodOverride({ app, query: '_method' }))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Options
|
|
64
|
+
|
|
65
|
+
### **(required)** app: `Acryl`
|
|
66
|
+
|
|
67
|
+
The instance of `Acryl` is used in your application.
|
|
68
|
+
|
|
69
|
+
### *(optional)* form: `string`
|
|
70
|
+
|
|
71
|
+
Form key with a value containing the method name.
|
|
72
|
+
The default is `_method`.
|
|
73
|
+
|
|
74
|
+
### *(optional)* header: `boolean`
|
|
75
|
+
|
|
76
|
+
Header name with a value containing the method name.
|
|
77
|
+
|
|
78
|
+
### *(optional)* query: `boolean`
|
|
79
|
+
|
|
80
|
+
Query parameter key with a value containing the method name.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Mount Middleware
|
|
2
|
+
|
|
3
|
+
> **Not yet available.** The `acrylblends/mount` subpath (an app can still call `app.mount()`) is documented here so the page structure matches the rest of the framework, and is not part of acrylblends yet. The code on this page is not checked by the docs tests.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
The Mount Middleware lets you mount an application built with another framework into your Acryl application.
|
|
7
|
+
|
|
8
|
+
## Import
|
|
9
|
+
|
|
10
|
+
```ts twoslash
|
|
11
|
+
import { Acryl } from 'acrylblends'
|
|
12
|
+
import { mount } from 'acrylblends/mount'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Register the middleware with `app.all()` on a path that ends with `/*`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Router as IttyRouter } from 'itty-router'
|
|
21
|
+
|
|
22
|
+
// Create itty-router application
|
|
23
|
+
const ittyRouter = IttyRouter()
|
|
24
|
+
|
|
25
|
+
// Handle `GET /itty-router/hello`
|
|
26
|
+
ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
|
|
27
|
+
|
|
28
|
+
const app = new Acryl()
|
|
29
|
+
|
|
30
|
+
app.all('/itty-router/*', mount(ittyRouter.handle))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
By default, the mounted application receives a new `Request` with the matched path prefix removed from its URL. A request to `/itty-router/hello` reaches itty-router as `/hello`.
|
|
34
|
+
|
|
35
|
+
The mounted application is called as `handler(request, c.env, c.executionCtx)`, the same arguments as a Cloudflare Workers `fetch` handler. Use `optionHandler` to pass different arguments.
|
|
36
|
+
|
|
37
|
+
## Options
|
|
38
|
+
|
|
39
|
+
You can pass a function or an object as the second argument.
|
|
40
|
+
|
|
41
|
+
### *(optional)* optionHandler: `(c: Context) => unknown`
|
|
42
|
+
|
|
43
|
+
Returns the arguments passed to the mounted application after the `Request`. An array is spread into multiple arguments. You can pass the function directly as the second argument:
|
|
44
|
+
|
|
45
|
+
```ts twoslash
|
|
46
|
+
import { Acryl } from 'acrylblends'
|
|
47
|
+
import { mount } from 'acrylblends/mount'
|
|
48
|
+
const app = new Acryl()
|
|
49
|
+
const anotherApp = (request: Request, ...args: unknown[]) =>
|
|
50
|
+
new Response(request.url)
|
|
51
|
+
// ---cut---
|
|
52
|
+
// Call `anotherApp(request, c.env)`
|
|
53
|
+
app.all(
|
|
54
|
+
'/app/*',
|
|
55
|
+
mount(anotherApp, (c) => c.env)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Call `anotherApp(request, c.env, c.executionCtx)`
|
|
59
|
+
app.all(
|
|
60
|
+
'/app/*',
|
|
61
|
+
mount(anotherApp, {
|
|
62
|
+
optionHandler: (c) => [c.env, c.executionCtx],
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### *(optional)* replaceRequest: `((originalRequest: Request) => Request) | false`
|
|
68
|
+
|
|
69
|
+
Controls which `Request` is passed to the mounted application:
|
|
70
|
+
|
|
71
|
+
```ts twoslash
|
|
72
|
+
import { Acryl } from 'acrylblends'
|
|
73
|
+
import { mount } from 'acrylblends/mount'
|
|
74
|
+
const app = new Acryl()
|
|
75
|
+
const anotherApp = (request: Request, ...args: unknown[]) =>
|
|
76
|
+
new Response(request.url)
|
|
77
|
+
// ---cut---
|
|
78
|
+
// `/legacy/users` reaches the mounted application as `/api/users`
|
|
79
|
+
app.all(
|
|
80
|
+
'/legacy/*',
|
|
81
|
+
mount(anotherApp, {
|
|
82
|
+
replaceRequest: (req) => {
|
|
83
|
+
const url = new URL(req.url)
|
|
84
|
+
url.pathname = url.pathname.replace(/^\/legacy/, '/api')
|
|
85
|
+
return new Request(url, req)
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
To pass the original `Request` unchanged, set it to `false` as a shorthand for `(req) => req`:
|
|
92
|
+
|
|
93
|
+
```ts twoslash
|
|
94
|
+
import { Acryl } from 'acrylblends'
|
|
95
|
+
import { mount } from 'acrylblends/mount'
|
|
96
|
+
const app = new Acryl()
|
|
97
|
+
const anotherApp = (request: Request, ...args: unknown[]) =>
|
|
98
|
+
new Response(request.url)
|
|
99
|
+
// ---cut---
|
|
100
|
+
app.all(
|
|
101
|
+
'/app/*',
|
|
102
|
+
mount(anotherApp, {
|
|
103
|
+
replaceRequest: false,
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Pretty JSON Middleware
|
|
2
|
+
|
|
3
|
+
Pretty JSON middleware enables "_JSON pretty print_" for JSON response body.
|
|
4
|
+
Adding `?pretty` to url query param, the JSON strings are prettified.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
// GET /
|
|
8
|
+
{"project":{"name":"Acryl","repository":"https://github.com/acrylblends/acrylblends"}}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
will be:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// GET /?pretty
|
|
15
|
+
{
|
|
16
|
+
"project": {
|
|
17
|
+
"name": "Acryl",
|
|
18
|
+
"repository": "https://github.com/acrylblends/acrylblends"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Import
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Acryl } from 'acrylblends'
|
|
27
|
+
import { prettyJSON } from 'acrylblends/pretty-json'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const app = new Acryl()
|
|
34
|
+
|
|
35
|
+
app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })
|
|
36
|
+
app.get('/', (c) => {
|
|
37
|
+
return c.json({ message: 'Acryl!' })
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
### *(optional)* space: `number`
|
|
44
|
+
|
|
45
|
+
Number of spaces for indentation. The default is `2`.
|
|
46
|
+
|
|
47
|
+
### *(optional)* query: `string`
|
|
48
|
+
|
|
49
|
+
The name of the query string for applying. The default is `pretty`.
|
|
50
|
+
|
|
51
|
+
### *(optional)* force: `boolean`
|
|
52
|
+
|
|
53
|
+
When set to `true`, JSON responses are always prettified regardless of the query parameter. The default is `false`.
|