acrylblends 0.7.3 → 0.7.4
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/docs/framework/README.md +4 -3
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/getting-started/aws-lambda.md +115 -0
- package/docs/framework/getting-started/bun.md +1 -3
- package/docs/framework/getting-started/cloudflare-pages.md +53 -0
- package/docs/framework/getting-started/cloudflare-workers.md +67 -0
- package/docs/framework/getting-started/lambda-edge.md +41 -0
- package/docs/framework/getting-started/netlify.md +25 -0
- package/docs/framework/getting-started/other-runtimes.md +20 -9
- package/docs/framework/getting-started/service-worker.md +31 -0
- package/docs/framework/getting-started/vercel.md +28 -0
- package/docs/framework/guides/create-acrylblends.md +40 -2
- package/docs/framework/guides/faq.md +2 -2
- package/docs/framework/helpers/conninfo.md +1 -1
- package/docs/framework/helpers/ssg.md +1 -1
- package/docs/framework/helpers/streaming.md +3 -2
- package/docs/framework/helpers/websocket.md +2 -2
- package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
- package/docs/framework/upstream.json +3 -3
- package/lib/aws-lambda.d.ts +324 -0
- package/lib/aws-lambda.js +357 -0
- package/lib/bun.d.ts +75 -0
- package/lib/bun.js +134 -0
- package/lib/cloudflare-pages.d.ts +58 -0
- package/lib/cloudflare-pages.js +78 -0
- package/lib/cloudflare-workers.d.ts +21 -0
- package/lib/cloudflare-workers.js +80 -0
- package/lib/index-CseujaYI.d.ts +31 -0
- package/lib/index-DaJtL7ci.d.ts +156 -0
- package/lib/lambda-edge.d.ts +92 -0
- package/lib/lambda-edge.js +105 -0
- package/lib/netlify.d.ts +29 -0
- package/lib/netlify.js +30 -0
- package/lib/node-server/register.js +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.js +3 -3
- package/lib/serve-static-Cp5adzGo.js +77 -0
- package/lib/serve-static.d.ts +1 -30
- package/lib/serve-static.js +1 -76
- package/lib/service-worker.d.ts +48 -0
- package/lib/service-worker.js +38 -0
- package/lib/ssg.d.ts +1 -155
- package/lib/vercel.d.ts +10 -0
- package/lib/vercel.js +9 -0
- package/package.json +33 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/upstream.json +1 -1
- package/plugins/acryl-router/src/core/adapter/aws-lambda/conninfo.ts +73 -0
- package/plugins/acryl-router/src/core/adapter/aws-lambda/handler.ts +682 -0
- package/plugins/acryl-router/src/core/adapter/aws-lambda/index.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/aws-lambda/types.ts +173 -0
- package/plugins/acryl-router/src/core/adapter/bun/conninfo.ts +43 -0
- package/plugins/acryl-router/src/core/adapter/bun/index.ts +11 -0
- package/plugins/acryl-router/src/core/adapter/bun/serve-static.ts +32 -0
- package/plugins/acryl-router/src/core/adapter/bun/server.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/bun/ssg.ts +26 -0
- package/plugins/acryl-router/src/core/adapter/bun/websocket.ts +113 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-pages/conninfo.ts +26 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-pages/handler.ts +122 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-pages/index.ts +11 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/conninfo.ts +7 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/index.ts +8 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/serve-static-module.ts +12 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/serve-static.ts +41 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/utils.ts +50 -0
- package/plugins/acryl-router/src/core/adapter/cloudflare-workers/websocket.ts +52 -0
- package/plugins/acryl-router/src/core/adapter/lambda-edge/conninfo.ts +15 -0
- package/plugins/acryl-router/src/core/adapter/lambda-edge/handler.ts +239 -0
- package/plugins/acryl-router/src/core/adapter/lambda-edge/index.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/netlify/conninfo.ts +57 -0
- package/plugins/acryl-router/src/core/adapter/netlify/handler.ts +10 -0
- package/plugins/acryl-router/src/core/adapter/netlify/index.ts +6 -0
- package/plugins/acryl-router/src/core/adapter/netlify/mod.ts +2 -0
- package/plugins/acryl-router/src/core/adapter/service-worker/handler.ts +37 -0
- package/plugins/acryl-router/src/core/adapter/service-worker/index.ts +36 -0
- package/plugins/acryl-router/src/core/adapter/service-worker/types.ts +14 -0
- package/plugins/acryl-router/src/core/adapter/vercel/conninfo.ts +8 -0
- package/plugins/acryl-router/src/core/adapter/vercel/handler.ts +8 -0
- package/plugins/acryl-router/src/core/adapter/vercel/index.ts +7 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/templates/todo/upstream.json +1 -1
- package/lib/{serve-static-B14MF1Va.js → serve-static-B849HlX2.js} +1 -1
- package/lib/{transpile-CTnWof4z.js → transpile-QUK9b3eG.js} +1 -1
package/docs/framework/README.md
CHANGED
|
@@ -35,12 +35,12 @@ The starter asks which template you want (`nodejs`, `bun` (experimental), or `ac
|
|
|
35
35
|
|
|
36
36
|
## Runtimes
|
|
37
37
|
|
|
38
|
-
Verified: Node.js. Bun is experimental.
|
|
38
|
+
Verified: Node.js. Bun is experimental (its adapter is checked on Bun). Adapters for AWS Lambda, Lambda@Edge, Cloudflare Workers and Pages, Vercel, Netlify and Service Workers are part of the package, with a guide each on [Other runtimes](getting-started/other-runtimes.md); they are tested with the platforms' own events and have not been deployed by the ACRYL team.
|
|
39
39
|
|
|
40
40
|
## Not yet available
|
|
41
41
|
|
|
42
|
-
Some parts of the documented API surface are not part of the package yet, and their pages say so
|
|
43
|
-
They are enabled as they are
|
|
42
|
+
Some parts of the documented API surface are not part of the package yet, and their pages say so: the Vite plugin for static site generation, a registry of third-party middleware, and benchmark results.
|
|
43
|
+
They are enabled as they are built.
|
|
44
44
|
|
|
45
45
|
## Where to read next
|
|
46
46
|
|
|
@@ -49,6 +49,7 @@ They are enabled as they are ported.
|
|
|
49
49
|
| Run something in five minutes | [Getting started](getting-started/basic.md) |
|
|
50
50
|
| Run the app as a desktop app | [Desktop](getting-started/desktop.md) |
|
|
51
51
|
| Run it on a server | [Deploy](getting-started/deploy.md) |
|
|
52
|
+
| Run it on a hosting platform | [Other runtimes](getting-started/other-runtimes.md): [AWS Lambda](getting-started/aws-lambda.md), [Cloudflare Workers](getting-started/cloudflare-workers.md), [Vercel](getting-started/vercel.md), [Netlify](getting-started/netlify.md) |
|
|
52
53
|
| Look up a call | [API](api/index.md), [Routing](api/routing.md), [Context](api/context.md), [Request](api/request.md), [API summary](api/summary.md) |
|
|
53
54
|
| Understand the design | [Concepts](concepts/motivation.md) |
|
|
54
55
|
| Copy a pattern | [Guides](guides/best-practices.md), [Validation](guides/validation.md), [Testing](guides/testing.md), [Forms, sessions and a database](guides/forms-and-sessions.md), [Client components without a bundler](guides/client-modules.md) |
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# Generated copy
|
|
2
2
|
|
|
3
|
-
This folder is a copy of `docs/framework` of acryldev/acryl at commit `
|
|
3
|
+
This folder is a copy of `docs/framework` of acryldev/acryl at commit `eada6fb06a5cafb6c2c92a97effeba567dbd44ba`, made by `scripts/sync-framework.mjs`. Do not edit it here: change the text in ACRYL and sync again. `pnpm run verify:framework` fails when a page differs from the recorded digest.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# AWS Lambda
|
|
2
|
+
|
|
3
|
+
An Acryl app runs on AWS Lambda behind API Gateway (REST API and HTTP API), an Application Load Balancer, a Lambda Function URL or VPC Lattice. `handle(app)` from `acrylblends/aws-lambda` turns the app into the Lambda handler: it builds a `Request` from the event, calls `app.fetch`, and returns the result in the shape the event source expects.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and the framework's release check calls it with API Gateway v1 and v2 and ALB events. It has not been deployed to an AWS account by the ACRYL team: try your first deploy with a test stage.
|
|
6
|
+
|
|
7
|
+
## Install and write the handler
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install acrylblends
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Acryl } from 'acrylblends'
|
|
15
|
+
import { handle } from 'acrylblends/aws-lambda'
|
|
16
|
+
|
|
17
|
+
const app = new Acryl()
|
|
18
|
+
|
|
19
|
+
app.get('/', (c) => c.text('Hello from Lambda'))
|
|
20
|
+
app.get('/json', (c) => c.json({ message: 'Hello JSON' }))
|
|
21
|
+
|
|
22
|
+
export const handler = handle(app)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Bundle the file to one JavaScript file for the `nodejs22.x` (or newer) runtime with your tool of choice (esbuild is common), upload it, and set the handler to `index.handler` (the file name, a dot, the export).
|
|
26
|
+
|
|
27
|
+
## The event and the Lambda context
|
|
28
|
+
|
|
29
|
+
Both are in the bindings of the request:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { Acryl } from 'acrylblends'
|
|
33
|
+
import type { LambdaContext, LambdaEvent } from 'acrylblends/aws-lambda'
|
|
34
|
+
|
|
35
|
+
type Bindings = {
|
|
36
|
+
event: LambdaEvent
|
|
37
|
+
lambdaContext: LambdaContext
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const app = new Acryl<{ Bindings: Bindings }>()
|
|
41
|
+
|
|
42
|
+
app.get('/info', (c) => {
|
|
43
|
+
return c.json({
|
|
44
|
+
isBase64Encoded: c.env.event.isBase64Encoded,
|
|
45
|
+
awsRequestId: c.env.lambdaContext.awsRequestId,
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`c.env.requestContext` holds the request context of the event (API Gateway, ALB or Lattice). The client's address is available through the [ConnInfo helper](../helpers/conninfo.md): `getConnInfo(c).remote.address`.
|
|
51
|
+
|
|
52
|
+
## Binary responses
|
|
53
|
+
|
|
54
|
+
A response is returned base64 encoded when its content type is binary (images, audio, video, PDF, archives and other non-text types) or it has a content encoding such as `gzip`. To decide differently, pass `isContentTypeBinary`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { Acryl } from 'acrylblends'
|
|
58
|
+
import { defaultIsContentTypeBinary, handle } from 'acrylblends/aws-lambda'
|
|
59
|
+
|
|
60
|
+
const app = new Acryl()
|
|
61
|
+
|
|
62
|
+
export const handler = handle(app, {
|
|
63
|
+
isContentTypeBinary: (contentType) =>
|
|
64
|
+
defaultIsContentTypeBinary(contentType) || contentType.startsWith('application/vnd.api+octet'),
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Response streaming
|
|
69
|
+
|
|
70
|
+
On a Function URL with `RESPONSE_STREAM` invoke mode, use `streamHandle` instead of `handle`: the body is written to the response stream as the app produces it, so [streaming helpers](../helpers/streaming.md) (SSE, `stream`, `streamText`) reach the client as they are written. It needs the `awslambda` global, which only the Lambda runtime provides.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { Acryl } from 'acrylblends'
|
|
74
|
+
import { streamHandle } from 'acrylblends/aws-lambda'
|
|
75
|
+
|
|
76
|
+
const app = new Acryl()
|
|
77
|
+
|
|
78
|
+
export const handler = streamHandle(app)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Try it without AWS
|
|
82
|
+
|
|
83
|
+
A handler is a function from an event to a result, so it can be called locally with a sample event:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { Acryl } from 'acrylblends'
|
|
87
|
+
import { handle } from 'acrylblends/aws-lambda'
|
|
88
|
+
|
|
89
|
+
const app = new Acryl().get('/', (c) => c.text('Hello from Lambda'))
|
|
90
|
+
|
|
91
|
+
const result = await handle(app)({
|
|
92
|
+
version: '2.0',
|
|
93
|
+
routeKey: '$default',
|
|
94
|
+
rawPath: '/',
|
|
95
|
+
rawQueryString: '',
|
|
96
|
+
headers: { host: 'example.execute-api.eu-west-1.amazonaws.com' },
|
|
97
|
+
requestContext: {
|
|
98
|
+
accountId: '1',
|
|
99
|
+
apiId: 'example',
|
|
100
|
+
authentication: null,
|
|
101
|
+
authorizer: {},
|
|
102
|
+
domainName: 'example.execute-api.eu-west-1.amazonaws.com',
|
|
103
|
+
domainPrefix: 'example',
|
|
104
|
+
http: { method: 'GET', path: '/', protocol: 'HTTP/1.1', sourceIp: '203.0.113.9', userAgent: 'test' },
|
|
105
|
+
requestId: 'r',
|
|
106
|
+
routeKey: '$default',
|
|
107
|
+
stage: '$default',
|
|
108
|
+
time: '25/Oct/2026:12:00:00 +0000',
|
|
109
|
+
timeEpoch: 0,
|
|
110
|
+
},
|
|
111
|
+
body: null,
|
|
112
|
+
isBase64Encoded: false,
|
|
113
|
+
})
|
|
114
|
+
console.log(result.statusCode, result.body) // 200 Hello from Lambda
|
|
115
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Bun
|
|
2
2
|
|
|
3
|
-
> **Experimental.** Bun is not a verified runtime yet: the Acryl application code is the same, but the `bun` template and this guide are not covered by the release checks. Node.js is the verified runtime.
|
|
3
|
+
> **Experimental.** Bun is not a verified runtime yet: the Acryl application code is the same, and the Bun adapter (`acrylblends/bun`) is checked on Bun in the framework's release check (a static file, the client address, a WebSocket echo), but the `bun` template and this guide are not covered by the release checks. Node.js is the verified runtime.
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[Bun](https://bun.com) is another JavaScript runtime. It's not Node.js. Bun includes a transcompiler, so we can write the code in TypeScript or plain JavaScript.
|
|
@@ -94,8 +94,6 @@ export default {
|
|
|
94
94
|
|
|
95
95
|
To serve static files, use `serveStatic` from `acrylblends/bun`.
|
|
96
96
|
|
|
97
|
-
> **Not yet available.** The Bun adapter, `acrylblends/bun`, is not part of acrylblends yet. The code below shows the planned usage.
|
|
98
|
-
|
|
99
97
|
```ts
|
|
100
98
|
import { serveStatic } from 'acrylblends/bun'
|
|
101
99
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Cloudflare Pages
|
|
2
|
+
|
|
3
|
+
Cloudflare Pages Functions run code next to a static site. `handle(app)` from `acrylblends/cloudflare-pages` turns an Acryl app into a Pages Function, and `handleMiddleware` turns a middleware into a Pages middleware.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and the framework's release check calls it with a Pages event context. It has not been deployed to Cloudflare by the ACRYL team. Cloudflare recommends Workers with static assets for new projects (see [Cloudflare Workers](cloudflare-workers.md)).
|
|
6
|
+
|
|
7
|
+
## A catch-all function
|
|
8
|
+
|
|
9
|
+
Put the file in `functions/[[route]].ts` so it answers every path that is not a static file:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Acryl } from 'acrylblends'
|
|
13
|
+
import { handle } from 'acrylblends/cloudflare-pages'
|
|
14
|
+
|
|
15
|
+
const app = new Acryl().basePath('/api')
|
|
16
|
+
|
|
17
|
+
app.get('/hello', (c) => c.json({ message: 'Hello from Pages Functions' }))
|
|
18
|
+
|
|
19
|
+
export const onRequest = handle(app)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`basePath('/api')` is for a function at `functions/api/[[route]].ts`.
|
|
23
|
+
|
|
24
|
+
## The event context and the bindings
|
|
25
|
+
|
|
26
|
+
The Pages event context (`env`, `params`, `data`, `waitUntil`, `next`) is `c.env.eventContext`; the environment bindings are `c.env` as in Workers:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Acryl } from 'acrylblends'
|
|
30
|
+
import type { EventContext } from 'acrylblends/cloudflare-pages'
|
|
31
|
+
|
|
32
|
+
type Bindings = { MY_KV: unknown; eventContext: EventContext }
|
|
33
|
+
|
|
34
|
+
const app = new Acryl<{ Bindings: Bindings }>()
|
|
35
|
+
|
|
36
|
+
app.get('/params', (c) => c.json(c.env.eventContext.params))
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Middleware
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { handleMiddleware } from 'acrylblends/cloudflare-pages'
|
|
43
|
+
|
|
44
|
+
// functions/_middleware.ts
|
|
45
|
+
export const onRequest = handleMiddleware(async (c, next) => {
|
|
46
|
+
await next()
|
|
47
|
+
c.res.headers.set('x-checked', 'yes')
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Static files
|
|
52
|
+
|
|
53
|
+
In [advanced mode](https://developers.cloudflare.com/pages/platform/functions/advanced-mode/) (a `_worker.js` that answers every request), `serveStatic()` from `acrylblends/cloudflare-pages` answers a request from the Pages `ASSETS` binding and falls through to the app's not-found handler when there is no such file. The client's address is available through the [ConnInfo helper](../helpers/conninfo.md).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Cloudflare Workers
|
|
2
|
+
|
|
3
|
+
A Cloudflare Worker is a module that exports a `fetch` handler, which is what an Acryl app has: `export default app` is a Worker. The adapter `acrylblends/cloudflare-workers` adds the pieces that need Cloudflare: the client's address, WebSockets, and (deprecated) serving static files from Workers KV.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and a Worker with these routes (`export default app`, bindings from `vars`, `getConnInfo`, `waitUntil`) has been run in `workerd` with `wrangler dev --local`, which needs no Cloudflare account. The ACRYL team has not deployed to Cloudflare itself: run `wrangler dev` first.
|
|
6
|
+
|
|
7
|
+
## A Worker
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Acryl } from 'acrylblends'
|
|
11
|
+
|
|
12
|
+
const app = new Acryl()
|
|
13
|
+
|
|
14
|
+
app.get('/', (c) => c.text('Hello Cloudflare Workers!'))
|
|
15
|
+
|
|
16
|
+
export default app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
With Wrangler: a `wrangler.jsonc` naming the entry file and a compatibility date (`"main": "src/index.ts"`, `"compatibility_date": "2026-01-01"`), then `npx wrangler dev` to run it locally and `npx wrangler deploy` to publish it.
|
|
20
|
+
|
|
21
|
+
## Bindings and the execution context
|
|
22
|
+
|
|
23
|
+
Environment variables, secrets, KV namespaces, D1 databases and R2 buckets arrive as `c.env`; the `ExecutionContext` is `c.executionCtx`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Acryl } from 'acrylblends'
|
|
27
|
+
|
|
28
|
+
type Bindings = {
|
|
29
|
+
MY_SECRET: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const app = new Acryl<{ Bindings: Bindings }>()
|
|
33
|
+
|
|
34
|
+
app.get('/', (c) => {
|
|
35
|
+
c.executionCtx.waitUntil(Promise.resolve()) // work that may finish after the response
|
|
36
|
+
return c.text(c.env.MY_SECRET ? 'configured' : 'missing')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default app
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The client's address
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { Acryl } from 'acrylblends'
|
|
46
|
+
import { getConnInfo } from 'acrylblends/cloudflare-workers'
|
|
47
|
+
|
|
48
|
+
const app = new Acryl()
|
|
49
|
+
|
|
50
|
+
app.get('/ip', (c) => c.text(getConnInfo(c).remote.address ?? 'unknown'))
|
|
51
|
+
|
|
52
|
+
export default app
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
It reads the `cf-connecting-ip` header that Cloudflare sets. See the [ConnInfo helper](../helpers/conninfo.md).
|
|
56
|
+
|
|
57
|
+
## WebSockets
|
|
58
|
+
|
|
59
|
+
`upgradeWebSocket` from `acrylblends/cloudflare-workers` accepts the upgrade with Cloudflare's `WebSocketPair`. Cloudflare does not give an `onOpen` event. See the [WebSocket helper](../helpers/websocket.md).
|
|
60
|
+
|
|
61
|
+
## Static files
|
|
62
|
+
|
|
63
|
+
Prefer Cloudflare's static assets (the `assets` setting of Wrangler), which serve files before your code runs. `serveStatic` from `acrylblends/cloudflare-workers`, which reads files from Workers KV (`__STATIC_CONTENT`), is deprecated by Cloudflare's move to static assets and is kept for existing Workers Sites projects. Cloudflare Pages is the other option: see [Cloudflare Pages](cloudflare-pages.md).
|
|
64
|
+
|
|
65
|
+
## Service Worker syntax
|
|
66
|
+
|
|
67
|
+
A Worker written in the old non-module syntax (`addEventListener('fetch', ...)`) uses `fire` from `acrylblends/service-worker`: see [Service Worker](service-worker.md).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Lambda@Edge
|
|
2
|
+
|
|
3
|
+
An Acryl app can answer CloudFront requests from Lambda@Edge. `handle(app)` from `acrylblends/lambda-edge` turns the app into the edge function: it builds a `Request` from the CloudFront event, calls `app.fetch`, and returns a CloudFront response. A viewer-request or origin-request function that answers by itself (a redirect, an HTML page, a JSON answer) is the typical use.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and the framework's release check calls it with a CloudFront viewer-request event. It has not been deployed to AWS by the ACRYL team.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { handle } from 'acrylblends/lambda-edge'
|
|
10
|
+
|
|
11
|
+
const app = new Acryl()
|
|
12
|
+
|
|
13
|
+
app.get('/', (c) => c.text('Hello from the edge'))
|
|
14
|
+
|
|
15
|
+
export const handler = handle(app)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Lambda@Edge has its own limits: the function must be created in `us-east-1`, has no environment variables, and its size and time limits are lower than Lambda's; check them in the AWS documentation when you choose between this adapter and [AWS Lambda](aws-lambda.md).
|
|
19
|
+
|
|
20
|
+
## The CloudFront event
|
|
21
|
+
|
|
22
|
+
The CloudFront request, response and config are in the bindings:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Acryl } from 'acrylblends'
|
|
26
|
+
import type { Callback, CloudFrontEdgeEvent } from 'acrylblends/lambda-edge'
|
|
27
|
+
|
|
28
|
+
type Bindings = {
|
|
29
|
+
callback: Callback
|
|
30
|
+
event: CloudFrontEdgeEvent
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const app = new Acryl<{ Bindings: Bindings }>()
|
|
34
|
+
|
|
35
|
+
app.get('/country', (c) => {
|
|
36
|
+
const request = c.env.event.Records[0].cf.request
|
|
37
|
+
return c.json({ uri: request.uri, clientIp: request.clientIp })
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The client's address is available through the [ConnInfo helper](../helpers/conninfo.md).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Netlify
|
|
2
|
+
|
|
3
|
+
Netlify Functions (version 2) and Edge Functions take a `Request` and a context and return a `Response`. `handle(app)` from `acrylblends/netlify` is that function; the Netlify context is available to the app as `c.env.context`.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and the framework's release check calls it with a `Request`. It has not been deployed to Netlify by the ACRYL team.
|
|
6
|
+
|
|
7
|
+
## A function
|
|
8
|
+
|
|
9
|
+
`netlify/functions/api.ts`:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Acryl } from 'acrylblends'
|
|
13
|
+
import { handle } from 'acrylblends/netlify'
|
|
14
|
+
import type { Context } from '@netlify/functions'
|
|
15
|
+
|
|
16
|
+
type Bindings = { context: Context }
|
|
17
|
+
|
|
18
|
+
const app = new Acryl<{ Bindings: Bindings }>().basePath('/.netlify/functions/api')
|
|
19
|
+
|
|
20
|
+
app.get('/hello', (c) => c.json({ hello: 'world', site: c.env.context.site.id }))
|
|
21
|
+
|
|
22
|
+
export default handle(app)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run it locally with `netlify dev` and publish with `netlify deploy`. The client's address comes from `getConnInfo` of `acrylblends/netlify` (the Netlify context's `ip`); see the [ConnInfo helper](../helpers/conninfo.md).
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
# Other runtimes
|
|
2
2
|
|
|
3
|
-
Acryl is built on Web Standards, so the same application code is expected to run wherever a `fetch`-style handler can run.
|
|
3
|
+
Acryl is built on Web Standards, so the same application code is expected to run wherever a `fetch`-style handler can run. Node.js ([guide](nodejs.md)) is the runtime the framework is verified on. Bun is experimental ([guide](bun.md)).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## Hosting platforms with an adapter
|
|
6
|
+
|
|
7
|
+
These platforms have an adapter in acrylblends, ported from the upstream adapters with their tests. Each is called with the platform's own events in the framework's release check; the ACRYL team has not deployed to these platforms, so try a first deploy on a test project.
|
|
8
|
+
|
|
9
|
+
| Platform | Guide | Import |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| AWS Lambda (API Gateway, ALB, Function URLs, VPC Lattice) | [AWS Lambda](aws-lambda.md) | `acrylblends/aws-lambda` |
|
|
12
|
+
| Lambda@Edge (CloudFront) | [Lambda@Edge](lambda-edge.md) | `acrylblends/lambda-edge` |
|
|
13
|
+
| Cloudflare Workers | [Cloudflare Workers](cloudflare-workers.md) | `acrylblends/cloudflare-workers` |
|
|
14
|
+
| Cloudflare Pages | [Cloudflare Pages](cloudflare-pages.md) | `acrylblends/cloudflare-pages` |
|
|
15
|
+
| Vercel | [Vercel](vercel.md) | `acrylblends/vercel` |
|
|
16
|
+
| Netlify | [Netlify](netlify.md) | `acrylblends/netlify` |
|
|
17
|
+
| Service Worker | [Service Worker](service-worker.md) | `acrylblends/service-worker` |
|
|
18
|
+
| Bun | [Bun](bun.md) | `acrylblends/bun` |
|
|
19
|
+
|
|
20
|
+
## Not yet verified
|
|
21
|
+
|
|
22
|
+
The following runtimes and platforms have no adapter and no getting-started page yet:
|
|
7
23
|
|
|
8
24
|
- Deno, and Deno Deploy
|
|
9
|
-
- Cloudflare Workers, and Cloudflare Pages
|
|
10
25
|
- Fastly Compute
|
|
11
|
-
- AWS Lambda, and Lambda@Edge
|
|
12
|
-
- Vercel
|
|
13
|
-
- Netlify
|
|
14
26
|
- Next.js
|
|
15
27
|
- Supabase Functions
|
|
16
28
|
- Google Cloud Run
|
|
17
29
|
- Azure Functions
|
|
18
30
|
- Alibaba Cloud Function Compute
|
|
19
31
|
- CloudBase
|
|
20
|
-
- Service Worker
|
|
21
32
|
- WebAssembly (WASI)
|
|
22
33
|
|
|
23
|
-
A page is added here, with a getting-started guide, when a platform has
|
|
34
|
+
A page is added here, with a getting-started guide, when a platform has an adapter and a test that calls it.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Service Worker
|
|
2
|
+
|
|
3
|
+
An Acryl app can answer `fetch` events in a Service Worker, and in any environment that uses the Service Worker syntax, such as a Cloudflare Worker written without ES modules. `fire(app)` from `acrylblends/service-worker` registers the app on the `fetch` event, and `handle(app)` returns the listener, if you want to register it yourself.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and the framework's release check calls it with a fetch event. It has not been run inside a browser's Service Worker by the ACRYL team.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import { fire } from 'acrylblends/service-worker'
|
|
10
|
+
|
|
11
|
+
const app = new Acryl()
|
|
12
|
+
|
|
13
|
+
app.get('/', (c) => c.text('Hello from a Service Worker'))
|
|
14
|
+
|
|
15
|
+
fire(app)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A request the app does not answer (`404`) can be passed on to the network by giving `fetch` as an option, so a Service Worker can serve some routes and leave the others to the origin:
|
|
19
|
+
|
|
20
|
+
```ts no-check
|
|
21
|
+
import { Acryl } from 'acrylblends'
|
|
22
|
+
import { handle } from 'acrylblends/service-worker'
|
|
23
|
+
|
|
24
|
+
const app = new Acryl()
|
|
25
|
+
|
|
26
|
+
app.get('/offline', (c) => c.text('Served by the Service Worker'))
|
|
27
|
+
|
|
28
|
+
addEventListener('fetch', handle(app, { fetch: globalThis.fetch }))
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`app.fire()` (a method of the app) is deprecated in favour of `fire()` from this module.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Vercel
|
|
2
|
+
|
|
3
|
+
Vercel runs a function that takes a `Request` and returns a `Response`, which is what `app.fetch` is. `handle(app)` from `acrylblends/vercel` is that function.
|
|
4
|
+
|
|
5
|
+
> **Status.** The adapter is the upstream adapter ported with its test suite, and the framework's release check calls it with a `Request`. It has not been deployed to Vercel by the ACRYL team.
|
|
6
|
+
|
|
7
|
+
## A function
|
|
8
|
+
|
|
9
|
+
Create `api/index.ts`:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Acryl } from 'acrylblends'
|
|
13
|
+
import { handle } from 'acrylblends/vercel'
|
|
14
|
+
|
|
15
|
+
const app = new Acryl().basePath('/api')
|
|
16
|
+
|
|
17
|
+
app.get('/', (c) => c.text('Hello from Vercel'))
|
|
18
|
+
app.get('/hello', (c) => c.json({ hello: 'world' }))
|
|
19
|
+
|
|
20
|
+
export const GET = handle(app)
|
|
21
|
+
export const POST = handle(app)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Export one handler per HTTP method the app answers. Run it locally with `vercel dev`, and publish with `vercel deploy`.
|
|
25
|
+
|
|
26
|
+
## The client's address
|
|
27
|
+
|
|
28
|
+
The client's address comes from `getConnInfo` of `acrylblends/vercel`, which reads the `x-real-ip` header Vercel sets; see the [ConnInfo helper](../helpers/conninfo.md).
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
| Template | What you get |
|
|
9
9
|
| --- | --- |
|
|
10
10
|
| `nodejs` | an Acryl application served with `serve()` from `acrylblends/node-server`; run with Node.js 22.19 or newer |
|
|
11
|
+
| `notes` | a full-stack starter: accounts (scrypt, cookie session), notes in SQLite (`node:sqlite`), validated forms, search, a JSON API with a typed test client, csrf, a test for every route, a client-component counter |
|
|
12
|
+
| `chat` | an AI chat starter: the answer streams token by token from an OpenAI-compatible API (DeepSeek by default, key only from `DEEPSEEK_API_KEY`), a client component with a Stop button that also stops the request to the model, history in SQLite, tests with a mock model |
|
|
11
13
|
| `bun` (experimental) | an Acryl application with `export default app`, run with `bun` |
|
|
12
14
|
| `acryl-app` | a Cordis app built from plugins, with an agent that can extend it while it runs (the same app `npx acrylblends init --template agent` creates) |
|
|
13
15
|
|
|
@@ -47,14 +49,50 @@ bun create acrylblends@latest my-app --template nodejs
|
|
|
47
49
|
| Argument | Description | Example |
|
|
48
50
|
| :-- | :-- | :-- |
|
|
49
51
|
| `[directory]` | The folder to create. It must not exist or must be empty. Asked for when it is missing. | `my-app` |
|
|
50
|
-
|
|
|
51
|
-
| `--
|
|
52
|
+
| `[source]` | A template from outside the package: `github:owner/repo[#ref]`, `@scope/name[@version]` or a packed `.tgz` file. See [Templates from a repository or a package](#templates-from-a-repository-or-a-package). | `github:acme/starter` |
|
|
53
|
+
| `--template <template>` | One of `nodejs`, `notes`, `chat`, `bun` (experimental), `acryl-app`. Skips the template question. | `--template bun` |
|
|
54
|
+
| `--install` | Install the dependencies with the package manager after creating the project. For a template from a source, the install scripts it will run are listed first. | `--install` |
|
|
52
55
|
| `--pm <packageManager>` | The package manager for `--install`: `npm`, `pnpm`, `yarn` or `bun`. Defaults to the one that started the initializer. | `--pm pnpm` |
|
|
53
56
|
| `--yes`, `-y` | Ask nothing: use `nodejs` unless `--template` is given, and the directory `acryl-app`. | `-y` |
|
|
54
57
|
| `--help` | Print the arguments. | `--help` |
|
|
55
58
|
|
|
56
59
|
The templates are inside the package, so creating a project needs no network except for `--install`.
|
|
57
60
|
|
|
61
|
+
## Templates from a repository or a package
|
|
62
|
+
|
|
63
|
+
A second argument after the directory makes the app from someone else's template:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npm create acrylblends@latest my-app github:acme/starter # a GitHub repository (the default branch)
|
|
67
|
+
npm create acrylblends@latest my-app github:acme/starter#v1.2 # a tag, branch or commit
|
|
68
|
+
npm create acrylblends@latest my-app @acme/starter # an npm package
|
|
69
|
+
npm create acrylblends@latest my-app ./acme-starter-1.0.0.tgz # a packed template on disk
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A template is a folder (a repository, or an npm package) with an `acryl.template.json` at its top:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"name": "starter",
|
|
77
|
+
"description": "What the app is",
|
|
78
|
+
"plugins": ["desktop"]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`name` and `description` are required and shown when the app is created. `plugins` is optional: library plugins the template needs, which the command lists with the `acrylblends add` line that adds them. A source without the file is refused with a message.
|
|
83
|
+
|
|
84
|
+
What the command does, in order:
|
|
85
|
+
|
|
86
|
+
1. **Fetches without running anything.** A GitHub source is downloaded as a tarball (never cloned, so no git hook can run). An npm source is fetched with `npm pack --ignore-scripts`. The archive is unpacked into a scratch folder; one with a path that leaves its folder, a symbolic link, more than 5,000 files or more than 50 MB is refused.
|
|
87
|
+
2. **Reads `acryl.template.json`** and refuses the source if it is missing or malformed.
|
|
88
|
+
3. **Fills the template in.** `__APP_NAME__`, `__PACKAGE__`, `__CLASS__` and `__RANGE__` in text files are replaced as in the built-in templates, `package.json` gets the app's name, a dependency on `acrylblends` is set to the release this command carries, a `gitignore` file becomes `.gitignore` (npm does not publish a `.gitignore`), `docs/framework/` is added, and so are `AGENTS.md` and `CLAUDE.md` when the template has none of its own.
|
|
89
|
+
4. **Looks for secrets.** The result is checked with the secret scan the save step of Blend apps uses; a template that holds a key or a password is refused, and the file and line are named (never the value).
|
|
90
|
+
5. **Creates the folder** only if all of that passed.
|
|
91
|
+
|
|
92
|
+
Nothing of the template is installed or run. If its `package.json` defines `preinstall`, `install`, `postinstall` or `prepare`, the command says so and prints them: `npm install` would run them. Read them first. With `--install` the command prints the list of what will be installed and which scripts will run, then runs the install.
|
|
93
|
+
|
|
94
|
+
To publish your own: put `acryl.template.json` in the repository or package, name the files with the placeholders above, keep a file named `gitignore` instead of `.gitignore`, and keep secrets out.
|
|
95
|
+
|
|
58
96
|
## Example flows
|
|
59
97
|
|
|
60
98
|
### Interactive
|
|
@@ -12,8 +12,8 @@ A `json` validation target reads the body only when the request has a JSON `Cont
|
|
|
12
12
|
|
|
13
13
|
## Which runtimes are verified?
|
|
14
14
|
|
|
15
|
-
Node.js. Bun is experimental. The
|
|
15
|
+
Node.js. Bun is experimental. The hosting platforms with an adapter (AWS Lambda, Lambda@Edge, Cloudflare Workers and Pages, Vercel, Netlify, Service Worker) and the ones without are listed on [Other runtimes](../getting-started/other-runtimes.md).
|
|
16
16
|
|
|
17
17
|
## Where are the parts marked "not yet available"?
|
|
18
18
|
|
|
19
|
-
Their pages are part of the documentation so that the structure is complete; each starts with a note saying so. They are enabled as they are
|
|
19
|
+
Their pages are part of the documentation so that the structure is complete; each starts with a note saying so. They are enabled as they are built.
|
|
@@ -4,7 +4,7 @@ The ConnInfo Helper helps you to get the connection information. For example, yo
|
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Each adapter has its own `getConnInfo`: `acrylblends/node-server`, `acrylblends/cloudflare-workers`, `acrylblends/vercel`, `acrylblends/aws-lambda`, `acrylblends/netlify`, `acrylblends/lambda-edge` and `acrylblends/bun`. The Bun example is not type-checked by the docs tests, which run without Bun's types.
|
|
8
8
|
|
|
9
9
|
**Cloudflare Workers**
|
|
10
10
|
|
|
@@ -98,7 +98,7 @@ export interface FileSystemModule {
|
|
|
98
98
|
|
|
99
99
|
### Using the adapter for Bun
|
|
100
100
|
|
|
101
|
-
On Bun, a `toSSG` function is provided by `acrylblends/bun
|
|
101
|
+
On Bun, a `toSSG` function is provided by `acrylblends/bun`; it writes the files with `Bun.write`.
|
|
102
102
|
|
|
103
103
|
### Options
|
|
104
104
|
|
|
@@ -170,7 +170,8 @@ async function readEvents(body: ReadableStream<Uint8Array>, on: (event: string,
|
|
|
170
170
|
for (;;) {
|
|
171
171
|
const { done, value } = await reader.read()
|
|
172
172
|
if (done) return
|
|
173
|
-
|
|
173
|
+
// Line endings may be CRLF; a \r at the end of a chunk is completed by the next chunk, so normalise the whole buffer.
|
|
174
|
+
buffer = (buffer + decoder.decode(value, { stream: true })).replaceAll('\r\n', '\n')
|
|
174
175
|
const blocks = buffer.split('\n\n')
|
|
175
176
|
buffer = blocks.pop() ?? ''
|
|
176
177
|
for (const block of blocks) {
|
|
@@ -178,7 +179,7 @@ async function readEvents(body: ReadableStream<Uint8Array>, on: (event: string,
|
|
|
178
179
|
const data: string[] = []
|
|
179
180
|
for (const line of block.split('\n')) {
|
|
180
181
|
if (line.startsWith('event:')) event = line.slice(6).trim()
|
|
181
|
-
else if (line.startsWith('data:')) data.push(line.slice(5).
|
|
182
|
+
else if (line.startsWith('data:')) data.push(line.slice(5).replace(/^ /, '')) // the SSE spec strips one leading space only
|
|
182
183
|
}
|
|
183
184
|
on(event, data.join('\n'))
|
|
184
185
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# WebSocket Helper
|
|
2
2
|
|
|
3
3
|
WebSocket Helper is a helper for server-side WebSockets in Acryl applications.
|
|
4
|
-
The Node.js
|
|
4
|
+
The Node.js, Cloudflare Workers and Bun adapters are available.
|
|
5
5
|
|
|
6
6
|
## Import
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The Node.js adapter (`acrylblends/node-server`), the Cloudflare Workers adapter (`acrylblends/cloudflare-workers`) and the Bun adapter (`acrylblends/bun`) provide `upgradeWebSocket`. The other platforms have no WebSocket support in their adapters. The Bun adapter is checked on Bun in the framework's release check; its examples below are not type-checked by the docs tests, which run without Bun's types.
|
|
9
9
|
|
|
10
10
|
**Cloudflare Workers**
|
|
11
11
|
|
|
@@ -31,7 +31,7 @@ app.use(
|
|
|
31
31
|
app.get('/', (c) => c.text('Acryl!'))
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Pass the `getConninfo` from the [ConnInfo helper](../../helpers/conninfo.md) appropriate for your environment as the first argument of `ipRestriction`.
|
|
34
|
+
Pass the `getConninfo` from the [ConnInfo helper](../../helpers/conninfo.md) appropriate for your environment as the first argument of `ipRestriction`. Every adapter has one (`acrylblends/bun`, `acrylblends/aws-lambda`, `acrylblends/node-server` and the others).
|
|
35
35
|
|
|
36
36
|
## Rules
|
|
37
37
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "docs/framework",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "eada6fb06a5cafb6c2c92a97effeba567dbd44ba"
|
|
6
6
|
},
|
|
7
|
-
"files":
|
|
8
|
-
"digest": "sha256:
|
|
7
|
+
"files": 98,
|
|
8
|
+
"digest": "sha256:396f882ac7b612455edc59961c52472fadc5f95ea1a778208e7edd860e4e6727"
|
|
9
9
|
}
|