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,112 @@
1
+ # ConnInfo Helper
2
+
3
+ The ConnInfo Helper helps you to get the connection information. For example, you can get the client's remote address easily.
4
+
5
+ ## Import
6
+
7
+ Only the Node.js adapter, `acrylblends/node-server`, is available today. The adapters for the other platforms (Cloudflare Workers, Bun, Vercel, AWS Lambda, Netlify and Lambda@Edge) are **not yet available**; their imports are shown for completeness and are not checked by the docs tests.
8
+
9
+ **Cloudflare Workers**
10
+
11
+ ```ts
12
+ import { Acryl } from 'acrylblends'
13
+ import { getConnInfo } from 'acrylblends/cloudflare-workers'
14
+ ```
15
+
16
+ **Bun**
17
+
18
+ ```ts
19
+ import { Acryl } from 'acrylblends'
20
+ import { getConnInfo } from 'acrylblends/bun'
21
+ ```
22
+
23
+ **Vercel**
24
+
25
+ ```ts
26
+ import { Acryl } from 'acrylblends'
27
+ import { getConnInfo } from 'acrylblends/vercel'
28
+ ```
29
+
30
+ **AWS Lambda**
31
+
32
+ ```ts
33
+ import { Acryl } from 'acrylblends'
34
+ import { getConnInfo } from 'acrylblends/aws-lambda'
35
+ ```
36
+
37
+ **Netlify**
38
+
39
+ ```ts
40
+ import { Acryl } from 'acrylblends'
41
+ import { getConnInfo } from 'acrylblends/netlify'
42
+ ```
43
+
44
+ **Lambda@Edge**
45
+
46
+ ```ts
47
+ import { Acryl } from 'acrylblends'
48
+ import { getConnInfo } from 'acrylblends/lambda-edge'
49
+ ```
50
+
51
+ **Node.js**
52
+
53
+ ```ts
54
+ import { Acryl } from 'acrylblends'
55
+ import { getConnInfo } from 'acrylblends/node-server'
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```ts
61
+ const app = new Acryl()
62
+
63
+ app.get('/', (c) => {
64
+ const info = getConnInfo(c) // info is `ConnInfo`
65
+ return c.text(`Your remote address is ${info.remote.address}`)
66
+ })
67
+ ```
68
+
69
+ ## Type Definitions
70
+
71
+ The type definitions of the values that you can get from `getConnInfo()` are the following:
72
+
73
+ ```ts
74
+ type AddressType = 'IPv6' | 'IPv4' | undefined
75
+
76
+ type NetAddrInfo = {
77
+ /**
78
+ * Transport protocol type
79
+ */
80
+ transport?: 'tcp' | 'udp'
81
+ /**
82
+ * Transport port number
83
+ */
84
+ port?: number
85
+
86
+ address?: string
87
+ addressType?: AddressType
88
+ } & (
89
+ | {
90
+ /**
91
+ * Host name such as IP Addr
92
+ */
93
+ address: string
94
+
95
+ /**
96
+ * Host name type
97
+ */
98
+ addressType: AddressType
99
+ }
100
+ | {}
101
+ )
102
+
103
+ /**
104
+ * HTTP Connection information
105
+ */
106
+ interface ConnInfo {
107
+ /**
108
+ * Remote information
109
+ */
110
+ remote: NetAddrInfo
111
+ }
112
+ ```
@@ -0,0 +1,232 @@
1
+ # Cookie Helper
2
+
3
+ The Cookie Helper provides an easy interface to manage cookies, enabling developers to set, parse, and delete cookies seamlessly.
4
+
5
+ ## Import
6
+
7
+ ```ts
8
+ import { Acryl } from 'acrylblends'
9
+ import {
10
+ deleteCookie,
11
+ getCookie,
12
+ getSignedCookie,
13
+ setCookie,
14
+ setSignedCookie,
15
+ generateCookie,
16
+ generateSignedCookie,
17
+ } from 'acrylblends/cookie'
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Regular cookies
23
+
24
+ ```ts
25
+ app.get('/cookie', (c) => {
26
+ setCookie(c, 'cookie_name', 'cookie_value')
27
+ const yummyCookie = getCookie(c, 'cookie_name')
28
+ deleteCookie(c, 'cookie_name')
29
+ const allCookies = getCookie(c)
30
+ // ...
31
+ })
32
+ ```
33
+
34
+ ### Signed cookies
35
+
36
+ **NOTE**: Setting and retrieving signed cookies returns a Promise due to the async nature of the WebCrypto API, which is used to create HMAC SHA-256 signatures.
37
+
38
+ ```ts
39
+ app.get('/signed-cookie', (c) => {
40
+ const secret = 'secret' // make sure it's a large enough string to be secure
41
+
42
+ await setSignedCookie(c, 'cookie_name0', 'cookie_value', secret)
43
+ const fortuneCookie = await getSignedCookie(
44
+ c,
45
+ secret,
46
+ 'cookie_name0'
47
+ )
48
+ deleteCookie(c, 'cookie_name0')
49
+ // `getSignedCookie` will return `false` for a specified cookie if its signature fails verification
50
+ const allSignedCookies = await getSignedCookie(c, secret)
51
+ // ...
52
+ })
53
+ ```
54
+
55
+ > [!NOTE]
56
+ > `getSignedCookie` distinguishes two cases. A cookie that has a signature but fails verification returns `false`. A cookie that does not have a valid signature format is treated as not being a signed cookie at all and returns `undefined` - the same as when the cookie is not present. This rule applies both when retrieving a single cookie by name and when retrieving all signed cookies. Since both `false` and `undefined` are falsy, `if (!value)` handles both cases.
57
+
58
+ ### Cookie Generation
59
+
60
+ `generateCookie` and `generateSignedCookie` functions allow you to create cookie strings directly without setting them in the response headers.
61
+
62
+ #### `generateCookie`
63
+
64
+ ```ts
65
+ // Basic cookie generation
66
+ const cookie = generateCookie('delicious_cookie', 'macha')
67
+ // Returns: 'delicious_cookie=macha; Path=/'
68
+
69
+ // Cookie with options
70
+ const cookie = generateCookie('delicious_cookie', 'macha', {
71
+ path: '/',
72
+ secure: true,
73
+ httpOnly: true,
74
+ domain: 'example.com',
75
+ })
76
+ ```
77
+
78
+ #### `generateSignedCookie`
79
+
80
+ ```ts
81
+ // Basic signed cookie generation
82
+ const signedCookie = await generateSignedCookie(
83
+ 'delicious_cookie',
84
+ 'macha',
85
+ 'secret chocolate chips'
86
+ )
87
+
88
+ // Signed cookie with options
89
+ const signedCookie = await generateSignedCookie(
90
+ 'delicious_cookie',
91
+ 'macha',
92
+ 'secret chocolate chips',
93
+ {
94
+ path: '/',
95
+ secure: true,
96
+ httpOnly: true,
97
+ }
98
+ )
99
+ ```
100
+
101
+ **Note**: Unlike `setCookie` and `setSignedCookie`, these functions only generate the cookie strings. You need to manually set them in headers if needed.
102
+
103
+ ## Options
104
+
105
+ ### `setCookie` & `setSignedCookie`
106
+
107
+ - domain: `string`
108
+ - expires: `Date`
109
+ - httpOnly: `boolean`
110
+ - maxAge: `number`
111
+ - path: `string`
112
+ - secure: `boolean`
113
+ - sameSite: `'Strict'` | `'Lax'` | `'None'`
114
+ - priority: `'Low' | 'Medium' | 'High'`
115
+ - prefix: `secure` | `'host'`
116
+ - partitioned: `boolean`
117
+
118
+ Example:
119
+
120
+ ```ts
121
+ // Regular cookies
122
+ setCookie(c, 'great_cookie', 'banana', {
123
+ path: '/',
124
+ secure: true,
125
+ domain: 'example.com',
126
+ httpOnly: true,
127
+ maxAge: 1000,
128
+ expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
129
+ sameSite: 'Strict',
130
+ })
131
+
132
+ // Signed cookies
133
+ await setSignedCookie(
134
+ c,
135
+ 'fortune_cookie',
136
+ 'lots-of-money',
137
+ 'secret ingredient',
138
+ {
139
+ path: '/',
140
+ secure: true,
141
+ domain: 'example.com',
142
+ httpOnly: true,
143
+ maxAge: 1000,
144
+ expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
145
+ sameSite: 'Strict',
146
+ }
147
+ )
148
+ ```
149
+
150
+ ### `deleteCookie`
151
+
152
+ - path: `string`
153
+ - secure: `boolean`
154
+ - domain: `string`
155
+
156
+ Example:
157
+
158
+ ```ts
159
+ deleteCookie(c, 'banana', {
160
+ path: '/',
161
+ secure: true,
162
+ domain: 'example.com',
163
+ })
164
+ ```
165
+
166
+ `deleteCookie` returns the deleted value:
167
+
168
+ ```ts
169
+ const deletedCookie = deleteCookie(c, 'delicious_cookie')
170
+ ```
171
+
172
+ ## `__Secure-` and `__Host-` prefix
173
+
174
+ The Cookie helper supports `__Secure-` and `__Host-` prefixes for cookie names.
175
+
176
+ If you want to verify if the cookie name has a prefix, specify the prefix option.
177
+
178
+ ```ts
179
+ const securePrefixCookie = getCookie(c, 'yummy_cookie', 'secure')
180
+ const hostPrefixCookie = getCookie(c, 'yummy_cookie', 'host')
181
+
182
+ const securePrefixSignedCookie = await getSignedCookie(
183
+ c,
184
+ secret,
185
+ 'fortune_cookie',
186
+ 'secure'
187
+ )
188
+ const hostPrefixSignedCookie = await getSignedCookie(
189
+ c,
190
+ secret,
191
+ 'fortune_cookie',
192
+ 'host'
193
+ )
194
+ ```
195
+
196
+ Also, if you wish to specify a prefix when setting the cookie, specify a value for the prefix option.
197
+
198
+ ```ts
199
+ setCookie(c, 'delicious_cookie', 'macha', {
200
+ prefix: 'secure', // or `host`
201
+ })
202
+
203
+ await setSignedCookie(
204
+ c,
205
+ 'delicious_cookie',
206
+ 'macha',
207
+ 'secret choco chips',
208
+ {
209
+ prefix: 'secure', // or `host`
210
+ }
211
+ )
212
+ ```
213
+
214
+ ## Following the best practices
215
+
216
+ A New Cookie RFC (a.k.a cookie-bis) and CHIPS include some best practices for Cookie settings that developers should follow.
217
+
218
+ - [RFC6265bis-13](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13)
219
+ - `Max-Age`/`Expires` limitation
220
+ - `__Host-`/`__Secure-` prefix limitation
221
+ - [CHIPS-01](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html)
222
+ - `Partitioned` limitation
223
+
224
+ Acryl is following the best practices.
225
+ The cookie helper will throw an `Error` when parsing cookies under the following conditions:
226
+
227
+ - The cookie name starts with `__Secure-`, but `secure` option is not set.
228
+ - The cookie name starts with `__Host-`, but `secure` option is not set.
229
+ - The cookie name starts with `__Host-`, but `path` is not `/`.
230
+ - The cookie name starts with `__Host-`, but `domain` is set.
231
+ - The `maxAge` option value is greater than 400 days.
232
+ - The `expires` option value is 400 days later than the current time.
@@ -0,0 +1,285 @@
1
+ # css Helper
2
+
3
+ > **Not yet available.** The CSS helper (`acrylblends/css`) 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 CSS helper - `acrylblends/css` - is Acryl's built-in CSS in JS(X).
7
+
8
+ You can write CSS in JSX in a JavaScript template literal named `css`. The return value of `css` will be the class name, which is set to the value of the class attribute. The `<Style />` component will then contain the value of the CSS.
9
+
10
+ ## Import
11
+
12
+ ```ts
13
+ import { Acryl } from 'acrylblends'
14
+ import { css, cx, keyframes, Style, createCssContext } from 'acrylblends/css'
15
+ ```
16
+
17
+ ## `css` *(Experimental)*
18
+
19
+ You can write CSS in the `css` template literal. In this case, it uses `headerClass` as a value of the `class` attribute. Don't forget to add `<Style />` as it contains the CSS content.
20
+
21
+ ```ts
22
+ app.get('/', (c) => {
23
+ const headerClass = css`
24
+ background-color: orange;
25
+ color: white;
26
+ padding: 1rem;
27
+ `
28
+ return c.html(
29
+ <html>
30
+ <head>
31
+ <Style />
32
+ </head>
33
+ <body>
34
+ <h1 class={headerClass}>Hello!</h1>
35
+ </body>
36
+ </html>
37
+ )
38
+ })
39
+ ```
40
+
41
+ You can style pseudo-classes like `:hover` by using the [nesting selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector), `&`:
42
+
43
+ ```ts
44
+ const buttonClass = css`
45
+ background-color: #fff;
46
+ &:hover {
47
+ background-color: red;
48
+ }
49
+ `
50
+ ```
51
+
52
+ ### Extending
53
+
54
+ You can extend the CSS definition by embedding the class name.
55
+
56
+ ```tsx
57
+ const baseClass = css`
58
+ color: white;
59
+ background-color: blue;
60
+ `
61
+
62
+ const header1Class = css`
63
+ ${baseClass}
64
+ font-size: 3rem;
65
+ `
66
+
67
+ const header2Class = css`
68
+ ${baseClass}
69
+ font-size: 2rem;
70
+ `
71
+ ```
72
+
73
+ In addition, the syntax of `${baseClass} {}` enables nesting classes.
74
+
75
+ ```tsx
76
+ const headerClass = css`
77
+ color: white;
78
+ background-color: blue;
79
+ `
80
+ const containerClass = css`
81
+ ${headerClass} {
82
+ h1 {
83
+ font-size: 3rem;
84
+ }
85
+ }
86
+ `
87
+ return c.render(
88
+ <div class={containerClass}>
89
+ <header class={headerClass}>
90
+ <h1>Hello!</h1>
91
+ </header>
92
+ </div>
93
+ )
94
+ ```
95
+
96
+ ### Global styles
97
+
98
+ A pseudo-selector called `:-acrylblends-global` allows you to define global styles.
99
+
100
+ ```tsx
101
+ const globalClass = css`
102
+ :-acrylblends-global {
103
+ html {
104
+ font-family: Arial, Helvetica, sans-serif;
105
+ }
106
+ }
107
+ `
108
+
109
+ return c.render(
110
+ <div class={globalClass}>
111
+ <h1>Hello!</h1>
112
+ <p>Today is a good day.</p>
113
+ </div>
114
+ )
115
+ ```
116
+
117
+ Or you can write CSS in the `<Style />` component with the `css` literal.
118
+
119
+ ```tsx
120
+ export const renderer = jsxRenderer(({ children, title }) => {
121
+ return (
122
+ <html>
123
+ <head>
124
+ <Style>{css`
125
+ html {
126
+ font-family: Arial, Helvetica, sans-serif;
127
+ }
128
+ `}</Style>
129
+ <title>{title}</title>
130
+ </head>
131
+ <body>
132
+ <div>{children}</div>
133
+ </body>
134
+ </html>
135
+ )
136
+ })
137
+ ```
138
+
139
+ ## `keyframes` *(Experimental)*
140
+
141
+ You can use `keyframes` to write the contents of `@keyframes`. In this case, `fadeInAnimation` will be the name of the animation.
142
+
143
+ ```tsx
144
+ const fadeInAnimation = keyframes`
145
+ from {
146
+ opacity: 0;
147
+ }
148
+ to {
149
+ opacity: 1;
150
+ }
151
+ `
152
+ const headerClass = css`
153
+ animation-name: ${fadeInAnimation};
154
+ animation-duration: 2s;
155
+ `
156
+ const Header = () => <a class={headerClass}>Hello!</a>
157
+ ```
158
+
159
+ ## `cx` *(Experimental)*
160
+
161
+ The `cx` composites the two class names.
162
+
163
+ ```tsx
164
+ const buttonClass = css`
165
+ border-radius: 10px;
166
+ `
167
+ const primaryClass = css`
168
+ background: orange;
169
+ `
170
+ const Button = () => (
171
+ <a class={cx(buttonClass, primaryClass)}>Click!</a>
172
+ )
173
+ ```
174
+
175
+ It can also compose simple strings.
176
+
177
+ ```tsx
178
+ const Header = () => <a class={cx('h1', primaryClass)}>Hi</a>
179
+ ```
180
+
181
+ ## Usage in combination with [Secure Headers](../middleware/builtin/secure-headers.md) middleware
182
+
183
+ If you want to use the CSS helpers in combination with the [Secure Headers](../middleware/builtin/secure-headers.md) middleware, you can add the [`nonce` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) to the `<Style nonce={c.get('secureHeadersNonce')} />` to avoid Content-Security-Policy caused by the CSS helpers.
184
+
185
+ ```tsx
186
+ import { secureHeaders, NONCE } from 'acrylblends/secure-headers'
187
+
188
+ app.get(
189
+ '*',
190
+ secureHeaders({
191
+ contentSecurityPolicy: {
192
+ // Set the pre-defined nonce value to `styleSrc`:
193
+ styleSrc: [NONCE],
194
+ },
195
+ })
196
+ )
197
+
198
+ app.get('/', (c) => {
199
+ const headerClass = css`
200
+ background-color: orange;
201
+ color: white;
202
+ padding: 1rem;
203
+ `
204
+ return c.html(
205
+ <html>
206
+ <head>
207
+ {/* Set the `nonce` attribute on the CSS helpers `style` and `script` elements */}
208
+ <Style nonce={c.get('secureHeadersNonce')} />
209
+ </head>
210
+ <body>
211
+ <h1 class={headerClass}>Hello!</h1>
212
+ </body>
213
+ </html>
214
+ )
215
+ })
216
+ ```
217
+
218
+ ## `createCssContext` *(Experimental)*
219
+
220
+ `createCssContext` creates CSS helper functions (`css`, `cx`, `keyframes`, `viewTransition`, `Style`) with a custom context. You can use it to customize the style element ID and the generated class names.
221
+
222
+ ```ts
223
+ import { createCssContext } from 'acrylblends/css'
224
+
225
+ const { css, cx, keyframes, Style } = createCssContext({
226
+ id: 'my-app',
227
+ })
228
+ ```
229
+
230
+ ### `classNameSlug`
231
+
232
+ By default, CSS class names are generated in the format `css-1234567890`. You can customize this by passing a `classNameSlug` function.
233
+
234
+ The function receives three arguments:
235
+
236
+ - `hash` - the default generated class name (e.g. `css-1234567890`)
237
+ - `label` - extracted from a `/* comment */` at the start of the CSS template (empty string if none)
238
+ - `css` - the minified CSS string
239
+
240
+ ```ts
241
+ const { css, Style } = createCssContext({
242
+ id: 'my-styles',
243
+ classNameSlug: (hash, label) => (label ? `h-${label}` : hash),
244
+ })
245
+
246
+ const heroClass = css`
247
+ /* hero-section */
248
+ background: blue;
249
+ `
250
+ // Generated class name: "h-hero-section"
251
+ ```
252
+
253
+ ### `onInvalidSlug`
254
+
255
+ If the `classNameSlug` function returns an invalid CSS class name, a warning is logged by default. You can customize this behavior with `onInvalidSlug`.
256
+
257
+ ```ts
258
+ const { css, Style } = createCssContext({
259
+ id: 'my-styles',
260
+ classNameSlug: (hash, label) => label || hash,
261
+ onInvalidSlug: (slug) => {
262
+ throw new Error(`Invalid CSS class name: ${slug}`)
263
+ },
264
+ })
265
+ ```
266
+
267
+ ## Security
268
+
269
+ The CSS helpers are CSS-authoring APIs: like other CSS-in-JS libraries, interpolated values are inserted as **raw CSS**. They block breaking out into HTML (quotes, backslashes, and `</`), but `{`, `}`, and `;` pass through since they are valid CSS.
270
+
271
+ **Warning.**
272
+
273
+ Treat the CSS helpers like other raw sinks (`html`, `raw`, `rawCssString`): **don't pass untrusted input into them directly.** Doing so allows CSS injection. Validate against an allowlist first.
274
+
275
+ ```tsx
276
+ const ALLOWED_COLORS = ['red', 'green', 'blue']
277
+ const color = ALLOWED_COLORS.includes(input) ? input : 'black'
278
+ const headerClass = css`
279
+ color: ${color};
280
+ `
281
+ ```
282
+
283
+ ## Tips
284
+
285
+ If you use VS Code, you can use [vscode-styled-components](https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components) for Syntax highlighting and IntelliSense for CSS tagged literals.
@@ -0,0 +1,67 @@
1
+ # Dev Helper
2
+
3
+ > **Not yet available.** The dev helper (`acrylblends/dev`) 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
+ Dev Helper provides useful methods you can use in development.
7
+
8
+ ```ts
9
+ import { Acryl } from 'acrylblends'
10
+ import { getRouterName, showRoutes } from 'acrylblends/dev'
11
+ ```
12
+
13
+ ## `getRouterName()`
14
+
15
+ You can get the name of the currently used router with `getRouterName()`.
16
+
17
+ ```ts
18
+ const app = new Acryl()
19
+
20
+ // ...
21
+
22
+ console.log(getRouterName(app))
23
+ ```
24
+
25
+ ## `showRoutes()`
26
+
27
+ `showRoutes()` function displays the registered routes in your console.
28
+
29
+ Consider an application like the following:
30
+
31
+ ```ts
32
+ const app = new Acryl().basePath('/v1')
33
+
34
+ app.get('/posts', (c) => {
35
+ // ...
36
+ })
37
+
38
+ app.get('/posts/:id', (c) => {
39
+ // ...
40
+ })
41
+
42
+ app.post('/posts', (c) => {
43
+ // ...
44
+ })
45
+
46
+ showRoutes(app, {
47
+ verbose: true,
48
+ })
49
+ ```
50
+
51
+ When this application starts running, the routes will be shown in your console as follows:
52
+
53
+ ```txt
54
+ GET /v1/posts
55
+ GET /v1/posts/:id
56
+ POST /v1/posts
57
+ ```
58
+
59
+ ## Options
60
+
61
+ ### *(optional)* verbose: `boolean`
62
+
63
+ When set to `true`, it displays verbose information.
64
+
65
+ ### *(optional)* colorize: `boolean`
66
+
67
+ When set to `false`, the output will not be colored.