@astrale-os/adapter-cloudflare 0.1.7 → 0.1.9
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/dist/assets-pack.d.ts +1 -1
- package/dist/assets-pack.js +1 -1
- package/dist/build.d.ts +15 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +15 -0
- package/dist/build.js.map +1 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -1
- package/dist/client.js.map +1 -1
- package/dist/cloudflare.d.ts +15 -3
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +73 -21
- package/dist/cloudflare.js.map +1 -1
- package/dist/codegen/worker.d.ts +26 -6
- package/dist/codegen/worker.d.ts.map +1 -1
- package/dist/codegen/worker.js +70 -54
- package/dist/codegen/worker.js.map +1 -1
- package/dist/codegen/wrangler.d.ts +11 -2
- package/dist/codegen/wrangler.d.ts.map +1 -1
- package/dist/codegen/wrangler.js +11 -5
- package/dist/codegen/wrangler.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/params.d.ts +30 -30
- package/dist/params.d.ts.map +1 -1
- package/dist/parse-output.d.ts +1 -1
- package/dist/parse-output.js +1 -1
- package/package.json +6 -2
- package/src/assets-pack.ts +1 -1
- package/src/build.ts +15 -0
- package/src/client.ts +11 -1
- package/src/cloudflare.ts +77 -23
- package/src/codegen/worker.ts +79 -59
- package/src/codegen/wrangler.ts +15 -5
- package/src/index.ts +6 -3
- package/src/params.ts +32 -31
- package/src/parse-output.ts +1 -1
- package/template/.agents/skills/astrale-cli/SKILL.md +25 -11
- package/template/.agents/skills/astrale-domain/SKILL.md +60 -32
- package/template/.env.example +8 -0
- package/template/README.md +26 -10
- package/template/astrale.config.ts +22 -29
- package/template/client/README.md +2 -2
- package/template/client/src/styles.css +4 -1
- package/template/client/tsconfig.json +1 -1
- package/template/client/vite.config.ts +3 -3
- package/template/client/vitest.config.ts +1 -1
- package/template/core/keys.ts +28 -0
- package/template/{methods → core}/note.ts +42 -25
- package/template/deps.ts +25 -0
- package/template/domain.ts +33 -0
- package/template/env.ts +11 -0
- package/template/integrations/summary/heuristic.ts +25 -0
- package/template/integrations/summary/http.ts +69 -0
- package/template/integrations/summary/port.ts +21 -0
- package/template/integrations/summary/registry.ts +52 -0
- package/template/package.json +2 -3
- package/template/runtime/index.ts +62 -0
- package/template/schema/index.ts +2 -0
- package/template/schema/note.ts +5 -2
- package/template/tsconfig.json +13 -2
- package/template/views/note.ts +1 -1
- package/dist/astrale.d.ts +0 -27
- package/dist/astrale.d.ts.map +0 -1
- package/dist/astrale.js +0 -212
- package/dist/astrale.js.map +0 -1
- package/src/astrale.ts +0 -244
- package/template/methods/index.ts +0 -66
- package/template/schema/compiled.ts +0 -14
package/src/codegen/wrangler.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* `wrangler.jsonc` that used to be hand-copied into every domain — now an
|
|
4
4
|
* internal adapter detail the dev never sees.
|
|
5
5
|
*
|
|
6
|
-
* `main` is `./worker.gen.ts` (same dir). `assets.directory`
|
|
7
|
-
*
|
|
6
|
+
* `main` is `./worker.gen.ts` (same dir). `assets.directory` points at the
|
|
7
|
+
* project's Vite build (`CLIENT_DIST_DIR`). A `SELF` service binding enables autobinding
|
|
8
8
|
* (a handler calling back into its own domain). Custom-domain deploys attach a
|
|
9
9
|
* `custom_domain` route; otherwise the Worker ships to `*.workers.dev`.
|
|
10
10
|
*
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { CLIENT_DIST_DIR } from '../client'
|
|
15
16
|
import { deepMergeConfig } from './merge'
|
|
16
17
|
|
|
17
18
|
export interface WranglerCodegenOptions {
|
|
@@ -24,6 +25,12 @@ export interface WranglerCodegenOptions {
|
|
|
24
25
|
vars?: Record<string, string>
|
|
25
26
|
/** Add the SELF service binding (autobinding). */
|
|
26
27
|
selfBinding: boolean
|
|
28
|
+
/**
|
|
29
|
+
* Add a service binding to a router Worker for instance-host subrequest
|
|
30
|
+
* routing (see `CloudflareParams.router`). Pairs with the worker
|
|
31
|
+
* entry's `routeSubrequest`.
|
|
32
|
+
*/
|
|
33
|
+
router?: { binding: string; service: string }
|
|
27
34
|
/**
|
|
28
35
|
* Raw wrangler config to deep-merge over the generated base — the escape
|
|
29
36
|
* hatch for extra bindings (KV, R2, D1, queues, service bindings, extra
|
|
@@ -52,16 +59,19 @@ export function generateWranglerConfig(opts: WranglerCodegenOptions): string {
|
|
|
52
59
|
|
|
53
60
|
if (opts.hasClient) {
|
|
54
61
|
config.assets = {
|
|
55
|
-
directory:
|
|
62
|
+
directory: `../${CLIENT_DIST_DIR}`,
|
|
56
63
|
binding: 'ASSETS',
|
|
57
64
|
not_found_handling: 'single-page-application',
|
|
58
65
|
run_worker_first: true,
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
const services: Array<{ binding: string; service: string }> = []
|
|
70
|
+
if (opts.selfBinding) services.push({ binding: 'SELF', service: opts.workerName })
|
|
71
|
+
if (opts.router) {
|
|
72
|
+
services.push({ binding: opts.router.binding, service: opts.router.service })
|
|
64
73
|
}
|
|
74
|
+
if (services.length > 0) config.services = services
|
|
65
75
|
|
|
66
76
|
if (opts.vars && Object.keys(opts.vars).length > 0) {
|
|
67
77
|
config.vars = opts.vars
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
|
|
3
|
-
* Cloudflare Worker.
|
|
3
|
+
* Cloudflare Worker on YOUR OWN Cloudflare account.
|
|
4
4
|
*
|
|
5
5
|
* import { cloudflare } from '@astrale-os/adapter-cloudflare'
|
|
6
6
|
*
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* `dev` runs `wrangler dev` locally; an env with no `route` ships to
|
|
13
13
|
* `*.workers.dev`; an env with a `route` ships to that custom domain.
|
|
14
|
+
*
|
|
15
|
+
* Prefer a managed deploy (no Cloudflare account)? Use
|
|
16
|
+
* `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
|
|
17
|
+
* `/build` toolkit and ships it through the Astrale platform.
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
|
-
export { astrale } from './astrale'
|
|
17
20
|
export { cloudflare } from './cloudflare'
|
|
18
|
-
export type {
|
|
21
|
+
export type { CloudflareParams } from './params'
|
package/src/params.ts
CHANGED
|
@@ -55,37 +55,38 @@ export interface CloudflareParams {
|
|
|
55
55
|
* worker-entry codegen and aren't supported through this field yet.
|
|
56
56
|
*/
|
|
57
57
|
wrangler?: Record<string, unknown>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Params for the Astrale-managed adapter (`astrale(envs)`). */
|
|
61
|
-
export interface AstraleParams {
|
|
62
58
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* off-localhost dev needs: wrangler binds 0.0.0.0 (reachable from outside)
|
|
84
|
-
* and `WORKER_URL` is pinned to this URL so the worker's per-request-Host
|
|
85
|
-
* identity (`iss`) doesn't drift to the proxy's internal hostname.
|
|
86
|
-
* Usually injected by `pnpm dev --host <url>` rather than written here.
|
|
59
|
+
* Route subrequests to platform INSTANCE hostnames through a service binding
|
|
60
|
+
* instead of the public edge. A Cloudflare Worker can't fetch a same-zone
|
|
61
|
+
* hostname served by another Worker — Cloudflare 522s it — so without this the
|
|
62
|
+
* worker can't fetch an instance kernel's JWKS to verify an inbound credential
|
|
63
|
+
* from that instance, and every cross-instance call (install hooks, the GUI's
|
|
64
|
+
* delegated calls, authed views) fails with `2002 Credential verification
|
|
65
|
+
* failed`. Point it at a router Worker that reaches those hosts internally
|
|
66
|
+
* (Astrale: `admin-router`).
|
|
67
|
+
*
|
|
68
|
+
* Wires BOTH halves the SDK needs: the `<binding>` service binding in the
|
|
69
|
+
* generated wrangler config, and a `routeSubrequest` predicate in the worker
|
|
70
|
+
* entry that diverts matching hosts through it.
|
|
71
|
+
*
|
|
72
|
+
* ON BY DEFAULT (targets `admin-router`) so every adapter-deployed worker can
|
|
73
|
+
* verify cross-instance credentials out of the box. Pass `false` to disable
|
|
74
|
+
* (a domain that never talks to another instance, or an account without a
|
|
75
|
+
* router), or an object to override the target / binding name / host match.
|
|
76
|
+
*
|
|
77
|
+
* router: false // opt out
|
|
78
|
+
* router: { service: 'my-router' } // override target
|
|
87
79
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
router?:
|
|
81
|
+
| false
|
|
82
|
+
| {
|
|
83
|
+
/** Router Worker the binding targets. Default `'admin-router'`. */
|
|
84
|
+
service?: string
|
|
85
|
+
/** Service-binding name the worker reads. Default `'ROUTER'`. */
|
|
86
|
+
binding?: string
|
|
87
|
+
/** Host suffix that marks an instance host. Default `'.astrale.ai'`. */
|
|
88
|
+
hostSuffix?: string
|
|
89
|
+
/** Min dot-separated label count for an instance host. Default `4` (`<slug>.<region>.astrale.ai`). */
|
|
90
|
+
minLabels?: number
|
|
91
|
+
}
|
|
91
92
|
}
|
package/src/parse-output.ts
CHANGED
|
@@ -14,7 +14,7 @@ const WORKERS_DEV_RE = /(https:\/\/[^\s]+\.workers\.dev)/i
|
|
|
14
14
|
* The fallback (when no explicit "Ready on" line) only accepts a localhost URL
|
|
15
15
|
* on the EXPECTED port — wrangler may print unrelated localhost URLs (e.g. an
|
|
16
16
|
* inspector/devtools endpoint on a different port) before readiness, and taking
|
|
17
|
-
* one of those would point `astrale
|
|
17
|
+
* one of those would point `astrale domain install <url>` at a dead endpoint.
|
|
18
18
|
*/
|
|
19
19
|
export function parseDevReadyUrl(text: string, port: number): string | undefined {
|
|
20
20
|
const ready = READY_RE.exec(text)
|
|
@@ -38,6 +38,7 @@ astrale logs <service>
|
|
|
38
38
|
astrale status
|
|
39
39
|
astrale browser
|
|
40
40
|
astrale instance ...
|
|
41
|
+
astrale domain ...
|
|
41
42
|
astrale admin ...
|
|
42
43
|
astrale identity ...
|
|
43
44
|
astrale auth ...
|
|
@@ -50,7 +51,7 @@ Command groups:
|
|
|
50
51
|
|---|---|
|
|
51
52
|
| Kernel | `call`, `token`, `get`, `ls`, `describe`, `query`, `logs` |
|
|
52
53
|
| Context | `status`, `whoami`, `use` |
|
|
53
|
-
| Management | `admin`, `instance`, `identity`, `auth`, `idp`, `update` |
|
|
54
|
+
| Management | `admin`, `instance`, `domain`, `identity`, `auth`, `idp`, `update` |
|
|
54
55
|
| Agent | `browser` |
|
|
55
56
|
|
|
56
57
|
Shared kernel options are merged onto kernel-touching commands at registration
|
|
@@ -148,7 +149,6 @@ astrale instance use my-app
|
|
|
148
149
|
astrale instance active
|
|
149
150
|
astrale instance bookmark staging --url https://kernel.example.com
|
|
150
151
|
astrale instance forget staging
|
|
151
|
-
astrale instance install https://domain.example.com
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
Important distinctions:
|
|
@@ -164,23 +164,37 @@ Important distinctions:
|
|
|
164
164
|
## Domain Dev Workflow
|
|
165
165
|
|
|
166
166
|
**The `astrale` CLI is connect-only — it does not build, run, or deploy
|
|
167
|
-
domains.**
|
|
168
|
-
|
|
167
|
+
domains.** The `astrale domain` group manages the admin catalog and installs a
|
|
168
|
+
running domain onto an instance (`list`, `publish`, `install`); it does NOT
|
|
169
|
+
build or run domains. `astrale domain list` shows the published catalog (add
|
|
170
|
+
`--check` to probe each URL's reachability, `--default-only` for the
|
|
171
|
+
install-on-every-instance set, `-q` to pipe install URLs). Building, running,
|
|
172
|
+
and deploying live in two separate tools:
|
|
169
173
|
|
|
170
174
|
- **`create-astrale-domain`** scaffolds a new standalone domain project
|
|
171
175
|
(`pnpm create astrale-domain <slug>`), writing an `astrale.config.ts`.
|
|
172
|
-
- **`astrale-domain`** (the `@astrale-os/
|
|
176
|
+
- **`astrale-domain`** (the `@astrale-os/sdk` bin, behind the project's
|
|
173
177
|
`pnpm dev` / `pnpm prod` scripts) runs `dev | prod | deploy <env> | build`.
|
|
174
178
|
|
|
175
|
-
Domains are **installed by URL**, never from a file
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
Domains are **installed by URL or catalog origin**, never from a file (there is
|
|
180
|
+
no committed `spec.json`). `astrale domain install` has two modes:
|
|
181
|
+
|
|
182
|
+
- **default (via admin)** — `astrale domain install <origin|url> -i <slug>`
|
|
183
|
+
installs a PUBLISHED domain through the admin control plane
|
|
184
|
+
(`DomainEntry.install`), addressed by its catalog `origin` (the unique
|
|
185
|
+
registry key) or `url`. Run it bare to pick from the catalog interactively.
|
|
186
|
+
The target instance is the active one or `-i <slug>` and **must be
|
|
187
|
+
admin-managed** (otherwise it fails loudly and points you at `--direct`).
|
|
188
|
+
- **`--direct`** — `astrale domain install <url> --direct` installs a url
|
|
189
|
+
straight onto the instance kernel (`Root.installDomain`), bypassing the
|
|
190
|
+
catalog. Works on ANY instance you can authenticate to (managed, bookmarked,
|
|
191
|
+
or local), using your own authority, and is the only mode that runs the
|
|
192
|
+
identity-override consent gate. Use it for dev/local instances and
|
|
193
|
+
freshly-deployed, not-yet-published workers.
|
|
180
194
|
|
|
181
195
|
With the **managed (`astrale`) adapter**, `pnpm prod` publishes the bundle
|
|
182
196
|
through the platform AND installs it on the configured instance in one step —
|
|
183
|
-
no manual `
|
|
197
|
+
no manual `domain install`. The service serves at
|
|
184
198
|
`https://<name>-<hash>.svc.<region>.astrale.ai` (the CLI session is the auth).
|
|
185
199
|
|
|
186
200
|
For authoring domains end-to-end (schema, handlers, external APIs, deploys),
|
|
@@ -19,7 +19,9 @@ an RPC layer; you declare a contract and implement handlers.
|
|
|
19
19
|
## 0 · Start
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npx -y create-astrale-domain@latest my-domain --yes # scaffold
|
|
22
|
+
npx -y create-astrale-domain@latest my-domain --yes --instance <slug> # scaffold
|
|
23
|
+
# managed `astrale` adapter is the DEFAULT; `--instance` stamps the
|
|
24
|
+
# target instance into prod (`--adapter cloudflare` = your own CF account)
|
|
23
25
|
cd my-domain && pnpm install
|
|
24
26
|
pnpm dev # local wrangler dev (prints a URL; install it on an instance to test)
|
|
25
27
|
# port 8787 taken? dev AUTO-PICKS a free one and prints it —
|
|
@@ -34,14 +36,21 @@ pnpm prod # deploy + (astrale adapter) auto-install on your instance
|
|
|
34
36
|
Project anatomy (the scaffold is the reference — read its comments):
|
|
35
37
|
|
|
36
38
|
```
|
|
37
|
-
|
|
39
|
+
domain.ts # THE manifest — wires it ALL: defineDomain({ schema, methods,
|
|
40
|
+
# deps, views, functions, client }) + origin/postInstall.
|
|
41
|
+
# Modules are imported & passed EXPLICITLY (no folder magic);
|
|
42
|
+
# a renamed module is a compile error here, not a missing route.
|
|
43
|
+
astrale.config.ts # binds the domain to its deploy adapter (deploy(domain, …)) — node-only
|
|
38
44
|
schema/ # classes/interfaces/edges — the contract (zod props, fn signatures)
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
# + compiled.ts (D = compileDomain) — the public compiled entry
|
|
46
|
+
core/ # pure, transport-agnostic logic + keys.ts (compiled accessors)
|
|
47
|
+
integrations/ # external-API ports + adapters + a lazy registry (see §4) — what deps is built from
|
|
48
|
+
runtime/index.ts # composition root: the methods map; each execute resolves deps → calls core logic
|
|
41
49
|
functions/ # standalone remote functions (webhook-shaped endpoints)
|
|
42
50
|
views/ # iframe-mountable UI declarations (defineView)
|
|
43
51
|
client/ # the SPA served under /ui (vite)
|
|
44
|
-
|
|
52
|
+
deps.ts # env → typed Deps container (the seam defineDomain({ deps }) mounts)
|
|
53
|
+
env.ts # typed worker env — config + secrets arrive here, mapped by deps.ts → ctx.deps
|
|
45
54
|
```
|
|
46
55
|
|
|
47
56
|
Iteration loop: edit → `pnpm prod` → call it. Managed redeploys keep the same
|
|
@@ -95,9 +104,10 @@ Conventions and rules:
|
|
|
95
104
|
|
|
96
105
|
## 2 · Handlers
|
|
97
106
|
|
|
98
|
-
Separate LOGIC from WIRING. Logic = plain async functions taking the
|
|
99
|
-
client + ports + params (testable with fakes). Wiring = `method()` /
|
|
100
|
-
`classMethods()` / `interfaceMethods()` in `
|
|
107
|
+
Separate LOGIC from WIRING. Logic = plain async functions in `core/` taking the
|
|
108
|
+
kernel client + ports + params (testable with fakes). Wiring = `method()` /
|
|
109
|
+
`classMethods()` / `interfaceMethods()` in `runtime/index.ts` (the composition
|
|
110
|
+
root — the only place request context + `deps` meet the `core/` logic):
|
|
101
111
|
|
|
102
112
|
```ts
|
|
103
113
|
const kickoff = method(schema, 'Project', 'kickoff', {
|
|
@@ -111,9 +121,11 @@ const kickoff = method(schema, 'Project', 'kickoff', {
|
|
|
111
121
|
|
|
112
122
|
Handler context: `kernel` (callback client bound to the composed credential —
|
|
113
123
|
caller's delegated authority ∪ this function's own), `self` (instance methods),
|
|
114
|
-
`params` (zod-validated), `deps` (your typed `
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
`params` (zod-validated), `deps` (your typed `Deps` from `deps.ts`; raw `Env` if
|
|
125
|
+
you omit the mapper), `auth` (principal, verified claims). Resolve ports from
|
|
126
|
+
`deps` per request (`deps.summarizer()` — the registry builds + caches them per
|
|
127
|
+
isolate) — NEVER construct external clients at module load (workers must be
|
|
128
|
+
import-side-effect-free).
|
|
117
129
|
|
|
118
130
|
## 3 · Talking to the kernel (and other domains)
|
|
119
131
|
|
|
@@ -153,16 +165,20 @@ Almost every real domain wraps an external API (payment, calendar, LLM
|
|
|
153
165
|
gateway, cloud provider). The shape (see admin/domain for the full-scale
|
|
154
166
|
example — Scaleway/WorkOS/KV):
|
|
155
167
|
|
|
156
|
-
1. **Port** — a narrow interface
|
|
157
|
-
(`WeatherClient { forecast(city) }
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
1. **Port** — a narrow interface in `integrations/<feature>/port.ts` declaring
|
|
169
|
+
only what the logic needs (`WeatherClient { forecast(city) }`, the scaffold's
|
|
170
|
+
`Summarizer { summarize(body) }`). `core/` logic depends on the port, never
|
|
171
|
+
on fetch/SDKs/env.
|
|
172
|
+
2. **Adapter(s) + registry** — `createXClient(config)` in
|
|
173
|
+
`integrations/<feature>/` (one per backend), plus a `registry.ts` that reads
|
|
174
|
+
config + secrets from `env`, validates LOUDLY (`if (!env.X_API_KEY) throw`),
|
|
175
|
+
sets base URLs (overridable so tests point at a stub), timeouts
|
|
176
|
+
(`AbortSignal.timeout`), and maps upstream failures to errors with the
|
|
177
|
+
upstream detail in `cause`. The registry builds the chosen adapter LAZILY +
|
|
178
|
+
caches it per isolate (a worker never validates an unused backend's env).
|
|
179
|
+
3. **Wiring** — `deps.ts` mounts the registry (`defineDomain({ deps })`); the
|
|
180
|
+
`execute` hook resolves the PORT from `deps` (`deps.summarizer()`) per
|
|
181
|
+
request and passes it to the `core/` logic.
|
|
166
182
|
|
|
167
183
|
Secrets & config:
|
|
168
184
|
- Declare every var as a typed field on `Env` in `env.ts`. Secrets ship via
|
|
@@ -200,8 +216,10 @@ Anti-patterns (all observed in production code — don't):
|
|
|
200
216
|
## 5 · Views & standalone functions (webhooks)
|
|
201
217
|
|
|
202
218
|
- `defineView({ auth, mount: '/ui/contact', viewFor: selfOf(Contact) })` in
|
|
203
|
-
`views
|
|
204
|
-
|
|
219
|
+
`views/`, collected into the `views` map in `views/index.ts`, which
|
|
220
|
+
`astrale.config.ts` imports and passes to `defineDomain({ views })`. The MAP
|
|
221
|
+
KEY is the view's node slug (`'ui-contact'` → `/<origin>/core/views/ui-contact`).
|
|
222
|
+
Installs a View
|
|
205
223
|
node whose binding URL = `<serving url><mount>` (managed:
|
|
206
224
|
`https://<slug>.svc.<region>.astrale.ai/ui/contact`). The client/ SPA
|
|
207
225
|
serves `/ui/*` — BOTH adapters ship it (cloudflare via Workers Assets;
|
|
@@ -231,9 +249,17 @@ createNode + USE on the class — narrow, never root).
|
|
|
231
249
|
the render ctx exposes `ctx.selfKernel()` (sdk ≥0.1.5) — a session as THE
|
|
232
250
|
VIEW'S OWN identity. Read with it directly in `render`, template to HTML:
|
|
233
251
|
`const k = await ctx.selfKernel(); const rows = await k.call('/messages::listChildren', {})`.
|
|
234
|
-
Grant the view's function identity the READ-side minimum in your seed
|
|
235
|
-
|
|
236
|
-
|
|
252
|
+
Grant the view's function identity the READ-side minimum in your seed —
|
|
253
|
+
the grant call is dispatched ON the identity node, with a bitmask:
|
|
254
|
+
```ts
|
|
255
|
+
import { READ, USE, toMask } from '@astrale-os/kernel-core'
|
|
256
|
+
await kernel.call('/<origin>/core/views/<view-slug>::grantPerm', { node: '/messages', perms: toMask(READ) })
|
|
257
|
+
await kernel.call('/<origin>/core/views/<view-slug>::grantPerm', { node: '/:kernel.astrale.ai:interface.Container:listChildren', perms: toMask(USE) })
|
|
258
|
+
```
|
|
259
|
+
Needs `deps.INSTANCE_KERNEL_URL` — managed deploys set it automatically; on
|
|
260
|
+
the cloudflare adapter set it yourself: the instance's kernel API base is
|
|
261
|
+
`https://<instance-slug>.eu.astrale.ai/api` (a vars entry or secret).
|
|
262
|
+
Public-input
|
|
237
263
|
hygiene: HTML-escape every stored string at render. (Prefer `selfKernel`
|
|
238
264
|
over the `SELF` service binding for graph reads — `SELF` exists on both
|
|
239
265
|
cloudflare and current managed runtimes, but it costs an extra HTTP hop and
|
|
@@ -266,11 +292,13 @@ capture when the sender's shape is fixed (see distribution's proxy functions).
|
|
|
266
292
|
|
|
267
293
|
## 7 · Deploy & install
|
|
268
294
|
|
|
269
|
-
Adapter choice in `astrale.config.ts
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
`wrangler`
|
|
273
|
-
|
|
295
|
+
Adapter choice in `astrale.config.ts` (each adapter is its OWN package — swap
|
|
296
|
+
BOTH the import and the call):
|
|
297
|
+
- `cloudflare({...})` from `@astrale-os/adapter-cloudflare` — your CF account;
|
|
298
|
+
`dev` (wrangler dev), `prod` (route or workers.dev); ships secrets + SPA
|
|
299
|
+
assets; extra bindings via a deep-merged `wrangler` block.
|
|
300
|
+
- `astrale({ dev: {...}, prod: { instance: '<slug>' } })` from
|
|
301
|
+
`@astrale-os/adapter-astrale` — managed (the scaffold's DEFAULT): publishes
|
|
274
302
|
the bundle THROUGH the platform and installs it as a host-local service next
|
|
275
303
|
to your instance (`https://<name>-<hash>.svc.<region>.astrale.ai`). No CF
|
|
276
304
|
account; auth = your `astrale auth login` session. Ships the client SPA
|
|
@@ -292,7 +320,7 @@ service URL stable).
|
|
|
292
320
|
(`/:origin:class.X:seed`; tree paths are rejected) and MUST be idempotent
|
|
293
321
|
(catch path-conflicts). Seed folders, defaults, and demo data here.
|
|
294
322
|
|
|
295
|
-
Manual install of any served domain: `astrale
|
|
323
|
+
Manual install of any served domain: `astrale domain install <url> --direct`.
|
|
296
324
|
|
|
297
325
|
The managed catalog surface (what `pnpm prod` shells into) is callable
|
|
298
326
|
directly — useful for recovery and inspection:
|
package/template/.env.example
CHANGED
|
@@ -4,3 +4,11 @@
|
|
|
4
4
|
# prod. These files are gitignored — copy this to `.env.dev` and fill in.
|
|
5
5
|
#
|
|
6
6
|
# EXAMPLE_API_KEY=sk-...
|
|
7
|
+
#
|
|
8
|
+
# ── Summarizer (the example external-API integration) ──────────────────────
|
|
9
|
+
# The scaffold summarizes notes with a no-network heuristic by default. To use a
|
|
10
|
+
# real OpenAI-compatible model instead, set NOTE_SUMMARIZER=http and provide:
|
|
11
|
+
# NOTE_SUMMARIZER=http
|
|
12
|
+
# SUMMARIZER_API_KEY=sk-...
|
|
13
|
+
# SUMMARIZER_BASE_URL=https://api.openai.com/v1
|
|
14
|
+
# SUMMARIZER_MODEL=gpt-4o-mini
|
package/template/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# astrale-domain
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
A standalone [Astrale](https://astrale.ai) domain, deployed as
|
|
4
|
+
A standalone [Astrale](https://astrale.ai) domain, deployed as an Astrale-managed
|
|
5
|
+
service (default) or as a worker on your own Cloudflare account.
|
|
5
6
|
|
|
6
7
|
## For AI agents
|
|
7
8
|
|
|
@@ -18,20 +19,35 @@ calls), use the `astrale-cli` skill.
|
|
|
18
19
|
```bash
|
|
19
20
|
pnpm install
|
|
20
21
|
pnpm dev # wrangler dev → prints a local URL
|
|
21
|
-
astrale
|
|
22
|
+
astrale domain install <url> --direct # mount it on an instance (CLI)
|
|
22
23
|
pnpm prod # deploy prod → prints a URL
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
## What you write
|
|
26
|
+
## What you write
|
|
26
27
|
|
|
27
28
|
```
|
|
28
|
-
schema/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
schema/ classes + interfaces (the data model) + compiled accessors
|
|
30
|
+
core/ pure, transport-agnostic logic + keys.ts (never hand-write keys)
|
|
31
|
+
integrations/ external-API ports + adapters + a lazy registry (built into deps)
|
|
32
|
+
runtime/ the execute() handlers — the composition root (the methods map)
|
|
33
|
+
views/ iframe-mountable UIs
|
|
34
|
+
functions/ standalone Functions (e.g. webhooks)
|
|
35
|
+
client/ the SPA served under /ui
|
|
36
|
+
deps.ts env → typed Deps container (what handlers read as ctx.deps)
|
|
37
|
+
domain.ts wires it all together — the worker-safe definition
|
|
38
|
+
astrale.config.ts binds the domain to its deploy adapter (node-only)
|
|
33
39
|
```
|
|
34
40
|
|
|
41
|
+
`domain.ts` is the one place everything is declared: it imports the modules
|
|
42
|
+
above and passes them to `defineDomain({ schema, methods, deps, views,
|
|
43
|
+
functions, client, … })` alongside the identity (`origin`, `requires`,
|
|
44
|
+
`postInstall`). Nothing is discovered by folder name — a renamed or mistyped
|
|
45
|
+
module is a compile error at that call, never a silently-missing route. Drop a
|
|
46
|
+
field (`deps`, `views`, `functions`, `client`) when the domain has none.
|
|
47
|
+
`astrale.config.ts` then binds that domain to a deploy adapter with
|
|
48
|
+
`deploy(domain, cloudflare({ … }))` — keeping the node-only adapter (wrangler,
|
|
49
|
+
filesystem) out of the worker bundle.
|
|
50
|
+
|
|
35
51
|
Everything else — the Worker entry, the wrangler config, the signing identity —
|
|
36
52
|
is generated under `.astrale/` (gitignored) by the adapter. You never edit it.
|
|
37
53
|
|
|
@@ -71,8 +87,8 @@ adapter-owned keys `name`, `main`, `assets`, `routes` are rejected (use
|
|
|
71
87
|
|
|
72
88
|
## The loop
|
|
73
89
|
|
|
74
|
-
- **Edit a handler** (`
|
|
75
|
-
- **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale
|
|
90
|
+
- **Edit a handler** (`core/` logic or `runtime/` wiring) → hot-reloads at the same URL. Nothing to reinstall.
|
|
91
|
+
- **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale domain install <url> --direct`.
|
|
76
92
|
- **`postInstall`** (the static `Note.seed` method) runs once after install, as
|
|
77
93
|
the system identity, so the domain can seed itself and set its own grants. It
|
|
78
94
|
must be a class-hosted static addressed by a typed colon-path
|
|
@@ -1,44 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* astrale.config.ts — the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* astrale.config.ts — binds the worker-safe domain (`domain.ts`) to its deploy
|
|
3
|
+
* adapter for the `astrale-domain` CLI. This is a NODE-only module: it imports
|
|
4
|
+
* the adapter (wrangler + filesystem). The generated worker never imports this
|
|
5
|
+
* file — it imports `domain.ts` directly — so the adapter stays out of the bundle.
|
|
6
|
+
*
|
|
7
|
+
* This variant deploys to YOUR OWN Cloudflare account (the `cloudflare` adapter).
|
|
7
8
|
*
|
|
8
9
|
* pnpm dev # wrangler dev → prints a local URL
|
|
9
|
-
* astrale
|
|
10
|
-
* pnpm prod # deploy
|
|
10
|
+
* astrale domain install <url> --direct # mount the dev URL on an instance
|
|
11
|
+
* pnpm prod # deploy to your Cloudflare account
|
|
11
12
|
*/
|
|
12
13
|
import { cloudflare } from '@astrale-os/adapter-cloudflare'
|
|
13
|
-
import {
|
|
14
|
+
import { deploy } from '@astrale-os/sdk'
|
|
14
15
|
// No Cloudflare account? Swap the adapter for the Astrale-managed one — it
|
|
15
16
|
// publishes the same bundle THROUGH the platform and installs it on your
|
|
16
|
-
// instance
|
|
17
|
+
// instance (auth = your `astrale auth login` session):
|
|
17
18
|
//
|
|
18
|
-
// import { astrale } from '@astrale-os/adapter-
|
|
19
|
-
//
|
|
20
|
-
// dev: { secrets: '.env.dev' },
|
|
21
|
-
// prod: { instance: '<your-instance-slug>' } // pnpm prod → managed deploy
|
|
22
|
-
// })
|
|
19
|
+
// import { astrale } from '@astrale-os/adapter-astrale'
|
|
20
|
+
// export default deploy(domain, astrale({
|
|
21
|
+
// dev: { secrets: '.env.dev' }, // local wrangler dev, unchanged
|
|
22
|
+
// prod: { instance: '<your-instance-slug>' }, // pnpm prod → managed deploy
|
|
23
|
+
// }))
|
|
23
24
|
|
|
24
|
-
import {
|
|
25
|
+
import { domain } from './domain'
|
|
25
26
|
|
|
26
|
-
export default
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// deliberately alias that identity (you'll get a DANGER prompt at install).
|
|
30
|
-
postInstall: `/:${schema.domain}:class.Note:seed`,
|
|
31
|
-
adapter: cloudflare({
|
|
27
|
+
export default deploy(
|
|
28
|
+
domain,
|
|
29
|
+
cloudflare({
|
|
32
30
|
// Local dev: `wrangler dev`. No route → URL is http://localhost:8787.
|
|
33
31
|
dev: { secrets: '.env.dev' },
|
|
34
32
|
// Custom-domain prod. Drop `route` to ship to *.workers.dev instead.
|
|
35
33
|
prod: { route: 'astrale-domain.example.dev', secrets: '.env.prod' },
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// adapter's `SELF` binding and `nodejs_compat` are preserved):
|
|
39
|
-
// dev: {
|
|
40
|
-
// secrets: '.env.dev',
|
|
41
|
-
// wrangler: { kv_namespaces: [{ binding: 'CACHE', id: '<kv-id>' }] },
|
|
42
|
-
// },
|
|
34
|
+
// Author secrets ship via `secrets: '.env.prod'` on any env. Extra bindings
|
|
35
|
+
// (KV, R2, D1, queues, …) ride a `wrangler` block on any env (deep-merged).
|
|
43
36
|
}),
|
|
44
|
-
|
|
37
|
+
)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A small React + Vite SPA that renders the domain's `ui-note` View. It is loaded
|
|
4
4
|
inside an iframe mounted by the Astrale shell, runs the shell handshake to learn
|
|
5
5
|
its target node id and a delegation token, fetches that Note from the kernel,
|
|
6
|
-
and renders its title/body. Built into
|
|
6
|
+
and renders its title/body. Built into `../.dist/` and served by the
|
|
7
7
|
generated worker (`.astrale/`) under `/ui/*` via its `ASSETS` binding.
|
|
8
8
|
|
|
9
9
|
## Self-contained on purpose
|
|
@@ -38,7 +38,7 @@ __tests__/
|
|
|
38
38
|
## Dev loops
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
pnpm dev # vite build --watch →
|
|
41
|
+
pnpm dev # vite build --watch → ../.dist/ (worker auto-reloads, no HMR)
|
|
42
42
|
pnpm dev:hmr # vite dev on http://127.0.0.1:5173/ (React fast-refresh)
|
|
43
43
|
pnpm test # vitest run (happy-dom; fake parent + fake kernel, JSON-only)
|
|
44
44
|
```
|
|
@@ -2,10 +2,10 @@ import viteReact from '@vitejs/plugin-react'
|
|
|
2
2
|
import { defineConfig } from 'vite'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Client SPA for the domain's `ui-note` View. Built into
|
|
5
|
+
* Client SPA for the domain's `ui-note` View. Built into `../.dist/`,
|
|
6
6
|
* served by the generated worker via its `ASSETS` binding (`.astrale/`).
|
|
7
7
|
*
|
|
8
|
-
* `base: '/ui/'` + `outDir: '
|
|
8
|
+
* `base: '/ui/'` + `outDir: '../.dist'`: Vite emits asset refs as
|
|
9
9
|
* `/ui/assets/<hash>.js`; the worker strips the `/ui` prefix before delegating
|
|
10
10
|
* to `ASSETS`, so files resolve from the directory root. `index.html` is the
|
|
11
11
|
* SPA fallback for `/ui/<anything>`.
|
|
@@ -26,7 +26,7 @@ export default defineConfig({
|
|
|
26
26
|
base: '/ui/',
|
|
27
27
|
plugins: [viteReact()],
|
|
28
28
|
build: {
|
|
29
|
-
outDir: '
|
|
29
|
+
outDir: '../.dist',
|
|
30
30
|
emptyOutDir: true,
|
|
31
31
|
sourcemap: false,
|
|
32
32
|
},
|
|
@@ -5,7 +5,7 @@ import { defineConfig } from 'vitest/config'
|
|
|
5
5
|
* Vitest config for the self-contained client. A DOM env (`happy-dom`) backs
|
|
6
6
|
* the React render + `window.postMessage`/`MessageChannel` used by the fake
|
|
7
7
|
* shell-parent harness. Kept separate from `vite.config.ts` (which sets the
|
|
8
|
-
* `/ui/` base +
|
|
8
|
+
* `/ui/` base + `../.dist` build) so tests don't inherit the SPA build
|
|
9
9
|
* options.
|
|
10
10
|
*/
|
|
11
11
|
export default defineConfig({
|