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.
Files changed (106) hide show
  1. package/bin/create-acrylblends.js +4 -0
  2. package/dist/index.d.ts +56 -0
  3. package/dist/index.js +274 -0
  4. package/dist/index.js.map +1 -0
  5. package/docs/framework/LICENSE +23 -0
  6. package/docs/framework/README.md +63 -0
  7. package/docs/framework/UPSTREAM.md +3 -0
  8. package/docs/framework/agents/index.md +24 -0
  9. package/docs/framework/api/acryl.md +259 -0
  10. package/docs/framework/api/context.md +527 -0
  11. package/docs/framework/api/exception.md +80 -0
  12. package/docs/framework/api/index.md +12 -0
  13. package/docs/framework/api/presets.md +65 -0
  14. package/docs/framework/api/request.md +396 -0
  15. package/docs/framework/api/routing.md +303 -0
  16. package/docs/framework/api/summary.md +140 -0
  17. package/docs/framework/concepts/benchmarks.md +26 -0
  18. package/docs/framework/concepts/developer-experience.md +5 -0
  19. package/docs/framework/concepts/middleware.md +21 -0
  20. package/docs/framework/concepts/motivation.md +14 -0
  21. package/docs/framework/concepts/routers.md +63 -0
  22. package/docs/framework/concepts/stacks.md +235 -0
  23. package/docs/framework/concepts/web-standard.md +37 -0
  24. package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
  25. package/docs/framework/cordis/concepts.md +105 -0
  26. package/docs/framework/cordis/middleware.md +8 -0
  27. package/docs/framework/cordis/migration-from-0-3.md +111 -0
  28. package/docs/framework/cordis/migration-from-0-4.md +14 -0
  29. package/docs/framework/cordis/migration-from-dsh-webserver.md +103 -0
  30. package/docs/framework/cordis/mounting.md +85 -0
  31. package/docs/framework/cordis/node-server.md +75 -0
  32. package/docs/framework/cordis/plugin-lifecycle.md +111 -0
  33. package/docs/framework/cordis/surfaces.md +71 -0
  34. package/docs/framework/cordis/transport-and-cancellation.md +57 -0
  35. package/docs/framework/cordis/validation.md +135 -0
  36. package/docs/framework/getting-started/basic.md +196 -0
  37. package/docs/framework/getting-started/bun.md +221 -0
  38. package/docs/framework/getting-started/nodejs.md +344 -0
  39. package/docs/framework/getting-started/other-runtimes.md +23 -0
  40. package/docs/framework/guides/best-practices.md +231 -0
  41. package/docs/framework/guides/create-acrylblends.md +79 -0
  42. package/docs/framework/guides/examples.md +3 -0
  43. package/docs/framework/guides/faq.md +19 -0
  44. package/docs/framework/guides/helpers.md +33 -0
  45. package/docs/framework/guides/jsx-dom.md +299 -0
  46. package/docs/framework/guides/jsx.md +440 -0
  47. package/docs/framework/guides/middleware.md +253 -0
  48. package/docs/framework/guides/rpc.md +886 -0
  49. package/docs/framework/guides/testing.md +133 -0
  50. package/docs/framework/guides/validation.md +383 -0
  51. package/docs/framework/helpers/accepts.md +60 -0
  52. package/docs/framework/helpers/adapter.md +87 -0
  53. package/docs/framework/helpers/conninfo.md +112 -0
  54. package/docs/framework/helpers/cookie.md +232 -0
  55. package/docs/framework/helpers/css.md +285 -0
  56. package/docs/framework/helpers/dev.md +67 -0
  57. package/docs/framework/helpers/factory.md +180 -0
  58. package/docs/framework/helpers/html.md +123 -0
  59. package/docs/framework/helpers/jwt.md +184 -0
  60. package/docs/framework/helpers/proxy.md +118 -0
  61. package/docs/framework/helpers/route.md +143 -0
  62. package/docs/framework/helpers/ssg.md +393 -0
  63. package/docs/framework/helpers/streaming.md +119 -0
  64. package/docs/framework/helpers/testing.md +101 -0
  65. package/docs/framework/helpers/websocket.md +215 -0
  66. package/docs/framework/middleware/builtin/basic-auth.md +165 -0
  67. package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
  68. package/docs/framework/middleware/builtin/body-limit.md +71 -0
  69. package/docs/framework/middleware/builtin/cache.md +122 -0
  70. package/docs/framework/middleware/builtin/combine.md +94 -0
  71. package/docs/framework/middleware/builtin/compress.md +59 -0
  72. package/docs/framework/middleware/builtin/context-storage.md +78 -0
  73. package/docs/framework/middleware/builtin/cors.md +135 -0
  74. package/docs/framework/middleware/builtin/csrf.md +105 -0
  75. package/docs/framework/middleware/builtin/etag.md +54 -0
  76. package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
  77. package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
  78. package/docs/framework/middleware/builtin/jwk.md +192 -0
  79. package/docs/framework/middleware/builtin/jwt.md +145 -0
  80. package/docs/framework/middleware/builtin/language.md +232 -0
  81. package/docs/framework/middleware/builtin/logger.md +74 -0
  82. package/docs/framework/middleware/builtin/loopback-only.md +31 -0
  83. package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
  84. package/docs/framework/middleware/builtin/method-override.md +80 -0
  85. package/docs/framework/middleware/builtin/mount.md +106 -0
  86. package/docs/framework/middleware/builtin/pretty-json.md +53 -0
  87. package/docs/framework/middleware/builtin/request-id.md +94 -0
  88. package/docs/framework/middleware/builtin/secure-headers.md +247 -0
  89. package/docs/framework/middleware/builtin/timeout.md +98 -0
  90. package/docs/framework/middleware/builtin/timing.md +101 -0
  91. package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
  92. package/docs/framework/middleware/third-party.md +4 -0
  93. package/docs/framework/upstream.json +9 -0
  94. package/package.json +47 -3
  95. package/templates/bun/gitignore +1 -0
  96. package/templates/bun/package.json +19 -0
  97. package/templates/bun/src/index.ts +7 -0
  98. package/templates/bun/test/app.test.ts +8 -0
  99. package/templates/bun/tsconfig.json +15 -0
  100. package/templates/nodejs/gitignore +1 -0
  101. package/templates/nodejs/package.json +22 -0
  102. package/templates/nodejs/src/app.ts +7 -0
  103. package/templates/nodejs/src/index.ts +9 -0
  104. package/templates/nodejs/test/app.test.ts +9 -0
  105. package/templates/nodejs/tsconfig.json +16 -0
  106. package/README.md +0 -3
@@ -0,0 +1,122 @@
1
+ # Cache Middleware
2
+
3
+ > **Not yet available.** The cache middleware (`acrylblends/cache`) 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 Cache middleware uses the Web Standards' [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
7
+
8
+ The Cache middleware currently supports Cloudflare Workers projects using custom domains and Deno projects using [Deno 1.26+](https://github.com/denoland/deno/releases/tag/v1.26.0). Also available with Deno Deploy.
9
+
10
+ Cloudflare Workers respects the `Cache-Control` header and return cached responses. For details, refer to [Cache on Cloudflare Docs](https://developers.cloudflare.com/workers/runtime-apis/cache/). Deno does not respect headers, so if you need to update the cache, you will need to implement your own mechanism.
11
+
12
+ See [Usage](#usage) below for instructions on each platform.
13
+
14
+ ## Import
15
+
16
+ ```ts
17
+ import { Acryl } from 'acrylblends'
18
+ import { cache } from 'acrylblends/cache'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ **Cloudflare Workers**
24
+
25
+ ```ts
26
+ app.get(
27
+ '*',
28
+ cache({
29
+ cacheName: 'my-app',
30
+ cacheControl: 'max-age=3600',
31
+ })
32
+ )
33
+ ```
34
+
35
+ **Deno**
36
+
37
+ ```ts
38
+ // Must use `wait: true` for the Deno runtime
39
+ app.get(
40
+ '*',
41
+ cache({
42
+ cacheName: 'my-app',
43
+ cacheControl: 'max-age=3600',
44
+ wait: true,
45
+ })
46
+ )
47
+ ```
48
+
49
+ ## Caching QUERY requests
50
+
51
+ The Cache middleware also caches responses to [QUERY](https://www.rfc-editor.org/rfc/rfc10008.html) requests. As required by RFC 10008, the cache key for a QUERY request incorporates a digest of the request content and its representation metadata, so requests with different bodies are cached separately.
52
+
53
+ QUERY requests with a body larger than `maxQueryBodySize` (64 KiB by default) bypass the cache.
54
+
55
+ > **Note.** To support this, cached entries are stored under an internal key, so they do not collide with your own requests.
56
+
57
+ ## Options
58
+
59
+ ### **(required)** cacheName: `string` | `(c: Context) => string` | `Promise<string>`
60
+
61
+ The name of the cache. Can be used to store multiple caches with different identifiers.
62
+
63
+ ### *(optional)* wait: `boolean`
64
+
65
+ A boolean indicating if Acryl should wait for the Promise of the `cache.put` function to resolve before continuing with the request. _Required to be true for the Deno environment_. The default is `false`.
66
+
67
+ ### *(optional)* cacheControl: `string`
68
+
69
+ A string of directives for the `Cache-Control` header. See the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) for more information. When this option is not provided, no `Cache-Control` header is added to requests.
70
+
71
+ ### *(optional)* vary: `string` | `string[]`
72
+
73
+ Sets the `Vary` header in the response. If the original response header already contains a `Vary` header, the values are merged, removing any duplicates. Setting this to `*` will result in an error. For more details on the Vary header and its implications for caching strategies, refer to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary).
74
+
75
+ ### *(optional)* keyGenerator: `(c: Context) => string | Promise<string>`
76
+
77
+ Generates keys for every request in the `cacheName` store. This can be used to cache data based on request parameters or context parameters. The default is `c.req.url`. For QUERY requests, the key additionally includes a digest of the request content and its representation metadata.
78
+
79
+ ### *(optional)* maxQueryBodySize: `number`
80
+
81
+ The maximum QUERY request body size in bytes that can be cached. QUERY requests with a larger body bypass the cache. The default is `65536` (64 KiB).
82
+
83
+ ### *(optional)* cacheableStatusCodes: `number[]`
84
+
85
+ An array of status codes that should be cached. The default is `[200]`. Use this option to cache responses with specific status codes.
86
+
87
+ ```ts
88
+ app.get(
89
+ '*',
90
+ cache({
91
+ cacheName: 'my-app',
92
+ cacheControl: 'max-age=3600',
93
+ cacheableStatusCodes: [200, 404, 412],
94
+ })
95
+ )
96
+ ```
97
+
98
+ ### *(optional)* onCacheNotAvailable: `((reason: string) => void | Promise<void>)` | `false`
99
+
100
+ A callback function or `false` that controls the behavior when the Cache API is not available in the global scope, or when QUERY caching cannot use Web Crypto. The callback is invoked with the reason. By default, the reason is logged with `console.log`. You can provide a custom function to customize the behavior, or set it to `false` to suppress the log entirely.
101
+
102
+ ```ts
103
+ // Custom logging
104
+ app.use(
105
+ cache({
106
+ cacheName: 'my-app-v1',
107
+ onCacheNotAvailable: () => {
108
+ console.log('Custom log: Cache API is not available.')
109
+ },
110
+ })
111
+ )
112
+ ```
113
+
114
+ ```ts
115
+ // Suppress logging
116
+ app.use(
117
+ cache({
118
+ cacheName: 'my-app-v1',
119
+ onCacheNotAvailable: false,
120
+ })
121
+ )
122
+ ```
@@ -0,0 +1,94 @@
1
+ # Combine Middleware
2
+
3
+ Combine Middleware combines multiple middleware functions into a single middleware. It provides three functions:
4
+
5
+ - `some` - Runs only one of the given middleware.
6
+ - `every` - Runs all given middleware.
7
+ - `except` - Runs all given middleware only if a condition is not met.
8
+
9
+ ## Import
10
+
11
+ ```ts
12
+ import { Acryl } from 'acrylblends'
13
+ import { some, every, except } from 'acrylblends/combine'
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ Here's an example of complex access control rules using Combine Middleware.
19
+
20
+ ```ts
21
+ import { Acryl } from 'acrylblends'
22
+ import { bearerAuth } from 'acrylblends/bearer-auth'
23
+ import { getConnInfo } from 'acrylblends/node-server'
24
+ import { every, some } from 'acrylblends/combine'
25
+ import { ipRestriction } from 'acrylblends/ip-restriction'
26
+ import { rateLimit } from '@/my-rate-limit'
27
+
28
+ const app = new Acryl()
29
+
30
+ app.use(
31
+ '*',
32
+ some(
33
+ every(
34
+ ipRestriction(getConnInfo, { allowList: ['192.168.0.2'] }),
35
+ bearerAuth({ token })
36
+ ),
37
+ // If both conditions are met, rateLimit will not execute.
38
+ rateLimit()
39
+ )
40
+ )
41
+
42
+ app.get('/', (c) => c.text('Hello Acryl!'))
43
+ ```
44
+
45
+ ### some
46
+
47
+ Runs the first middleware that returns true. Middleware is applied in order, and if any middleware exits successfully, subsequent middleware will not run.
48
+
49
+ ```ts
50
+ import { some } from 'acrylblends/combine'
51
+ import { bearerAuth } from 'acrylblends/bearer-auth'
52
+ import { myRateLimit } from '@/rate-limit'
53
+
54
+ // If client has a valid token, skip rate limiting.
55
+ // Otherwise, apply rate limiting.
56
+ app.use(
57
+ '/api/*',
58
+ some(bearerAuth({ token }), myRateLimit({ limit: 100 }))
59
+ )
60
+ ```
61
+
62
+ ### every
63
+
64
+ Runs all middleware and stops if any of them fail. Middleware is applied in order, and if any middleware throws an error, subsequent middleware will not run.
65
+
66
+ ```ts
67
+ import { some, every } from 'acrylblends/combine'
68
+ import { bearerAuth } from 'acrylblends/bearer-auth'
69
+ import { myCheckLocalNetwork } from '@/check-local-network'
70
+ import { myRateLimit } from '@/rate-limit'
71
+
72
+ // If client is in local network, skip authentication and rate limiting.
73
+ // Otherwise, apply authentication and rate limiting.
74
+ app.use(
75
+ '/api/*',
76
+ some(
77
+ myCheckLocalNetwork(),
78
+ every(bearerAuth({ token }), myRateLimit({ limit: 100 }))
79
+ )
80
+ )
81
+ ```
82
+
83
+ ### except
84
+
85
+ Runs all middleware except when the condition is met. You can pass a string or function as the condition. If multiple targets need to be matched, pass them as an array.
86
+
87
+ ```ts
88
+ import { except } from 'acrylblends/combine'
89
+ import { bearerAuth } from 'acrylblends/bearer-auth'
90
+
91
+ // If client is accessing public API, skip authentication.
92
+ // Otherwise, require a valid token.
93
+ app.use('/api/*', except('/api/public/*', bearerAuth({ token })))
94
+ ```
@@ -0,0 +1,59 @@
1
+ # Compress Middleware
2
+
3
+ This middleware compresses the response body, according to `Accept-Encoding` request header.
4
+
5
+ > **Note.** On Cloudflare Workers and Deno Deploy, the response body will be compressed automatically, so there is no need to use this middleware.
6
+
7
+ ## Import
8
+
9
+ ```ts
10
+ import { Acryl } from 'acrylblends'
11
+ import { compress } from 'acrylblends/compress'
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```ts
17
+ const app = new Acryl()
18
+
19
+ app.use(compress())
20
+ ```
21
+
22
+ ## Options
23
+
24
+ ### *(optional)* encoding: `'gzip'` | `'deflate'`
25
+
26
+ The compression scheme to allow for response compression. Either `gzip` or `deflate`. If not defined, both are allowed and will be used based on the `Accept-Encoding` header. `gzip` is prioritized if this option is not provided and the client provides both in the `Accept-Encoding` header.
27
+
28
+ ### *(optional)* threshold: `number`
29
+
30
+ The minimum size in bytes to compress. Defaults to 1024 bytes.
31
+
32
+ ### *(optional)* contentTypeFilter: `RegExp` | `(contentType: string) => boolean`
33
+
34
+ A `RegExp` or function to determine whether the response should be compressed based on its `Content-Type`. By default, a built-in list of compressible Content-Types is used.
35
+
36
+ You can pass a `RegExp` to compress only matching Content-Types:
37
+
38
+ ```ts
39
+ // Compress only JSON responses
40
+ app.use(compress({ contentTypeFilter: /^application\/json/ }))
41
+ ```
42
+
43
+ Or pass a function for custom logic. The built-in `COMPRESSIBLE_CONTENT_TYPE_REGEX` is also exported so you can extend the default behavior:
44
+
45
+ ```ts
46
+ import {
47
+ compress,
48
+ COMPRESSIBLE_CONTENT_TYPE_REGEX,
49
+ } from 'acrylblends/compress'
50
+
51
+ // Compress the default Content-Types plus a custom one
52
+ app.use(
53
+ compress({
54
+ contentTypeFilter: (type) =>
55
+ COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type) ||
56
+ type === 'application/x-myformat',
57
+ })
58
+ )
59
+ ```
@@ -0,0 +1,78 @@
1
+ # Context Storage Middleware
2
+
3
+ The Context Storage Middleware stores the Acryl `Context` in the `AsyncLocalStorage`, to make it globally accessible.
4
+
5
+ > **Note.** This middleware uses `AsyncLocalStorage`. The runtime should support it.
6
+ >
7
+ > **Cloudflare Workers**: To enable `AsyncLocalStorage`, add the [`nodejs_compat` or `nodejs_als` flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) to your `wrangler.toml` file.
8
+
9
+ ## Import
10
+
11
+ ```ts
12
+ import { Acryl } from 'acrylblends'
13
+ import {
14
+ contextStorage,
15
+ getContext,
16
+ tryGetContext,
17
+ } from 'acrylblends/context-storage'
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ The `getContext()` will return the current Context object if the `contextStorage()` is applied as a middleware.
23
+
24
+ ```ts
25
+ type Env = {
26
+ Variables: {
27
+ message: string
28
+ }
29
+ }
30
+
31
+ const app = new Acryl<Env>()
32
+
33
+ app.use(contextStorage())
34
+
35
+ app.use(async (c, next) => {
36
+ c.set('message', 'Hello!')
37
+ await next()
38
+ })
39
+
40
+ // You can access the variable outside the handler.
41
+ const getMessage = () => {
42
+ return getContext<Env>().var.message
43
+ }
44
+
45
+ app.get('/', (c) => {
46
+ return c.text(getMessage())
47
+ })
48
+ ```
49
+
50
+ On Cloudflare Workers, you can access the bindings outside the handler.
51
+
52
+ ```ts
53
+ type Env = {
54
+ Bindings: {
55
+ KV: KVNamespace
56
+ }
57
+ }
58
+
59
+ const app = new Acryl<Env>()
60
+
61
+ app.use(contextStorage())
62
+
63
+ const setKV = (value: string) => {
64
+ return getContext<Env>().env.KV.put('key', value)
65
+ }
66
+ ```
67
+
68
+ ## tryGetContext
69
+
70
+ `tryGetContext()` works like `getContext()`, but returns `undefined` instead of throwing an error when the context is not available:
71
+
72
+ ```ts
73
+ const context = tryGetContext<Env>()
74
+ if (context) {
75
+ // Context is available
76
+ console.log(context.var.message)
77
+ }
78
+ ```
@@ -0,0 +1,135 @@
1
+ # CORS Middleware
2
+
3
+ There are many use cases of Cloudflare Workers as Web APIs and calling them from external front-end application.
4
+ For them we have to implement CORS, let's do this with middleware as well.
5
+
6
+ ## Import
7
+
8
+ ```ts
9
+ import { Acryl } from 'acrylblends'
10
+ import { cors } from 'acrylblends/cors'
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ const app = new Acryl()
17
+
18
+ // CORS should be called before the route
19
+ app.use('/api/*', cors())
20
+ app.use(
21
+ '/api2/*',
22
+ cors({
23
+ origin: 'http://example.com',
24
+ allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],
25
+ allowMethods: ['POST', 'GET', 'OPTIONS'],
26
+ exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
27
+ maxAge: 600,
28
+ credentials: true,
29
+ })
30
+ )
31
+
32
+ app.all('/api/abc', (c) => {
33
+ return c.json({ success: true })
34
+ })
35
+ app.all('/api2/abc', (c) => {
36
+ return c.json({ success: true })
37
+ })
38
+ ```
39
+
40
+ Multiple origins:
41
+
42
+ ```ts
43
+ app.use(
44
+ '/api3/*',
45
+ cors({
46
+ origin: ['https://example.com', 'https://example.org'],
47
+ })
48
+ )
49
+
50
+ // Or you can use "function"
51
+ app.use(
52
+ '/api4/*',
53
+ cors({
54
+ // `c` is a `Context` object
55
+ origin: (origin, c) => {
56
+ return origin.endsWith('.example.com')
57
+ ? origin
58
+ : 'http://example.com'
59
+ },
60
+ })
61
+ )
62
+ ```
63
+
64
+ Dynamic allowed methods based on origin:
65
+
66
+ ```ts
67
+ app.use(
68
+ '/api5/*',
69
+ cors({
70
+ origin: (origin) =>
71
+ origin === 'https://example.com' ? origin : '*',
72
+ // `c` is a `Context` object
73
+ allowMethods: (origin, c) =>
74
+ origin === 'https://example.com'
75
+ ? ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE']
76
+ : ['GET', 'HEAD'],
77
+ })
78
+ )
79
+ ```
80
+
81
+ ## Options
82
+
83
+ ### *(optional)* origin: `string` | `string[]` | `(origin:string, c:Context) => string`
84
+
85
+ The value of "_Access-Control-Allow-Origin_" CORS header. You can also pass the callback function like `origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com')`. The default is `*`.
86
+
87
+ ### *(optional)* allowMethods: `string[]` | `(origin:string, c:Context) => string[]`
88
+
89
+ The value of "_Access-Control-Allow-Methods_" CORS header. You can also pass a callback function to dynamically determine allowed methods based on the origin. The default is `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'QUERY']`.
90
+
91
+ ### *(optional)* allowHeaders: `string[]`
92
+
93
+ The value of "_Access-Control-Allow-Headers_" CORS header. The default is `[]`.
94
+
95
+ ### *(optional)* maxAge: `number`
96
+
97
+ The value of "_Access-Control-Max-Age_" CORS header.
98
+
99
+ ### *(optional)* credentials: `boolean`
100
+
101
+ The value of "_Access-Control-Allow-Credentials_" CORS header.
102
+
103
+ ### *(optional)* exposeHeaders: `string[]`
104
+
105
+ The value of "_Access-Control-Expose-Headers_" CORS header. The default is `[]`.
106
+
107
+ ## Environment-dependent CORS configuration
108
+
109
+ If you want to adjust CORS configuration according to the execution environment, such as development or production, injecting values from environment variables is convenient as it eliminates the need for the application to be aware of its own execution environment. See the example below for clarification.
110
+
111
+ ```ts
112
+ app.use('*', async (c, next) => {
113
+ const corsMiddlewareHandler = cors({
114
+ origin: c.env.CORS_ORIGIN,
115
+ })
116
+ return corsMiddlewareHandler(c, next)
117
+ })
118
+ ```
119
+
120
+ ## Using with Vite
121
+
122
+ When using Acryl with Vite, you should disable Vite's built-in CORS feature by setting `server.cors` to `false` in your `vite.config.ts`. This prevents conflicts with Acryl's CORS middleware.
123
+
124
+ ```ts
125
+ // vite.config.ts
126
+ import { cloudflare } from '@cloudflare/vite-plugin'
127
+ import { defineConfig } from 'vite'
128
+
129
+ export default defineConfig({
130
+ server: {
131
+ cors: false, // disable Vite's built-in CORS setting
132
+ },
133
+ plugins: [cloudflare()],
134
+ })
135
+ ```
@@ -0,0 +1,105 @@
1
+ # CSRF Protection
2
+
3
+ This middleware protects against CSRF attacks by checking both the `Origin` header and the `Sec-Fetch-Site` header. The request is allowed if either validation passes.
4
+
5
+ The middleware only validates requests that:
6
+
7
+ - Use unsafe HTTP methods (not GET, HEAD, or OPTIONS)
8
+ - Have content types that can be sent by HTML forms (`application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`)
9
+
10
+ Old browsers that do not send `Origin` headers, or environments that use reverse proxies to remove these headers, may not work well. In such environments, use other CSRF token methods.
11
+
12
+ ## Import
13
+
14
+ ```ts
15
+ import { Acryl } from 'acrylblends'
16
+ import { csrf } from 'acrylblends/csrf'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ const app = new Acryl()
23
+
24
+ // Default: both origin and sec-fetch-site validation
25
+ app.use(csrf())
26
+
27
+ // Allow specific origins
28
+ app.use(csrf({ origin: 'https://myapp.example.com' }))
29
+
30
+ // Allow multiple origins
31
+ app.use(
32
+ csrf({
33
+ origin: [
34
+ 'https://myapp.example.com',
35
+ 'https://development.myapp.example.com',
36
+ ],
37
+ })
38
+ )
39
+
40
+ // Allow specific sec-fetch-site values
41
+ app.use(csrf({ secFetchSite: 'same-origin' }))
42
+ app.use(csrf({ secFetchSite: ['same-origin', 'none'] }))
43
+
44
+ // Dynamic origin validation
45
+ // It is strongly recommended that the protocol be verified to ensure a match to `$`.
46
+ // You should *never* do a forward match.
47
+ app.use(
48
+ '*',
49
+ csrf({
50
+ origin: (origin) =>
51
+ /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
52
+ })
53
+ )
54
+
55
+ // Dynamic sec-fetch-site validation
56
+ app.use(
57
+ csrf({
58
+ secFetchSite: (secFetchSite, c) => {
59
+ // Always allow same-origin
60
+ if (secFetchSite === 'same-origin') return true
61
+ // Allow cross-site for webhook endpoints
62
+ if (
63
+ secFetchSite === 'cross-site' &&
64
+ c.req.path.startsWith('/webhook/')
65
+ ) {
66
+ return true
67
+ }
68
+ return false
69
+ },
70
+ })
71
+ )
72
+ ```
73
+
74
+ ## Options
75
+
76
+ ### *(optional)* origin: `string` | `string[]` | `Function`
77
+
78
+ Specify allowed origins for CSRF protection.
79
+
80
+ - **`string`**: Single allowed origin (e.g., `'https://example.com'`)
81
+ - **`string[]`**: Array of allowed origins
82
+ - **`Function`**: Custom handler `(origin: string, context: Context) => boolean` for flexible origin validation and bypass logic
83
+
84
+ **Default**: Only same origin as the request URL
85
+
86
+ The function handler receives the request's `Origin` header value and the request context, allowing for dynamic validation based on request properties like path, headers, or other context data.
87
+
88
+ ### *(optional)* secFetchSite: `string` | `string[]` | `Function`
89
+
90
+ Specify allowed Sec-Fetch-Site header values for CSRF protection using [Fetch Metadata](https://web.dev/articles/fetch-metadata).
91
+
92
+ - **`string`**: Single allowed value (e.g., `'same-origin'`)
93
+ - **`string[]`**: Array of allowed values (e.g., `['same-origin', 'none']`)
94
+ - **`Function`**: Custom handler `(secFetchSite: string, context: Context) => boolean` for flexible validation
95
+
96
+ **Default**: Only allows `'same-origin'`
97
+
98
+ Standard Sec-Fetch-Site values:
99
+
100
+ - `same-origin`: Request from same origin
101
+ - `same-site`: Request from same site (different subdomain)
102
+ - `cross-site`: Request from different site
103
+ - `none`: Request not from a web page (e.g., browser address bar, bookmark)
104
+
105
+ The function handler receives the request's `Sec-Fetch-Site` header value and the request context, enabling dynamic validation based on request properties.
@@ -0,0 +1,54 @@
1
+ # ETag Middleware
2
+
3
+ Using this middleware, you can add ETag headers easily.
4
+
5
+ ## Import
6
+
7
+ ```ts
8
+ import { Acryl } from 'acrylblends'
9
+ import { etag } from 'acrylblends/etag'
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ const app = new Acryl()
16
+
17
+ app.use('/etag/*', etag())
18
+ app.get('/etag/abc', (c) => {
19
+ return c.text('Acryl is cool')
20
+ })
21
+ ```
22
+
23
+ ## The retained headers
24
+
25
+ The 304 Response must include the headers that would have been sent in an equivalent 200 OK response. The default headers are Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
26
+
27
+ If you want to add the header that is sent, you can use `retainedHeaders` option and `RETAINED_304_HEADERS` strings array variable that includes the default headers:
28
+
29
+ ```ts
30
+ import { etag, RETAINED_304_HEADERS } from 'acrylblends/etag'
31
+
32
+ // ...
33
+
34
+ app.use(
35
+ '/etag/*',
36
+ etag({
37
+ retainedHeaders: ['x-message', ...RETAINED_304_HEADERS],
38
+ })
39
+ )
40
+ ```
41
+
42
+ ## Options
43
+
44
+ ### *(optional)* weak: `boolean`
45
+
46
+ Define using or not using a [weak validation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#weak_validation). If `true` is set, then `w/` is added to the prefix of the value. The default is `false`.
47
+
48
+ ### *(optional)* retainedHeaders: `string[]`
49
+
50
+ The headers that you want to retain in the 304 Response.
51
+
52
+ ### *(optional)* generateDigest: `(body: Uint8Array) => ArrayBuffer | Promise<ArrayBuffer>`
53
+
54
+ A custom digest generation function. By default, it uses `SHA-1`. This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one.