@astrale-os/adapter-cloudflare 0.1.8 → 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.
Files changed (71) hide show
  1. package/dist/assets-pack.d.ts +1 -1
  2. package/dist/assets-pack.js +1 -1
  3. package/dist/build.d.ts +15 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +15 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/client.d.ts +9 -0
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +10 -1
  10. package/dist/client.js.map +1 -1
  11. package/dist/cloudflare.d.ts +15 -3
  12. package/dist/cloudflare.d.ts.map +1 -1
  13. package/dist/cloudflare.js +52 -18
  14. package/dist/cloudflare.js.map +1 -1
  15. package/dist/codegen/worker.d.ts +26 -6
  16. package/dist/codegen/worker.d.ts.map +1 -1
  17. package/dist/codegen/worker.js +67 -54
  18. package/dist/codegen/worker.js.map +1 -1
  19. package/dist/codegen/wrangler.d.ts +11 -2
  20. package/dist/codegen/wrangler.d.ts.map +1 -1
  21. package/dist/codegen/wrangler.js +11 -5
  22. package/dist/codegen/wrangler.js.map +1 -1
  23. package/dist/index.d.ts +6 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/params.d.ts +30 -30
  28. package/dist/params.d.ts.map +1 -1
  29. package/dist/parse-output.d.ts +1 -1
  30. package/dist/parse-output.js +1 -1
  31. package/package.json +6 -2
  32. package/src/assets-pack.ts +1 -1
  33. package/src/build.ts +15 -0
  34. package/src/client.ts +11 -1
  35. package/src/cloudflare.ts +53 -18
  36. package/src/codegen/worker.ts +76 -59
  37. package/src/codegen/wrangler.ts +15 -5
  38. package/src/index.ts +6 -3
  39. package/src/params.ts +32 -31
  40. package/src/parse-output.ts +1 -1
  41. package/template/.agents/skills/astrale-cli/SKILL.md +25 -11
  42. package/template/.agents/skills/astrale-domain/SKILL.md +46 -29
  43. package/template/.env.example +8 -0
  44. package/template/README.md +24 -9
  45. package/template/astrale.config.ts +27 -33
  46. package/template/client/README.md +2 -2
  47. package/template/client/tsconfig.json +1 -1
  48. package/template/client/vite.config.ts +3 -3
  49. package/template/client/vitest.config.ts +1 -1
  50. package/template/core/keys.ts +28 -0
  51. package/template/{methods → core}/note.ts +42 -25
  52. package/template/deps.ts +25 -0
  53. package/template/domain.ts +33 -0
  54. package/template/env.ts +11 -0
  55. package/template/integrations/summary/heuristic.ts +25 -0
  56. package/template/integrations/summary/http.ts +69 -0
  57. package/template/integrations/summary/port.ts +21 -0
  58. package/template/integrations/summary/registry.ts +52 -0
  59. package/template/package.json +2 -3
  60. package/template/runtime/index.ts +62 -0
  61. package/template/schema/index.ts +2 -0
  62. package/template/schema/note.ts +5 -2
  63. package/template/tsconfig.json +13 -2
  64. package/template/views/note.ts +1 -1
  65. package/dist/astrale.d.ts +0 -27
  66. package/dist/astrale.d.ts.map +0 -1
  67. package/dist/astrale.js +0 -222
  68. package/dist/astrale.js.map +0 -1
  69. package/src/astrale.ts +0 -259
  70. package/template/methods/index.ts +0 -66
  71. package/template/schema/compiled.ts +0 -14
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
- * Target instance slug the domain installs onto (e.g. `'bryan-e2e5'`).
64
- * Required for `deploy` (managed publish + install); ignored by `watch`
65
- * (local dev)so dev-only envs may omit it.
66
- */
67
- instance?: string
68
- /** Catalog package name. Default: the origin's first DNS label. */
69
- name?: string
70
- /** Admin kernel URL the publish calls target. Default the platform admin. */
71
- adminUrl?: string
72
- /** Routing region of the `.svc` service URL. Default `'eu'`. */
73
- region?: string
74
- /** CLI identity (`--as`) for the publish calls. Default: the CLI's active identity. */
75
- identity?: string
76
- /** Gitignored secrets file. `watch`: injected into local wrangler dev. `deploy`: shipped on the install call and applied to the managed service env (encrypted at rest platform-side; platform keys win precedence). */
77
- secrets?: string
78
- /** Local dev port for `wrangler dev`. Default 8787. */
79
- port?: number
80
- /**
81
- * Public URL the dev server is reached through (tunnel / sandbox preview /
82
- * reverse proxy), e.g. `'https://my-box.preview.dev'`. Sets BOTH effects an
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
- host?: string
89
- /** Extra plain vars for local `watch`. */
90
- vars?: Record<string, string>
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
  }
@@ -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 instance install <url>` at a dead endpoint.
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.** There is no `astrale domain …` command group. Domain development
168
- lives in two separate tools:
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/devkit` bin, behind the project's
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: run or deploy the domain
176
- worker, then `astrale instance install <url> -i <slug>` — the kernel fetches a
177
- signed install bundle from the running worker. There is no committed
178
- `spec.json` and no `astrale domain install`; install lives under the
179
- `instance` group because it operates on an instance graph.
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 `instance install`. The service serves at
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
- astrale.config.ts # THE manifest: schema + postInstall + adapter (where it ships)
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
- methods/ # handler logic (transport-agnostic fns) + thin wiring (index.ts)
42
- services/ # RECOMMENDED: ports + factories for external APIs (see §4)
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
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
- env.ts # typed worker env config + secrets arrive here, as ctx.deps
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 kernel
101
- client + ports + params (testable with fakes). Wiring = `method()` /
102
- `classMethods()` / `interfaceMethods()` in `methods/index.ts`:
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 `Env`), `auth` (principal,
117
- verified claims). Construct external clients from `deps` per request NEVER
118
- at module load (workers must be import-side-effect-free).
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).
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 declaring only what the logic needs
159
- (`WeatherClient { forecast(city) }`). Logic depends on the port, never on
160
- fetch/SDKs/env.
161
- 2. **Factory** — `createXClient(env)` in `services/`: reads config + secrets
162
- from `env`, validates LOUDLY (`if (!env.X_API_KEY) throw`), sets base URLs
163
- (overridable so tests point at a stub), timeouts (`AbortSignal.timeout`),
164
- and maps upstream failures to meaningful errors with the upstream detail in
165
- `cause` (the kernel threads cause chains to callers).
166
- 3. **Wiring** — the `execute` hook builds the client from `deps` per request
167
- and passes it to the logic.
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.
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/` + register in `views/index.ts` — the MAP KEY is the view's node
206
- slug (`'ui-contact'` `/<origin>/core/views/ui-contact`). Installs a View
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
- - `cloudflare({...})` your CF account; `dev` (wrangler dev), `prod` (route or
281
- workers.dev); ships secrets + SPA assets; extra bindings via a deep-merged
282
- `wrangler` block.
283
- - `astrale({ dev: {...}, prod: { instance: '<slug>' } })` — managed (the
284
- scaffold's DEFAULT): publishes
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 instance install <url>`.
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:
@@ -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
@@ -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 instance install <url> # mount it on an instance (CLI)
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/classes + interfaces (the data model)
30
- methods/ the execute() handlers
31
- views/ ★ iframe-mountable UIs
32
- functions/ standalone Functions (e.g. the post-install seed)
33
- astrale.config.ts identity (origin) · requires · postInstall · adapter
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)
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,8 +87,8 @@ adapter-owned keys `name`, `main`, `assets`, `routes` are rejected (use
72
87
 
73
88
  ## The loop
74
89
 
75
- - **Edit a handler** (`methods/`) → hot-reloads at the same URL. Nothing to reinstall.
76
- - **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale instance install <url>`.
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`.
77
92
  - **`postInstall`** (the static `Note.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
@@ -1,43 +1,37 @@
1
1
  /**
2
- * astrale.config.ts — the single source of truth for this domain's identity and
3
- * deployment. `origin` defaults to `schema.domain`; `postInstall` points at the
4
- * static `Note.seed` method the kernel runs after install (a typed colon-path —
5
- * the kernel refuses tree paths here, since they can't prove their origin);
6
- * `adapter` chooses the target.
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 instance install <url> # mount the dev URL on an instance
10
- * pnpm prod # managed deploy prints a URL + installs
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 { astrale } from '@astrale-os/adapter-cloudflare'
13
- import { defineDomain } from '@astrale-os/devkit'
14
- // Have your own Cloudflare account and prefer deploying there directly? Swap
15
- // the adapter — same bundle, shipped with wrangler under YOUR account (then
16
- // mount it yourself: `astrale instance install <url>`). Extra bindings (KV,
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 { cloudflare } from '@astrale-os/adapter-cloudflare'
20
- // adapter: cloudflare({
21
- // dev: { secrets: '.env.dev' }, // local wrangler dev, unchanged
22
- // prod: { route: 'astrale-domain.example.dev', secrets: '.env.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 { schema } from './schema'
25
+ import { domain } from './domain'
26
26
 
27
- export default defineDomain({
28
- schema,
29
- // origin defaults to `schema.domain`. Set it to another domain's origin to
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
- // Managed deploy: `pnpm prod` publishes the bundle through the platform and
36
- // installs the domain on this instance — no Cloudflare account needed
37
- // (auth = your `astrale auth login` session). The slug comes from
38
- // `astrale instance create <slug>` / `astrale instance status`.
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
+ )
@@ -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 `../dist-client/` and served by the
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 → ../dist-client/ (worker auto-reloads, no HMR)
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
  ```
@@ -21,5 +21,5 @@
21
21
  "vite.config.ts",
22
22
  "vitest.config.ts"
23
23
  ],
24
- "exclude": ["node_modules", "../dist-client"]
24
+ "exclude": ["node_modules", "../.dist"]
25
25
  }
@@ -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 `../dist-client/`,
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: '../dist-client'`: Vite emits asset refs as
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: '../dist-client',
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 + `../dist-client` build) so tests don't inherit the SPA build
8
+ * `/ui/` base + `../.dist` build) so tests don't inherit the SPA build
9
9
  * options.
10
10
  */
11
11
  export default defineConfig({
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Compiled-schema accessors — the ONE place class paths, method paths, and
3
+ * qualified prop keys come from. Pure (schema-derived); never hand-write key
4
+ * strings. `D` (the compiled domain) is the public `schema/compiled` entry,
5
+ * re-exported here so `core/` and `runtime/` read it — plus the named keys —
6
+ * from one place.
7
+ */
8
+ import { K } from '@astrale-os/kernel-core'
9
+
10
+ import { D } from '../schema/compiled'
11
+
12
+ export { D, K }
13
+
14
+ /** Kernel ops/classes the logic addresses. */
15
+ export const NODE_CREATE = K.Node.createNode.path.method.raw
16
+ export const FOLDER_CLASS = K.Folder.path.class.raw
17
+ export const NAME_KEY = K.Named.name.key
18
+
19
+ /** Domain class paths. */
20
+ export const NOTE_CLASS = D.Note.path.class.raw
21
+ export const REFERENCES_EDGE = D.references.path.class.raw
22
+
23
+ /** Qualified storage keys for Note node props. */
24
+ export const NOTE_KEYS = {
25
+ title: D.Note.title.key,
26
+ body: D.Note.body.key,
27
+ summary: D.Note.summary.key,
28
+ } as const
@@ -1,26 +1,27 @@
1
1
  /**
2
- * Note context — method LOGIC, written once, transport-agnostic.
2
+ * Note context — method LOGIC, written once, transport-agnostic and testable.
3
3
  *
4
4
  * Each handler touches the kernel ONLY through `kernel.call(...)` (the universal
5
- * syscalls) plus its `params` and, for instance methods, the source node's
6
- * `path.raw`. Address callables/edges with layout-independent forms — a
7
- * `ClassPath` for the edge class, the `<id>::link` instance form, and an
8
- * `@<id>` id-form target. Reserve an absolute path string for a node *location*.
5
+ * syscalls) plus its `params`, the resolved `Summarizer` PORT, and for
6
+ * instance methods — the source node's `path.raw`. It never names `fetch`, the
7
+ * worker `env`, or a concrete provider: the port arrives ready-to-use from
8
+ * `runtime/` (which built it from `deps`). Address callables/edges with
9
+ * layout-independent forms — a `ClassPath` for the edge class, the `<id>::link`
10
+ * instance form, and an `@<id>` id-form target.
9
11
  */
10
- import { K } from '@astrale-os/kernel-core'
11
-
12
- import { D } from '../schema/compiled'
12
+ import type { Summarizer } from '../integrations/summary/port'
13
+ import {
14
+ FOLDER_CLASS,
15
+ NAME_KEY,
16
+ NODE_CREATE,
17
+ NOTE_CLASS,
18
+ NOTE_KEYS,
19
+ REFERENCES_EDGE,
20
+ } from './keys'
13
21
 
14
22
  /** The minimal kernel surface the worker runtime exposes to a handler. */
15
23
  export type CallableKernel = { call(path: string, params: unknown): Promise<unknown> }
16
24
 
17
- const NODE_CREATE = K.Node.createNode.path.method.raw
18
- const NAME_KEY = K.Named.name.key
19
- const NOTE_CLASS = D.Note.path.class.raw
20
- const REFERENCES_EDGE = D.references.path.class.raw
21
- const TITLE_KEY = D.Note.title.key
22
- const BODY_KEY = D.Note.body.key
23
-
24
25
  /** Notes live under a `/notes` folder at the graph root (created by `seed`). */
25
26
  const NOTES_PARENT = '/notes'
26
27
 
@@ -37,16 +38,26 @@ function slugify(text: string): string {
37
38
  return `${stem}-${Date.now().toString(36).slice(-4)}${Math.random().toString(36).slice(2, 6)}`
38
39
  }
39
40
 
40
- /** `NoteOps.createNote` (static) — create a Note under `/notes`. */
41
+ /**
42
+ * `NoteOps.createNote` (static) — create a Note under `/notes`, stamping a
43
+ * one-line `summary` from the resolved summarizer port (the external-API seam).
44
+ */
41
45
  export async function createNote(
42
46
  kernel: CallableKernel,
47
+ summarizer: Summarizer,
43
48
  params: { title: string; body: string },
44
49
  ): Promise<{ id: string; path: string }> {
50
+ const summary = await summarizer.summarize(params.body)
45
51
  const path = `${NOTES_PARENT}/${slugify(params.title)}`
46
52
  const created = (await kernel.call(NODE_CREATE, {
47
53
  class: NOTE_CLASS,
48
54
  path,
49
- props: { [NAME_KEY]: params.title, [TITLE_KEY]: params.title, [BODY_KEY]: params.body },
55
+ props: {
56
+ [NAME_KEY]: params.title,
57
+ [NOTE_KEYS.title]: params.title,
58
+ [NOTE_KEYS.body]: params.body,
59
+ [NOTE_KEYS.summary]: summary,
60
+ },
50
61
  })) as { id: string }
51
62
  return { id: created.id, path }
52
63
  }
@@ -64,8 +75,6 @@ export async function reference(
64
75
  return { linked: params.target }
65
76
  }
66
77
 
67
- const FOLDER_CLASS = K.Folder.path.class.raw
68
-
69
78
  const STARTERS: ReadonlyArray<{ slug: string; title: string; body: string }> = [
70
79
  {
71
80
  slug: 'welcome',
@@ -81,12 +90,15 @@ function isPathConflict(e: unknown): boolean {
81
90
 
82
91
  /**
83
92
  * `Note.seed` (static) — the domain's post-install bootstrap. The kernel calls
84
- * it ONCE after install, as __SYSTEM__ (see `postInstall` in
85
- * `astrale.config.ts`), so the domain can lay down its initial state and
86
- * grants: the `/notes` folder, a couple of starter Notes, and a `references`
87
- * edge between them. Idempotent: a re-run swallows `PATH_CONFLICT` per node.
93
+ * it ONCE after install, as __SYSTEM__ (see `postInstall` in `domain.ts`), so
94
+ * the domain can lay down its initial state: the `/notes` folder, a couple of
95
+ * starter Notes (each summarized through the port), and a `references` edge
96
+ * between them. Idempotent: a re-run swallows `PATH_CONFLICT` per node.
88
97
  */
89
- export async function seed(kernel: CallableKernel): Promise<{ seeded: number }> {
98
+ export async function seed(
99
+ kernel: CallableKernel,
100
+ summarizer: Summarizer,
101
+ ): Promise<{ seeded: number }> {
90
102
  // 1. The `/notes` folder at the graph root.
91
103
  try {
92
104
  await kernel.call(NODE_CREATE, {
@@ -106,7 +118,12 @@ export async function seed(kernel: CallableKernel): Promise<{ seeded: number }>
106
118
  const created = (await kernel.call(NODE_CREATE, {
107
119
  class: NOTE_CLASS,
108
120
  path: `${NOTES_PARENT}/${s.slug}`,
109
- props: { [NAME_KEY]: s.title, [TITLE_KEY]: s.title, [BODY_KEY]: s.body },
121
+ props: {
122
+ [NAME_KEY]: s.title,
123
+ [NOTE_KEYS.title]: s.title,
124
+ [NOTE_KEYS.body]: s.body,
125
+ [NOTE_KEYS.summary]: await summarizer.summarize(s.body),
126
+ },
110
127
  })) as { id: string }
111
128
  ids[s.slug] = created.id
112
129
  seeded++