@astrale-os/adapter-cloudflare 0.1.8 → 0.1.10
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 +52 -18
- 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 +67 -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 +53 -18
- package/src/codegen/worker.ts +76 -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 +26 -12
- package/template/.agents/skills/astrale-domain/SKILL.md +46 -29
- package/template/.env.example +6 -0
- package/template/README.md +25 -10
- package/template/astrale.config.ts +27 -33
- package/template/client/README.md +80 -63
- package/template/client/__tests__/app.test.tsx +188 -99
- package/template/client/__tests__/harness.ts +67 -12
- package/template/client/__tests__/kernel.test.ts +65 -50
- package/template/client/__tests__/seam.test.tsx +111 -0
- package/template/client/index.html +1 -1
- package/template/client/package.json +1 -0
- package/template/client/src/app.tsx +40 -83
- package/template/client/src/main.tsx +2 -2
- package/template/client/src/monitor/components/MonitorCard.tsx +50 -0
- package/template/client/src/monitor/components/index.ts +1 -0
- package/template/client/src/monitor/hooks/index.ts +3 -0
- package/template/client/src/monitor/hooks/useCheck.mutation.ts +16 -0
- package/template/client/src/monitor/hooks/useMonitor.query.ts +64 -0
- package/template/client/src/monitor/index.ts +6 -0
- package/template/client/src/monitor/monitor.api.ts +11 -0
- package/template/client/src/monitor/monitor.mappers.ts +38 -0
- package/template/client/src/monitor/monitor.types.ts +23 -0
- package/template/client/src/monitor/ui/MonitorDetails.UI.tsx +38 -0
- package/template/client/src/monitor/ui/StatusBadge.UI.tsx +14 -0
- package/template/client/src/monitor/ui/index.ts +8 -0
- package/template/client/src/shell/client.ts +67 -0
- package/template/client/src/shell/index.ts +20 -0
- package/template/client/src/shell/invoke.ts +35 -0
- package/template/client/src/shell/transformers.ts +72 -0
- package/template/client/src/shell/use-async.ts +56 -0
- package/template/client/src/shell/use-capability.ts +61 -0
- package/template/client/src/shell/use-node.ts +61 -0
- package/template/client/src/shell/use-shell.ts +91 -0
- package/template/client/src/shell/view-router.tsx +98 -0
- package/template/client/src/styles.css +177 -4
- package/template/client/src/ui/format.ts +24 -0
- package/template/client/src/ui/index.ts +9 -0
- package/template/client/src/ui/surface.tsx +56 -0
- package/template/client/src/ui/value.tsx +32 -0
- package/template/client/src/views/monitor.tsx +30 -0
- package/template/client/tsconfig.json +3 -2
- package/template/client/vite.config.ts +14 -15
- package/template/client/vitest.config.ts +12 -5
- package/template/core/monitor/health.ts +19 -0
- package/template/core/monitor/index.ts +9 -0
- package/template/core/monitor/keys.ts +29 -0
- package/template/core/monitor/node.ts +51 -0
- package/template/deps.ts +25 -0
- package/template/domain.ts +33 -0
- package/template/env.ts +4 -0
- package/template/integrations/prober/http.ts +43 -0
- package/template/integrations/prober/mock.ts +22 -0
- package/template/integrations/prober/port.ts +28 -0
- package/template/integrations/prober/registry.ts +66 -0
- package/template/package.json +2 -3
- package/template/pnpm-lock.yaml +2766 -0
- package/template/runtime/index.ts +79 -0
- package/template/runtime/monitor/check.ts +29 -0
- package/template/runtime/monitor/dependsOn.ts +16 -0
- package/template/runtime/monitor/index.ts +12 -0
- package/template/runtime/monitor/seed.ts +74 -0
- package/template/runtime/monitor/shared.ts +17 -0
- package/template/runtime/monitor/watch.ts +37 -0
- package/template/schema/index.ts +13 -4
- package/template/schema/monitor.ts +80 -0
- package/template/tsconfig.json +13 -2
- package/template/views/index.ts +9 -2
- package/template/views/monitor.ts +22 -0
- package/dist/astrale.d.ts +0 -27
- package/dist/astrale.d.ts.map +0 -1
- package/dist/astrale.js +0 -222
- package/dist/astrale.js.map +0 -1
- package/src/astrale.ts +0 -259
- package/template/client/src/lib/kernel.ts +0 -135
- package/template/client/src/lib/shell.ts +0 -197
- package/template/client/src/lib/use-node.ts +0 -66
- package/template/client/src/lib/use-shell.ts +0 -85
- package/template/methods/index.ts +0 -66
- package/template/methods/note.ts +0 -131
- package/template/schema/compiled.ts +0 -14
- package/template/schema/note.ts +0 -64
- package/template/views/note.ts +0 -21
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
|
|
@@ -90,7 +91,7 @@ Examples:
|
|
|
90
91
|
|
|
91
92
|
```bash
|
|
92
93
|
astrale call /:blog.acme.com:class.Author:list
|
|
93
|
-
astrale call /:blog.acme.com:interface.
|
|
94
|
+
astrale call /:blog.acme.com:interface.MonitorOps:watch url=https://astrale.ai
|
|
94
95
|
astrale call /blog.acme.com/alice::deactivate
|
|
95
96
|
astrale call @f00d...::deactivate
|
|
96
97
|
```
|
|
@@ -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),
|
|
@@ -36,14 +36,21 @@ pnpm prod # deploy + (astrale adapter) auto-install on your instance
|
|
|
36
36
|
Project anatomy (the scaffold is the reference — read its comments):
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
-
|
|
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
|
|
40
44
|
schema/ # classes/interfaces/edges — the contract (zod props, fn signatures)
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
# + index.ts exports D = compileDomain(schema) (resolved paths/keys)
|
|
46
|
+
core/<context>/ # pure, transport-agnostic logic per bounded context (e.g. core/monitor: keys/health/node)
|
|
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
|
|
43
49
|
functions/ # standalone remote functions (webhook-shaped endpoints)
|
|
44
50
|
views/ # iframe-mountable UI declarations (defineView)
|
|
45
51
|
client/ # the SPA served under /ui (vite)
|
|
46
|
-
|
|
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
|
|
47
54
|
```
|
|
48
55
|
|
|
49
56
|
Iteration loop: edit → `pnpm prod` → call it. Managed redeploys keep the same
|
|
@@ -97,9 +104,10 @@ Conventions and rules:
|
|
|
97
104
|
|
|
98
105
|
## 2 · Handlers
|
|
99
106
|
|
|
100
|
-
Separate LOGIC from WIRING. Logic = plain async functions taking the
|
|
101
|
-
client + ports + params (testable with fakes). Wiring = `method()` /
|
|
102
|
-
`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):
|
|
103
111
|
|
|
104
112
|
```ts
|
|
105
113
|
const kickoff = method(schema, 'Project', 'kickoff', {
|
|
@@ -113,9 +121,11 @@ const kickoff = method(schema, 'Project', 'kickoff', {
|
|
|
113
121
|
|
|
114
122
|
Handler context: `kernel` (callback client bound to the composed credential —
|
|
115
123
|
caller's delegated authority ∪ this function's own), `self` (instance methods),
|
|
116
|
-
`params` (zod-validated), `deps` (your typed `
|
|
117
|
-
|
|
118
|
-
|
|
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.prober()` — the registry builds + caches them per
|
|
127
|
+
isolate) — NEVER construct external clients at module load (workers must be
|
|
128
|
+
import-side-effect-free).
|
|
119
129
|
|
|
120
130
|
## 3 · Talking to the kernel (and other domains)
|
|
121
131
|
|
|
@@ -155,16 +165,20 @@ Almost every real domain wraps an external API (payment, calendar, LLM
|
|
|
155
165
|
gateway, cloud provider). The shape (see admin/domain for the full-scale
|
|
156
166
|
example — Scaleway/WorkOS/KV):
|
|
157
167
|
|
|
158
|
-
1. **Port** — a narrow interface
|
|
159
|
-
(`WeatherClient { forecast(city) }
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
+
`Prober { probe(url) }`). `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.prober()`) per
|
|
181
|
+
request and passes it to the `core/` logic.
|
|
168
182
|
|
|
169
183
|
Secrets & config:
|
|
170
184
|
- Declare every var as a typed field on `Env` in `env.ts`. Secrets ship via
|
|
@@ -202,8 +216,10 @@ Anti-patterns (all observed in production code — don't):
|
|
|
202
216
|
## 5 · Views & standalone functions (webhooks)
|
|
203
217
|
|
|
204
218
|
- `defineView({ auth, mount: '/ui/contact', viewFor: selfOf(Contact) })` in
|
|
205
|
-
`views
|
|
206
|
-
|
|
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
|
|
207
223
|
node whose binding URL = `<serving url><mount>` (managed:
|
|
208
224
|
`https://<slug>.svc.<region>.astrale.ai/ui/contact`). The client/ SPA
|
|
209
225
|
serves `/ui/*` — BOTH adapters ship it (cloudflare via Workers Assets;
|
|
@@ -276,12 +292,13 @@ capture when the sender's shape is fixed (see distribution's proxy functions).
|
|
|
276
292
|
|
|
277
293
|
## 7 · Deploy & install
|
|
278
294
|
|
|
279
|
-
Adapter choice in `astrale.config.ts
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
`wrangler`
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
285
302
|
the bundle THROUGH the platform and installs it as a host-local service next
|
|
286
303
|
to your instance (`https://<name>-<hash>.svc.<region>.astrale.ai`). No CF
|
|
287
304
|
account; auth = your `astrale auth login` session. Ships the client SPA
|
|
@@ -303,7 +320,7 @@ service URL stable).
|
|
|
303
320
|
(`/:origin:class.X:seed`; tree paths are rejected) and MUST be idempotent
|
|
304
321
|
(catch path-conflicts). Seed folders, defaults, and demo data here.
|
|
305
322
|
|
|
306
|
-
Manual install of any served domain: `astrale
|
|
323
|
+
Manual install of any served domain: `astrale domain install <url> --direct`.
|
|
307
324
|
|
|
308
325
|
The managed catalog surface (what `pnpm prod` shells into) is callable
|
|
309
326
|
directly — useful for recovery and inspection:
|
package/template/.env.example
CHANGED
|
@@ -4,3 +4,9 @@
|
|
|
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
|
+
# ── Prober (the example external-API integration) ──────────────────────────
|
|
9
|
+
# The scaffold probes monitor targets with a real, KEYLESS HTTP request by
|
|
10
|
+
# default (no secret needed). These knobs are optional config, not secrets:
|
|
11
|
+
# PROBER=http # `http` (default) | `mock` (offline/deterministic)
|
|
12
|
+
# PROBE_TIMEOUT_MS=10000
|
package/template/README.md
CHANGED
|
@@ -19,20 +19,35 @@ calls), use the `astrale-cli` skill.
|
|
|
19
19
|
```bash
|
|
20
20
|
pnpm install
|
|
21
21
|
pnpm dev # wrangler dev → prints a local URL
|
|
22
|
-
astrale
|
|
22
|
+
astrale domain install <url> --direct # mount it on an instance (CLI)
|
|
23
23
|
pnpm prod # deploy prod → prints a URL
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
## What you write
|
|
26
|
+
## What you write
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
schema/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
schema/ classes + interfaces (the data model) + compiled accessors
|
|
30
|
+
core/<context>/ pure, transport-agnostic logic per bounded context (e.g. core/monitor)
|
|
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)
|
|
34
39
|
```
|
|
35
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
|
+
|
|
36
51
|
Everything else — the Worker entry, the wrangler config, the signing identity —
|
|
37
52
|
is generated under `.astrale/` (gitignored) by the adapter. You never edit it.
|
|
38
53
|
|
|
@@ -72,9 +87,9 @@ adapter-owned keys `name`, `main`, `assets`, `routes` are rejected (use
|
|
|
72
87
|
|
|
73
88
|
## The loop
|
|
74
89
|
|
|
75
|
-
- **Edit a handler** (`
|
|
76
|
-
- **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale
|
|
77
|
-
- **`postInstall`** (the static `
|
|
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`.
|
|
92
|
+
- **`postInstall`** (the static `Monitor.seed` method) runs once after install, as
|
|
78
93
|
the system identity, so the domain can seed itself and set its own grants. It
|
|
79
94
|
must be a class-hosted static addressed by a typed colon-path
|
|
80
95
|
(`/:origin:class.X:seed`) — the kernel refuses tree paths here.
|
|
@@ -1,43 +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 #
|
|
10
|
+
* astrale domain install <url> --direct # mount the dev URL on an instance
|
|
11
|
+
* pnpm prod # deploy to your Cloudflare account
|
|
11
12
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
//
|
|
15
|
-
// the
|
|
16
|
-
//
|
|
17
|
-
// R2, D1, queues, …) ride a `wrangler` block on any env (deep-merged):
|
|
13
|
+
import { cloudflare } from '@astrale-os/adapter-cloudflare'
|
|
14
|
+
import { deploy } from '@astrale-os/sdk'
|
|
15
|
+
// No Cloudflare account? Swap the adapter for the Astrale-managed one — it
|
|
16
|
+
// publishes the same bundle THROUGH the platform and installs it on your
|
|
17
|
+
// instance (auth = your `astrale auth login` session):
|
|
18
18
|
//
|
|
19
|
-
// import {
|
|
20
|
-
//
|
|
21
|
-
// dev: { secrets: '.env.dev' },
|
|
22
|
-
// prod: {
|
|
23
|
-
// })
|
|
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
|
+
// }))
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { domain } from './domain'
|
|
26
26
|
|
|
27
|
-
export default
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// deliberately alias that identity (you'll get a DANGER prompt at install).
|
|
31
|
-
postInstall: `/:${schema.domain}:class.Note:seed`,
|
|
32
|
-
adapter: astrale({
|
|
27
|
+
export default deploy(
|
|
28
|
+
domain,
|
|
29
|
+
cloudflare({
|
|
33
30
|
// Local dev: `wrangler dev`. No route → URL is http://localhost:8787.
|
|
34
31
|
dev: { secrets: '.env.dev' },
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
prod: { instance: 'my-instance-slug' },
|
|
40
|
-
// Author secrets ship via `secrets: '.env.prod'` on any env (encrypted at
|
|
41
|
-
// rest platform-side, re-applied on redeploys; omit = keep, `{}` = clear).
|
|
32
|
+
// Custom-domain prod. Drop `route` to ship to *.workers.dev instead.
|
|
33
|
+
prod: { route: 'astrale-domain.example.dev', secrets: '.env.prod' },
|
|
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).
|
|
42
36
|
}),
|
|
43
|
-
|
|
37
|
+
)
|
|
@@ -1,46 +1,72 @@
|
|
|
1
1
|
# astrale-domain-client — SPA for domain views
|
|
2
2
|
|
|
3
|
-
A small React + Vite SPA that renders the domain's
|
|
4
|
-
|
|
5
|
-
its target node id and a
|
|
6
|
-
and renders
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
A small React + Vite SPA that renders the domain's Views. It is loaded inside an
|
|
4
|
+
iframe mounted by the Astrale shell, runs the shell handshake (via the real
|
|
5
|
+
`@astrale-os/shell`) to learn its target node id and a kernel session, fetches
|
|
6
|
+
that node from the kernel, and renders it. The bundled `ui-monitor` view loads a
|
|
7
|
+
Monitor and renders its status/url/latency, with a "Check now" button that calls
|
|
8
|
+
the Monitor's `check` instance method and reloads the node. Built into `../.dist/`
|
|
9
|
+
and served by the generated worker (`.astrale/`) under `/ui/*` via its `ASSETS`
|
|
10
|
+
binding.
|
|
11
|
+
|
|
12
|
+
## Built on the real shell SDK
|
|
13
|
+
|
|
14
|
+
This client consumes `@astrale-os/shell` for the child handshake AND the kernel
|
|
15
|
+
client: `src/shell/use-shell.ts` boots `createShell({ mode: 'sandboxed' })`, and
|
|
16
|
+
feature hooks call kernel methods through `shell.kernel` (token refresh, codec
|
|
17
|
+
negotiation, redirect following, and delegation are all handled by the SDK — no
|
|
18
|
+
inline wire code). `@` is aliased to `src/` (mirrors `tsconfig` paths), so feature
|
|
19
|
+
code imports `@/shell`, `@/ui`, `@/monitor`.
|
|
19
20
|
|
|
20
21
|
## Layout
|
|
21
22
|
|
|
23
|
+
Feature-first: each feature owns its types/api/mappers/hooks/components. The
|
|
24
|
+
`shell/` and `ui/` folders are the generic, domain-neutral seams every feature
|
|
25
|
+
builds on.
|
|
26
|
+
|
|
22
27
|
```
|
|
23
28
|
src/
|
|
24
29
|
main.tsx # entry → createRoot(App)
|
|
25
|
-
app.tsx #
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
use-
|
|
31
|
-
|
|
30
|
+
app.tsx # path router: ROUTES { '/ui/monitor': MonitorView }
|
|
31
|
+
styles.css # self-contained styles (no Tailwind), design tokens
|
|
32
|
+
shell/ # GENERIC kernel/shell adapter on @astrale-os/shell
|
|
33
|
+
client.ts # prop readers (PROP, readProp, readPropBySuffix) + errors
|
|
34
|
+
invoke.ts # callMethod / invokeNode (@<id>::<method>) / nodeAddr
|
|
35
|
+
use-shell.ts # useShell() → createShell({ mode: 'sandboxed' })
|
|
36
|
+
use-node.ts # useNode(session, id) → @<id>::get (+ reload)
|
|
37
|
+
use-async.ts # generic reloadable async resource
|
|
38
|
+
use-capability.ts # write lifecycle (idle → running → done/failed)
|
|
39
|
+
transformers.ts # qualified-prop / link / node-array shaping
|
|
40
|
+
view-router.tsx # resolveView(routes) + ViewFrame (handshake gate)
|
|
41
|
+
index.ts # barrel → @/shell
|
|
42
|
+
ui/ # PURE presentation — no kernel, no hooks
|
|
43
|
+
surface.tsx # Panel / ErrorBanner / EmptyState / Spinner
|
|
44
|
+
badge.tsx # StatusBadge (up | down | unknown)
|
|
45
|
+
value.tsx # KV / Mono / ExternalLink
|
|
46
|
+
format.ts # relativeTime
|
|
47
|
+
index.ts # barrel → @/ui
|
|
48
|
+
monitor/ # THE Monitor feature (template example)
|
|
49
|
+
monitor.types.ts # MonitorRecord
|
|
50
|
+
monitor.api.ts # check(session, id) → @<id>::check
|
|
51
|
+
monitor.mappers.ts # monitorFromNode (node → record)
|
|
52
|
+
hooks/ # useMonitor.query / useCheck.mutation
|
|
53
|
+
components/ # MonitorCard (container: hooks + @/ui)
|
|
54
|
+
index.ts # barrel → @/monitor
|
|
55
|
+
views/
|
|
56
|
+
monitor.tsx # MonitorView = <ViewFrame>…<MonitorCard/></ViewFrame>
|
|
32
57
|
__tests__/
|
|
33
|
-
harness.ts # fake shell parent + fake kernel (fetch stub)
|
|
34
|
-
app.test.tsx # handshake → render → setTarget →
|
|
35
|
-
|
|
58
|
+
harness.ts # fake shell parent (real protocol) + fake kernel (fetch stub)
|
|
59
|
+
app.test.tsx # handshake → render → check now → setTarget → fallback
|
|
60
|
+
seam.test.tsx # pure @/ui + @/monitor hooks in isolation (no handshake)
|
|
61
|
+
kernel.test.ts # pure helpers: prop readers, mapper, relativeTime
|
|
36
62
|
```
|
|
37
63
|
|
|
38
64
|
## Dev loops
|
|
39
65
|
|
|
40
66
|
```bash
|
|
41
|
-
pnpm dev # vite build --watch →
|
|
67
|
+
pnpm dev # vite build --watch → ../.dist/ (worker auto-reloads, no HMR)
|
|
42
68
|
pnpm dev:hmr # vite dev on http://127.0.0.1:5173/ (React fast-refresh)
|
|
43
|
-
pnpm test # vitest run (happy-dom; fake parent
|
|
69
|
+
pnpm test # vitest run (happy-dom; fake parent speaks the real shell protocol)
|
|
44
70
|
```
|
|
45
71
|
|
|
46
72
|
For HMR, set `VIEW_DEV_URL=http://127.0.0.1:5173` in the worker's `.dev.vars`;
|
|
@@ -48,38 +74,29 @@ the generated worker forwards `/ui/*` to vite.
|
|
|
48
74
|
|
|
49
75
|
## How it connects
|
|
50
76
|
|
|
51
|
-
1. The domain declares
|
|
52
|
-
|
|
53
|
-
2. The shell mounts that iframe and completes the handshake
|
|
54
|
-
`MessagePort`
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
3. `useNode(session, nodeId)`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- no redirect following — a `{ redirect }` envelope (remote-domain Functions)
|
|
78
|
-
throws rather than re-minting against the target worker,
|
|
79
|
-
- no client-side credential minting / `whoami` (the handshake token is used
|
|
80
|
-
as-is), and
|
|
81
|
-
- no writes — only `@<id>::get`.
|
|
82
|
-
|
|
83
|
-
These all live in `@astrale-os/kernel-client` / `@astrale-os/shell`. To grow
|
|
84
|
-
past the template, pull those in (and `link:` them in dev) instead of extending
|
|
85
|
-
the inline subset.
|
|
77
|
+
1. The domain declares each View in `../views/` with `mount: '/ui/<path>'`. The
|
|
78
|
+
SDK points the View node's iframe at `<serving url>/ui/<path>`.
|
|
79
|
+
2. The shell mounts that iframe and completes the handshake (a transferred
|
|
80
|
+
`MessagePort` + a `ctrl:handshake` carrying `kernelUrl`, the delegation token,
|
|
81
|
+
and the `targetNodeId`). `useShell()` boots `@astrale-os/shell`, surfaces the
|
|
82
|
+
kernel session, and tracks `setTarget` hot-swaps.
|
|
83
|
+
3. A feature loads its node through the session: `useNode(session, nodeId)` calls
|
|
84
|
+
`@<id>::get` over `shell.kernel`, and a mapper (`monitorFromNode`) projects it
|
|
85
|
+
to a typed record. Domain props are read by key SUFFIX (`.property.url`, …)
|
|
86
|
+
because the domain origin is unknown at build time; the name uses the kernel
|
|
87
|
+
`Named.name` key.
|
|
88
|
+
4. Writes go through `useCapability`: "Check now" runs `@<id>::check`, then calls
|
|
89
|
+
`useMonitor`'s `reload()` so the fresh status/latency re-render. The shell's
|
|
90
|
+
kernel client owns the credential and refreshes it before expiry — features
|
|
91
|
+
never touch the token.
|
|
92
|
+
|
|
93
|
+
## Adding a view
|
|
94
|
+
|
|
95
|
+
1. Write a `ViewComponent` (usually wrapping `ViewFrame`) under `src/views/`.
|
|
96
|
+
2. Add a `ROUTES` entry keyed by its mount path in `src/app.tsx`.
|
|
97
|
+
3. Register a matching `defineView({ mount: '/ui/<path>' })` in the domain's
|
|
98
|
+
`views/` so the shell mounts an iframe there.
|
|
99
|
+
|
|
100
|
+
For a NEW feature, mirror `src/monitor/`: a `*.types.ts`, `*.api.ts`,
|
|
101
|
+
`*.mappers.ts`, a `hooks/` folder of `.query`/`.mutation` hooks, and a
|
|
102
|
+
`components/` folder of containers that compose `@/ui`.
|