acrylblends 0.11.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -53
- package/dist/index.d.ts +29 -2
- package/dist/index.js +450 -69
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +5 -3
- package/docs/framework/agents/index.md +1 -1
- package/docs/framework/api/acryl.md +5 -17
- package/docs/framework/api/context.md +12 -61
- package/docs/framework/api/presets.md +1 -1
- package/docs/framework/api/request.md +3 -3
- package/docs/framework/api/summary.md +1 -1
- package/docs/framework/concepts/web-standard.md +6 -16
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +13 -8
- package/docs/framework/cordis/migration-from-0-4.md +1 -1
- package/docs/framework/cordis/migration-to-1-0.md +45 -0
- package/docs/framework/cordis/node-server.md +2 -2
- package/docs/framework/getting-started/basic.md +15 -10
- package/docs/framework/getting-started/desktop.md +1 -1
- package/docs/framework/getting-started/other-runtimes.md +10 -14
- package/docs/framework/guides/create-acrylblends.md +4 -2
- package/docs/framework/guides/data.md +1 -1
- package/docs/framework/guides/faq.md +1 -1
- package/docs/framework/guides/jsx-dom.md +1 -1
- package/docs/framework/guides/production.md +43 -0
- package/docs/framework/guides/rpc.md +3 -11
- package/docs/framework/helpers/adapter.md +13 -43
- package/docs/framework/helpers/conninfo.md +1 -36
- package/docs/framework/helpers/streaming.md +1 -1
- package/docs/framework/helpers/websocket.md +4 -11
- package/docs/framework/middleware/builtin/basic-auth.md +1 -1
- package/docs/framework/middleware/builtin/cache.md +2 -22
- package/docs/framework/middleware/builtin/compress.md +1 -1
- package/docs/framework/middleware/builtin/context-storage.md +10 -14
- package/docs/framework/middleware/builtin/cors.md +1 -2
- package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
- package/docs/framework/middleware/builtin/jsx-renderer.md +1 -1
- package/docs/framework/middleware/builtin/logger.md +1 -1
- package/docs/framework/middleware/builtin/loopback-only.md +1 -1
- package/docs/framework/middleware/builtin/mount.md +1 -1
- package/docs/framework/middleware/builtin/request-id.md +13 -20
- package/docs/framework/middleware/builtin/timing.md +1 -2
- package/lib/cordis/door.d.ts +5 -0
- package/lib/cordis/door.js +8 -4
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/router-http.js +1 -1
- package/lib/cordis/router.d.ts +1 -2
- package/lib/cordis/router.js +2 -3
- package/lib/cordis/state.js +1 -1
- package/lib/door/host.js +1 -1
- package/lib/drivers.d.ts +66 -0
- package/lib/drivers.js +16 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/{snapshot-text-yaWlezbd.js → snapshot-text-DmE0YfeZ.js} +2 -2
- package/lib/{src-DNzw0Qsb.js → src-2Z4mJg0x.js} +0 -1
- package/package.json +5 -121
- package/plugins/acryl-door/src/index.ts +8 -0
- package/plugins/acryl-router/src/index.ts +0 -1
- package/plugins/agent-panel/drivers.ts +1 -16
- package/plugins/agent-panel/index.ts +1 -1
- package/plugins/agent-panel/registry.ts +16 -0
- package/plugins/driver-acp/README.md +53 -0
- package/plugins/driver-acp/chat.ts +258 -0
- package/plugins/driver-acp/connection.ts +239 -0
- package/plugins/driver-acp/index.ts +65 -0
- package/plugins/driver-acp/plugin.json +12 -0
- package/plugins/driver-acp/provenance.json +20 -0
- package/plugins/driver-pty/index.ts +1 -0
- package/shared/library-history.json +464 -0
- package/docs/framework/getting-started/aws-lambda.md +0 -115
- package/docs/framework/getting-started/cloudflare-pages.md +0 -53
- package/docs/framework/getting-started/cloudflare-workers.md +0 -67
- package/docs/framework/getting-started/lambda-edge.md +0 -41
- package/docs/framework/getting-started/netlify.md +0 -25
- package/docs/framework/getting-started/service-worker.md +0 -33
- package/docs/framework/getting-started/vercel.md +0 -28
- package/lib/aws-lambda.d.ts +0 -324
- package/lib/aws-lambda.js +0 -357
- package/lib/cloudflare-pages.d.ts +0 -58
- package/lib/cloudflare-pages.js +0 -78
- package/lib/cloudflare-workers.d.ts +0 -21
- package/lib/cloudflare-workers.js +0 -80
- package/lib/deprecated-BP4fPt38.js +0 -10
- package/lib/deprecated-CTy4N7ng.d.ts +0 -12
- package/lib/lambda-edge.d.ts +0 -92
- package/lib/lambda-edge.js +0 -105
- package/lib/netlify.d.ts +0 -29
- package/lib/netlify.js +0 -30
- package/lib/service-worker.d.ts +0 -48
- package/lib/service-worker.js +0 -38
- package/lib/vercel.d.ts +0 -10
- package/lib/vercel.js +0 -9
- package/plugins/acryl-router/src/deprecated.ts +0 -15
|
@@ -25,7 +25,7 @@ with SQLite, small state on the `kv` port with one JSON file, the Node server, t
|
|
|
25
25
|
|
|
26
26
|
## What an agent can do inside a running app
|
|
27
27
|
|
|
28
|
-
The `
|
|
28
|
+
The `agent` template (a Cordis app; `acryl-app` is its old name and still works) also lets an agent change the app while it runs:
|
|
29
29
|
|
|
30
30
|
1. The agent writes a plugin into `plugins/<name>/index.ts`. The plugin may import `acrylblends` and the other subpaths by name.
|
|
31
31
|
2. The app installs it as a child plugin, runs its `check(ctx)` with the plugin's own context, and keeps the previous version if the new one fails to start or to pass its check.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Acryl
|
|
2
2
|
|
|
3
3
|
`Acryl` is the primary object.
|
|
4
4
|
It will be imported first and used until the end.
|
|
@@ -9,7 +9,7 @@ import { Acryl } from 'acrylblends'
|
|
|
9
9
|
const app = new Acryl()
|
|
10
10
|
//...
|
|
11
11
|
|
|
12
|
-
export default app //
|
|
12
|
+
export default app // a Bun server, or a value a Cordis plugin mounts; see Node.js for the Node entry
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Methods
|
|
@@ -64,25 +64,13 @@ app.onError((err, c) => {
|
|
|
64
64
|
|
|
65
65
|
## fire()
|
|
66
66
|
|
|
67
|
-
> **Warning.** **`app.fire()` is deprecated
|
|
68
|
-
|
|
69
|
-
`app.fire()` automatically adds a global `fetch` event listener.
|
|
70
|
-
|
|
71
|
-
This can be useful for environments that adhere to the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), such as [non-ES module Cloudflare Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/).
|
|
72
|
-
|
|
73
|
-
`app.fire()` executes the following for you:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
addEventListener('fetch', (event: FetchEventLike): void => {
|
|
77
|
-
event.respondWith(this.dispatch(...))
|
|
78
|
-
})
|
|
79
|
-
```
|
|
67
|
+
> **Warning.** **`app.fire()` is deprecated**, and the Service Worker adapter is not part of the package in 1.0. It adds a global `fetch` event listener, which only a Service Worker-style host reads; use `app.fetch` in an entry point of your own instead.
|
|
80
68
|
|
|
81
69
|
## fetch()
|
|
82
70
|
|
|
83
71
|
`app.fetch` will be the entry point of your application.
|
|
84
72
|
|
|
85
|
-
|
|
73
|
+
As the `fetch` method of an object a host calls:
|
|
86
74
|
|
|
87
75
|
```ts twoslash
|
|
88
76
|
import { Acryl } from 'acrylblends'
|
|
@@ -230,7 +218,7 @@ const app = new Acryl({ router: new RegExpRouter() })
|
|
|
230
218
|
|
|
231
219
|
## Generics
|
|
232
220
|
|
|
233
|
-
You can pass Generics to specify the
|
|
221
|
+
You can pass Generics to specify the type of the environment the host passes and the variables used in `c.set`/`c.get`.
|
|
234
222
|
|
|
235
223
|
```ts twoslash
|
|
236
224
|
import { Acryl } from 'acrylblends'
|
|
@@ -362,81 +362,32 @@ app.get('/pages/my-hobbies', (c) => {
|
|
|
362
362
|
|
|
363
363
|
## executionCtx
|
|
364
364
|
|
|
365
|
-
|
|
365
|
+
A host that embeds the app can pass an execution context: `waitUntil(promise)` keeps work alive after the response has been sent, and `passThroughOnException()` lets the host take over when the handler throws. The Node server passes none, so read it only in an app whose host provides one.
|
|
366
366
|
|
|
367
367
|
```ts twoslash
|
|
368
368
|
import { Acryl } from 'acrylblends'
|
|
369
|
-
const app = new Acryl
|
|
370
|
-
|
|
371
|
-
KV: any
|
|
372
|
-
}
|
|
373
|
-
}>()
|
|
374
|
-
declare const key: string
|
|
375
|
-
declare const data: string
|
|
376
|
-
// ---cut---
|
|
377
|
-
// ExecutionContext object
|
|
378
|
-
app.get('/foo', async (c) => {
|
|
379
|
-
c.executionCtx.waitUntil(c.env.KV.put(key, data))
|
|
380
|
-
// ...
|
|
381
|
-
})
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
The `ExecutionContext` also has an [`exports`](https://developers.cloudflare.com/workers/runtime-apis/context/#exports) field. To get autocomplete with Wrangler's generated types, you can use module augmentation:
|
|
385
|
-
|
|
386
|
-
```ts
|
|
387
|
-
import 'acrylblends'
|
|
388
|
-
|
|
389
|
-
declare module 'acrylblends' {
|
|
390
|
-
interface ExecutionContext {
|
|
391
|
-
readonly exports: Cloudflare.Exports
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
## event
|
|
397
|
-
|
|
398
|
-
You can access Cloudflare Workers' specific `FetchEvent`. This was used in "Service Worker" syntax. But, it is not recommended now.
|
|
399
|
-
|
|
400
|
-
```ts twoslash
|
|
401
|
-
import { Acryl } from 'acrylblends'
|
|
402
|
-
declare const key: string
|
|
403
|
-
declare const data: string
|
|
404
|
-
type KVNamespace = any
|
|
369
|
+
const app = new Acryl()
|
|
370
|
+
declare const sendReceipt: Promise<void>
|
|
405
371
|
// ---cut---
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const app = new Acryl<{ Bindings: Bindings }>()
|
|
412
|
-
|
|
413
|
-
// FetchEvent object (only set when using Service Worker syntax)
|
|
414
|
-
app.get('/foo', async (c) => {
|
|
415
|
-
c.event.waitUntil(c.env.MY_KV.put(key, data))
|
|
416
|
-
// ...
|
|
372
|
+
// Work that must finish after the response, when the host provides a context
|
|
373
|
+
app.get('/orders/:id', async (c) => {
|
|
374
|
+
c.executionCtx.waitUntil(sendReceipt)
|
|
375
|
+
return c.json({ ok: true })
|
|
417
376
|
})
|
|
418
377
|
```
|
|
419
378
|
|
|
420
379
|
## env
|
|
421
380
|
|
|
422
|
-
|
|
423
|
-
Regardless of type, bindings are always available as global variables and can be accessed via the context `c.env.BINDING_KEY`.
|
|
381
|
+
`c.env` is the environment the host hands the app. The Node server passes `process.env`, so `c.env.MY_KEY` is the variable `MY_KEY`. A host that passes something else gives the app a type parameter, and then `c.env` is that shape.
|
|
424
382
|
|
|
425
383
|
```ts twoslash
|
|
426
384
|
import { Acryl } from 'acrylblends'
|
|
427
|
-
type KVNamespace = any
|
|
428
385
|
// ---cut---
|
|
429
|
-
|
|
430
|
-
type Bindings = {
|
|
431
|
-
MY_KV: KVNamespace
|
|
432
|
-
}
|
|
386
|
+
const app = new Acryl<{ Bindings: { REGION: string } }>()
|
|
433
387
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
app.get('/', async (c) => {
|
|
438
|
-
c.env.MY_KV.get('my-key')
|
|
439
|
-
// ...
|
|
388
|
+
app.get('/', (c) => {
|
|
389
|
+
const region = c.env.REGION
|
|
390
|
+
return c.text(region)
|
|
440
391
|
})
|
|
441
392
|
```
|
|
442
393
|
|
|
@@ -57,6 +57,6 @@ this.router = new PatternRouter()
|
|
|
57
57
|
|
|
58
58
|
| Preset | Suitable platforms |
|
|
59
59
|
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
-
| `acrylblends` | This is highly recommended for most use cases. Although the registration phase may be slower than `acrylblends/quick`, it exhibits high performance once booted. It's ideal for long-life
|
|
60
|
+
| `acrylblends` | This is highly recommended for most use cases. Although the registration phase may be slower than `acrylblends/quick`, it exhibits high performance once booted. It's ideal for a long-life server on **Node.js** (the verified runtime) or **Bun** (experimental). |
|
|
61
61
|
| `acrylblends/quick` | This preset is designed for environments where the application is initialized for every request. |
|
|
62
62
|
| `acrylblends/tiny` | This is the smallest router package and it's suitable for environments where resources are limited. |
|
|
@@ -364,9 +364,9 @@ app.get('/about/me', async (c) => {
|
|
|
364
364
|
The raw [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object.
|
|
365
365
|
|
|
366
366
|
```ts
|
|
367
|
-
//
|
|
368
|
-
app.post('/', async (c) => {
|
|
369
|
-
const
|
|
367
|
+
// Hand the raw Request to an API that takes one, such as a Web-standard parser
|
|
368
|
+
app.post('/upload', async (c) => {
|
|
369
|
+
const bytes = await c.req.raw.arrayBuffer()
|
|
370
370
|
// ...
|
|
371
371
|
})
|
|
372
372
|
```
|
|
@@ -10,7 +10,7 @@ import { Acryl } from 'acrylblends'
|
|
|
10
10
|
import { trimTrailingSlash } from 'acrylblends/trailing-slash'
|
|
11
11
|
import { schemaValidator, type StandardSchema } from 'acrylblends/schema-validator'
|
|
12
12
|
import { streamSSE } from 'acrylblends/streaming'
|
|
13
|
-
import type {} from '
|
|
13
|
+
import type {} from 'acrylblends/cordis/state'
|
|
14
14
|
|
|
15
15
|
export const name = 'orders' // the package name and the Loader row id
|
|
16
16
|
export const inject = ['router', 'state'] // the plugin waits (PENDING) until both exist
|
|
@@ -4,8 +4,8 @@ Acryl uses only **Web Standards** like Fetch.
|
|
|
4
4
|
They were originally used in the `fetch` function and consist of basic objects that handle HTTP requests and responses.
|
|
5
5
|
In addition to `Requests` and `Responses`, there are `URL`, `URLSearchParam`, `Headers` and others.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
For example, a server that returns "Hello World"
|
|
7
|
+
Bun is built on Web Standards the same way.
|
|
8
|
+
For example, a server that returns "Hello World" can be written as below, and it runs on Node.js and Bun.
|
|
9
9
|
|
|
10
10
|
```ts twoslash
|
|
11
11
|
export default {
|
|
@@ -15,23 +15,13 @@ export default {
|
|
|
15
15
|
}
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Acryl uses only Web Standards,
|
|
19
|
-
In addition, we have a Node.js adapter. Acryl runs on these runtimes:
|
|
18
|
+
Acryl uses only Web Standards, so the same application code is expected to run wherever a `fetch`-style handler can run: a platform that can call `app.fetch(request)` can serve an Acryl app. In 1.0 the framework builds, exports and gates two runtimes:
|
|
20
19
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- Bun
|
|
24
|
-
- Fastly Compute
|
|
25
|
-
- AWS Lambda
|
|
26
|
-
- Node.js
|
|
27
|
-
- Vercel (edge-light)
|
|
28
|
-
- WebAssembly (w/ [WebAssembly System Interface (WASI)][wasi] via [`wasi:http`][wasi-http])
|
|
20
|
+
- **Node.js** (`acrylblends/node-server`): the verified runtime, on Node 22 and 24, on Linux, macOS and Windows.
|
|
21
|
+
- **Bun** (`acrylblends/bun`): experimental, with one smoke gate.
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
The adapters the upstream project had for Cloudflare Workers and Pages, Deno, Fastly Compute, AWS Lambda, Lambda@Edge, Vercel, Netlify and service workers are not part of the 1.0 package: no gate of ours deploys to them. Their ported sources stay in the repository and may return as separate plugins, each with its own gate. See [Other runtimes](../getting-started/other-runtimes.md).
|
|
31
24
|
The same code runs on all platforms.
|
|
32
25
|
|
|
33
26
|
Cloudflare Workers, Deno, Shopify, and others launched [WinterCG](https://wintercg.org) to discuss the possibility of using the Web Standards to enable "web-interoperability".
|
|
34
27
|
Acryl will follow their steps and go for **the Standard of the Web Standards**.
|
|
35
|
-
|
|
36
|
-
[wasi]: https://github.com/WebAssembly/wasi
|
|
37
|
-
[wasi-http]: https://github.com/WebAssembly/wasi-http
|
|
@@ -126,7 +126,7 @@ smallest one to copy is `examples/acryl-framework-starter`.
|
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
128
|
import { Context } from '@deepseek-ai/cordis'
|
|
129
|
-
import * as router from '
|
|
129
|
+
import * as router from 'acrylblends/cordis/router'
|
|
130
130
|
import * as todo from '../src/index.ts'
|
|
131
131
|
|
|
132
132
|
const ctx = new Context()
|
|
@@ -139,13 +139,18 @@ expect(await response.json()).toEqual({ id: '42' })
|
|
|
139
139
|
|
|
140
140
|
## Package map
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
| `acryl-
|
|
147
|
-
| `acryl-
|
|
148
|
-
| `acryl-
|
|
142
|
+
The framework plugins are published as subpaths of the `acrylblends` package (the Cordis forms); an app made by `acrylblends init` carries their source under `plugins/` instead, with the same names.
|
|
143
|
+
|
|
144
|
+
| Plugin | Published as | Responsibility |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `acryl-router` | `acrylblends/cordis/router` | the application API and the Cordis `router` service |
|
|
147
|
+
| `acryl-router-http` | `acrylblends/cordis/router-http` | serving router prefixes on the ACRYL Host's web server, with WebSocket upgrades owned by the mounting plugin |
|
|
148
|
+
| `acryl-node-server` | `acrylblends/cordis/node-server` | serving the whole router from a standalone Node.js process |
|
|
149
|
+
| `acryl-state` | `acrylblends/cordis/state` | shared application state across surfaces |
|
|
150
|
+
| `acryl-inspect` | `acrylblends/cordis/inspect` | runtime inspection of mounts, routes and plugins |
|
|
151
|
+
| `acryl-db`, `acryl-db-sqlite` | `acrylblends/cordis/db`, `acrylblends/cordis/db-sqlite` | the `db` port and its SQLite provider |
|
|
152
|
+
| `acryl-kv`, `acryl-kv-json`, `acryl-kv-db` | `acrylblends/cordis/kv`, `.../kv-json`, `.../kv-db` | the `kv` port and its providers |
|
|
153
|
+
| `acryl-door` | `acrylblends/cordis/door` (experimental) | the door: an agent looks at the running app and acts on it, in development only |
|
|
149
154
|
|
|
150
155
|
## Next
|
|
151
156
|
|
|
@@ -6,7 +6,7 @@ unchanged.
|
|
|
6
6
|
|
|
7
7
|
| 0.4 | 0.5 |
|
|
8
8
|
| --- | --- |
|
|
9
|
-
| `import { App } from 'acryl-router'`, `new App()` | `import { Acryl } from '
|
|
9
|
+
| `import { App } from 'acryl-router'`, `new App()` | `import { Acryl } from 'acrylblends/cordis/router'`, `new Acryl()` (`App` and `AppRequest` are removed in 1.0) |
|
|
10
10
|
| `AppRequest` | `AcrylRequest` |
|
|
11
11
|
|
|
12
12
|
`App` and `AppRequest` still work in 0.5, marked deprecated, and are removed in the next release. Code outside Cordis imports the same
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Migration to 1.0
|
|
2
|
+
|
|
3
|
+
1.0 fixes the public surface of `acrylblends` and gives an app a way to move its copies of the framework plugins forward. This page is the one path from any 0.x app that was made by 0.9.1 or later; an older app is best made again with `npm create acrylblends@latest` and its own code moved over (its routes and plugins are plain `Acryl` code and Cordis plugins).
|
|
4
|
+
|
|
5
|
+
## 1. Upgrade the app's copies of the framework plugins
|
|
6
|
+
|
|
7
|
+
An app made by `acrylblends init` carries copies of the framework plugins (`acryl-router`, `acryl-node-server`, `acryl-door`, `extensions`, ...) in `plugins/`. `acrylblends upgrade` brings them to the version of the installed package:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install acrylblends@latest # in an app that depends on the package; the agent template does not
|
|
11
|
+
npx acrylblends upgrade --dry-run # what would change, and the diff of every file you edited
|
|
12
|
+
npx acrylblends upgrade # do it
|
|
13
|
+
npm install && npm run check && npm test
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- A file nobody edited is replaced. A file the app edited stops the upgrade: nothing is changed, the diff of what would be lost is shown, and the command exits 1. Move your change out of the plugin (a plugin of your own in `plugins/<name>/` is never touched), or run `npx acrylblends upgrade --force`, which saves the edited versions in `.acrylblends/upgrade-backup/<time>/` before replacing them.
|
|
17
|
+
- `--diff` prints the diff of every changed file, not only the edited ones. `acrylblends upgrade acryl-router extensions` upgrades only those plugins.
|
|
18
|
+
- A file of your own inside a library plugin folder (a note, a test) is kept and reported. A plugin that a newer version needs and the app lacks is added.
|
|
19
|
+
- How it tells an edit from an untouched file: since 1.0 `acrylblends init` and `add` record the hash of every copied file in `plugins/library.lock.json`; for an app made earlier, the package knows the hashes of the released versions (0.9.1, 0.9.2, 0.10.0, 0.11.0). A file that matches neither counts as edited, so a doubt always stops the upgrade instead of overwriting.
|
|
20
|
+
- `package.json` gets the new `acrylblends` range when it has one, and the dependencies the new plugin versions need (existing ones are kept; run `npm install`).
|
|
21
|
+
|
|
22
|
+
## 2. What changed in the public surface
|
|
23
|
+
|
|
24
|
+
| Removed or changed | Do this |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `App` and `AppRequest` (the 0.4 names, deprecated since 0.5) are removed | `Acryl` and `AcrylRequest`: `import { Acryl } from 'acrylblends'` |
|
|
27
|
+
| `acrylblends/utils/*` and `acrylblends/acryl-base` are internal, not exported | Use the public helpers (`acrylblends/cookie`, `acrylblends/http-exception`, ...); the utilities are not promised |
|
|
28
|
+
| The adapters for AWS Lambda, Lambda@Edge, Cloudflare Workers and Pages, Vercel, Netlify and Service Workers are not exported | An app is plain `fetch` code (`app.fetch(request)`), so a platform that calls a `fetch` handler can call it. Their ported sources stay in the repository; they may return as separate plugins, each with a gate. See [Other runtimes](../getting-started/other-runtimes.md) |
|
|
29
|
+
| `acrylblends/bun` is experimental | The one Bun gate runs it on Bun; no promise beyond that |
|
|
30
|
+
| The door (`door/*`, `cordis/door`), the agent panel and the PTY driver are experimental and development-only | They start nothing with `NODE_ENV=production` (the door plugin now checks it itself; `allowInProduction: true` for a host that means it); see [what runs in production](../guides/production.md) |
|
|
31
|
+
| `HTTPException` is `acrylblends/http-exception`, not the root | `import { HTTPException } from 'acrylblends/http-exception'` |
|
|
32
|
+
| `acrylblends/cordis/*` are the published forms of the framework plugins | In code that uses the package (not an app's own copies), import `acrylblends/cordis/router` where an old guide wrote `acryl-router` |
|
|
33
|
+
| One template list: `blank`, `nodejs`, `notes`, `chat`, `todo`, `agent` in both `create-acrylblends` and `acrylblends init` | `acryl-app` still means `agent` |
|
|
34
|
+
|
|
35
|
+
Node.js 22.19 or newer. Bun is optional and experimental; Deno is not supported.
|
|
36
|
+
|
|
37
|
+
## 3. Check
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
npx acrylblends check-deps
|
|
41
|
+
npm run check
|
|
42
|
+
npm test
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
An app on the vendored plugins keeps working without the package's own newer subpaths; moving to the package's `acrylblends/cordis/*` forms in place of the copies is a separate, optional step.
|
|
@@ -9,8 +9,8 @@ The server is a Cordis plugin that provides the `nodeServer` service:
|
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
import { Context } from '@deepseek-ai/cordis'
|
|
12
|
-
import * as router from '
|
|
13
|
-
import * as nodeServer from 'acrylblends/node-server'
|
|
12
|
+
import * as router from 'acrylblends/cordis/router'
|
|
13
|
+
import * as nodeServer from 'acrylblends/cordis/node-server'
|
|
14
14
|
import * as todo from './todo.ts'
|
|
15
15
|
|
|
16
16
|
const root = new Context()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
Using Acryl is easy. You set up the project, write code, develop with a local server, and run it. The same application code
|
|
4
|
-
entry point
|
|
3
|
+
Using Acryl is easy. You set up the project, write code, develop with a local server, and run it. The same application code runs on Node.js, the verified runtime, and on Bun,
|
|
4
|
+
which is experimental; only the entry point differs. This page shows the basic usage.
|
|
5
5
|
|
|
6
6
|
## Starter
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
A starter is a Cordis app that arrives already wired: `blend.yaml`, its plugins in `plugins/`, and a test. Use the "create-acrylblends" command.
|
|
9
9
|
|
|
10
10
|
**npm**
|
|
11
11
|
|
|
@@ -34,12 +34,17 @@ bun create acrylblends@latest my-app
|
|
|
34
34
|
You are asked which template you want:
|
|
35
35
|
|
|
36
36
|
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
Templates:
|
|
38
|
+
blank the router, a server and one route: the smallest app
|
|
39
|
+
nodejs a web app with a page and a client component
|
|
40
|
+
notes a multi-user notes app on the db port (sign-up, log-in, sessions)
|
|
41
|
+
chat a chat app that streams an answer over server-sent events
|
|
42
|
+
todo a to-do list on the router and state
|
|
43
|
+
agent the blank app with an offline agent that builds plugins into the running app
|
|
44
|
+
Which template do you want to use? [nodejs]:
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
Every template is a Cordis app: `blend.yaml` lists its plugins and every plugin is a folder in `plugins/`. `nodejs` is a web app with a page and a client component, `notes` and `chat` add a database, and `
|
|
47
|
+
Every template is a Cordis app: `blend.yaml` lists its plugins and every plugin is a folder in `plugins/`. `blank` is the smallest app, `nodejs` is a web app with a page and a client component, `notes` and `chat` add a database, `todo` is a to-do list, and `agent` adds an agent that can extend the app while it
|
|
43
48
|
runs (see [Acryl in a Cordis app](../cordis/acryl-in-a-cordis-app.md)). Pass `--template` to skip the question, see [create-acrylblends](../guides/create-acrylblends.md).
|
|
44
49
|
|
|
45
50
|
The template is created in `my-app`: go to it, install the dependencies and start the local server.
|
|
@@ -52,9 +57,9 @@ npm run dev
|
|
|
52
57
|
|
|
53
58
|
## Hello World
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
In a generated app the routes live in `src/routes.ts`, which builds an `Acryl` app and mounts it with `ctx.router.route`; `src/main.ts` picks where the app lives and starts it. The app on its own is plain:
|
|
56
61
|
|
|
57
|
-
The `import` and the final `export default` are the same on
|
|
62
|
+
The `import` and the final `export default` are the same on Node.js and on Bun; what differs is how the runtime starts the app (see [Node.js](nodejs.md) and [Bun](bun.md)).
|
|
58
63
|
|
|
59
64
|
```ts twoslash
|
|
60
65
|
import { Acryl } from 'acrylblends'
|
|
@@ -131,7 +136,7 @@ app.get('/', (c) => {
|
|
|
131
136
|
})
|
|
132
137
|
```
|
|
133
138
|
|
|
134
|
-
[JSX](../guides/jsx.md)
|
|
139
|
+
JSX works too: `acrylblends/jsx` renders on the server and in the browser, and the `nodejs` template ships a `.tsx` page. See [JSX](../guides/jsx.md).
|
|
135
140
|
|
|
136
141
|
## Return raw Response
|
|
137
142
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Desktop
|
|
2
2
|
|
|
3
3
|
An Acryl app also runs as a desktop app: an Electron window opens on the app's own server. It is the same app and the same code as `npm run dev`; nothing in the app changes. This page covers a plain Acryl app (the `nodejs`
|
|
4
|
-
template) and a Blend app (the `
|
|
4
|
+
template) and a Blend app (the `agent` template or any other `acrylblends init` template). It runs from source today: packaging a distributable app (dmg, exe, deb) is not part of it yet.
|
|
5
5
|
|
|
6
6
|
## Add it
|
|
7
7
|
|
|
@@ -2,24 +2,20 @@
|
|
|
2
2
|
|
|
3
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
|
-
##
|
|
5
|
+
## Runtimes with an adapter
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
| Runtime | Guide | Import | Status |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| Node.js | [Node.js](nodejs.md) | `acrylblends/node-server` | Primary. Verified on Node 22 and 24, on Linux, macOS and Windows. |
|
|
10
|
+
| Bun | [Bun](bun.md) | `acrylblends/bun` | Experimental. One smoke gate: the Bun adapter is run on Bun (a static file, the client address, a WebSocket echo). |
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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` |
|
|
12
|
+
## Removed in 1.0
|
|
13
|
+
|
|
14
|
+
The upstream also ships adapters for AWS Lambda, Lambda@Edge, Cloudflare Workers and Pages, Vercel, Netlify and Service Workers. They were part of 0.x, ported with their tests, but no gate of ours deploys to any of them, so a 1.0 package does not promise them: they are not built or exported. Their ported sources stay in `plugins/acryl-router/src/core/adapter/` in the repository and may come back as separate plugins, each with its own gate. An app written with `acrylblends` is still plain `fetch` code (`app.fetch(request)`), so a platform that calls a `fetch` handler can call it directly.
|
|
19
15
|
|
|
20
16
|
## Not yet verified
|
|
21
17
|
|
|
22
|
-
The following runtimes and platforms have no adapter and no getting-started page
|
|
18
|
+
The following runtimes and platforms have no adapter and no getting-started page:
|
|
23
19
|
|
|
24
20
|
- Deno, and Deno Deploy
|
|
25
21
|
- Fastly Compute
|
|
@@ -31,4 +27,4 @@ The following runtimes and platforms have no adapter and no getting-started page
|
|
|
31
27
|
- CloudBase
|
|
32
28
|
- WebAssembly (WASI)
|
|
33
29
|
|
|
34
|
-
A page is added here, with a getting-started guide, when a
|
|
30
|
+
A page is added here, with a getting-started guide, when a runtime has an adapter and a gate that runs it.
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
| Template | What you get |
|
|
9
9
|
| --- | --- |
|
|
10
|
+
| `blank` | the smallest app: the router, a server and one route, with a test |
|
|
10
11
|
| `nodejs` | a web app: routes, a server-rendered JSX page and a client component, mounted by the app's own plugin; run with Node.js 22.19 or newer |
|
|
11
12
|
| `notes` | a full-stack starter: accounts (scrypt, cookie session), notes in SQLite through the [`db` port](data.md) with numbered migrations, validated forms, search, a JSON API with a typed test client, csrf, a test for every route, a client-component counter |
|
|
12
13
|
| `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 through the [`db` port](data.md), tests with a mock model |
|
|
13
|
-
| `
|
|
14
|
+
| `todo` | a to-do list on the router and state: CRUD routes, a page, tests |
|
|
15
|
+
| `agent` | the blank app with an offline agent that builds plugins into the running app, through the door (`acryl-app` is the old name for it and still works) |
|
|
14
16
|
|
|
15
17
|
Every template is a Cordis app: `blend.yaml` lists the plugins that are mounted, every plugin is a folder in `plugins/`, and the app is started by `src/main.ts`. A bare template name after the directory is the same as `--template`: `npm create acrylblends@latest my-app notes`. `bun` is no longer a template; the framework still runs on Bun (see [Bun](../getting-started/bun.md)), but a new app is a Cordis app on Node.
|
|
16
18
|
|
|
@@ -51,7 +53,7 @@ bun create acrylblends@latest my-app --template nodejs
|
|
|
51
53
|
| :-- | :-- | :-- |
|
|
52
54
|
| `[directory]` | The folder to create. It must not exist or must be empty. Asked for when it is missing. | `my-app` |
|
|
53
55
|
| `[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` |
|
|
54
|
-
| `--template <template>` | One of `nodejs`, `notes`, `chat`, `acryl-app
|
|
56
|
+
| `--template <template>` | One of `blank`, `nodejs`, `notes`, `chat`, `todo`, `agent` (the same list as `acrylblends init --template`; `acryl-app` is accepted as `agent`). Skips the template question; a bare name after the directory means the same. | `--template notes` |
|
|
55
57
|
| `--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` |
|
|
56
58
|
| `--pm <packageManager>` | The package manager for `--install`: `npm`, `pnpm`, `yarn` or `bun`. Defaults to the one that started the initializer. | `--pm pnpm` |
|
|
57
59
|
| `--yes`, `-y` | Ask nothing: use `nodejs` unless `--template` is given, and the directory `acryl-app`. | `-y` |
|
|
@@ -289,7 +289,7 @@ applies the `migrations` folder, and provides `db`; a plugin declares `inject: [
|
|
|
289
289
|
|
|
290
290
|
```ts
|
|
291
291
|
import type { Context } from '@deepseek-ai/cordis'
|
|
292
|
-
import type {} from '
|
|
292
|
+
import type {} from 'acrylblends/cordis/db'
|
|
293
293
|
|
|
294
294
|
export const name = 'notes'
|
|
295
295
|
export const inject = ['db']
|
|
@@ -12,7 +12,7 @@ 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 hosting platforms
|
|
15
|
+
Node.js. Bun is experimental. The other hosting platforms have no adapter in 1.0; they are listed on [Other runtimes](../getting-started/other-runtimes.md).
|
|
16
16
|
|
|
17
17
|
## Where are the parts marked "not yet available"?
|
|
18
18
|
|
|
@@ -277,7 +277,7 @@ export default function App() {
|
|
|
277
277
|
On Node you can skip the bundler: [Client components without a bundler](client-modules.md) serves `.tsx` client modules to the browser with
|
|
278
278
|
this runtime and one script tag, in development and in production.
|
|
279
279
|
|
|
280
|
-
There is a small JSX Runtime for Client Components. Using this will result in smaller bundled results than using `acrylblends/jsx`. Specify `acrylblends/jsx/dom` in `tsconfig.json`.
|
|
280
|
+
There is a small JSX Runtime for Client Components. Using this will result in smaller bundled results than using `acrylblends/jsx`. Specify `acrylblends/jsx/dom` in `tsconfig.json`.
|
|
281
281
|
|
|
282
282
|
```json
|
|
283
283
|
{
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# What runs in production
|
|
2
|
+
|
|
3
|
+
`acrylblends` has two kinds of parts: the ones an app is built from, which run in production, and the development tools that let an agent (or you) work on the running app, which do not. This page says which is which, and exactly what the safety checks around live plugin install do and do not promise. A test (`plugins/driver-pty/test/production.test.ts`) mounts every development tool in one app and checks the table below.
|
|
4
|
+
|
|
5
|
+
## The table
|
|
6
|
+
|
|
7
|
+
| Part | In development | With `NODE_ENV=production` |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Your routes and plugins, the router, the server, state, the `db` and `kv` ports and their providers | run | run |
|
|
10
|
+
| **The door** (`acryl-door`: `.acryl/bin/acryl`, the page script, the operator routes) | **experimental**: starts when the server is up | starts nothing: no service, no route, no `.acryl/` folder, no script in the page. `ACRYL_DOOR=off` also turns it off. A host that means to run it in production sets `allowInProduction: true` in the plugin's config |
|
|
11
|
+
| **The agent panel** (`agent-panel`) | **experimental**: a script tag on every HTML page and `/api/agent-panel/*` | does nothing (no tag, no routes). `ACRYL_DOOR=off` too |
|
|
12
|
+
| **The PTY driver** (`driver-pty`) | **experimental**: terminal drivers in the panel, `/api/driver-pty/*` | does nothing. `ACRYL_DOOR=off` too |
|
|
13
|
+
| **Live plugin install** (`extensions`, `builder`) | **experimental**: install, update, remove and reload plugins while the app runs | the `nodejs`, `notes` and `chat` templates leave the `extensions` row out; the `agent` template does not (see below) |
|
|
14
|
+
| `acrylblends/bun` | **experimental**: one smoke gate runs it on Bun | the same |
|
|
15
|
+
|
|
16
|
+
Experimental means: it works and is gated, its interface may still change in a minor version, and it is not for the public internet. The door and the panel are loopback and own-page only, with a per-run secret and an audit log; that is a convenience fence for a developer's machine, not an authentication system.
|
|
17
|
+
|
|
18
|
+
## The `agent` template is a development app
|
|
19
|
+
|
|
20
|
+
The `agent` template mounts an agent behind `POST /ask` that can write plugins into the running app (`builder`) and install them (`extensions`). Only its door is left out in production. Do not deploy it as it is: remove the `agent`, `builder` and `extensions` rows from `blend.yaml` (or start from `blank`, `nodejs`, `notes`, `chat` or `todo`), and if live install is wanted anywhere shared, set `approval: true` so an install waits for `POST /extensions/approve`.
|
|
21
|
+
|
|
22
|
+
## What live plugin install does and does not promise
|
|
23
|
+
|
|
24
|
+
Installing a plugin (`ctx.extensions.install`, the door's `plugin add`, the builder's `install_plugin`) is a **correctness check, not a security boundary**:
|
|
25
|
+
|
|
26
|
+
- It stages a content-hashed copy, imports it, mounts it, and reports the real state (`active`, `pending` with what it waits for, or `failed` with the error). If an update fails to start, the previous version is mounted again. A ledger line records every change.
|
|
27
|
+
- **The plugin's code runs inside the app's own process with the app's permissions**: it can read the app's files and environment, open sockets and start processes. Nothing in the install check stops that.
|
|
28
|
+
- At start-up the app reinstalls what was installed, and installs a new folder only from a source trusted to `install` (the app's own `plugins/` is). Code that arrived through a `git pull` into an untrusted source is listed, not run.
|
|
29
|
+
- `approval: true` holds an install until someone posts to `/extensions/approve`: it decides *whether* a plugin installs, not what an installed plugin can do.
|
|
30
|
+
|
|
31
|
+
## What the sandbox does and does not stop
|
|
32
|
+
|
|
33
|
+
`ctx.sandbox` runs code you do not trust, such as a **tool** an agent just wrote, in a separate Node process with the permission model on (`node --permission`):
|
|
34
|
+
|
|
35
|
+
- It **stops**: reading any file outside the tool's own folder; writing files; starting child processes or workers; loading native addons; using the inspector. It gets an empty environment (no keys, no paths). It is killed after a time limit and capped in memory and output size. Calls are independent: nothing survives into the next one.
|
|
36
|
+
- It **does not stop**: network access (Node 24 has no network permission: restrict egress outside the process where that matters), and it does not apply to plugins installed live (those are not run in the sandbox).
|
|
37
|
+
|
|
38
|
+
## Checklist for a deployment
|
|
39
|
+
|
|
40
|
+
1. `NODE_ENV=production` (the process manager or the Dockerfile sets it; the templates' Dockerfile does).
|
|
41
|
+
2. Start from a template that is not `agent`, or remove the agent rows.
|
|
42
|
+
3. Do not set `allowInProduction` on the door unless the host is a single-user desktop app.
|
|
43
|
+
4. Keep `ACRYL_DOOR=off` in any environment where an agent should not be able to see the page.
|
|
@@ -584,21 +584,13 @@ const route = app.put(
|
|
|
584
584
|
|
|
585
585
|
## Custom `fetch` method
|
|
586
586
|
|
|
587
|
-
You can set the custom `fetch` method.
|
|
588
|
-
|
|
589
|
-
In the following example script for Cloudflare Worker, the Service Bindings' `fetch` method is used instead of the default `fetch`.
|
|
590
|
-
|
|
591
|
-
```toml
|
|
592
|
-
# wrangler.toml
|
|
593
|
-
services = [
|
|
594
|
-
{ binding = "AUTH", service = "auth-service" },
|
|
595
|
-
]
|
|
596
|
-
```
|
|
587
|
+
You can set the custom `fetch` method: anything with the `fetch` signature works in place of the global one, such as a client that adds a header or a test double.
|
|
597
588
|
|
|
598
589
|
```ts
|
|
599
590
|
// src/client.ts
|
|
600
591
|
const client = hc<CreateProfileType>('http://localhost', {
|
|
601
|
-
fetch:
|
|
592
|
+
fetch: (input, init) =>
|
|
593
|
+
fetch(input, { ...init, headers: { ...init?.headers, authorization: `Bearer ${token}` } }),
|
|
602
594
|
})
|
|
603
595
|
```
|
|
604
596
|
|