@astrale-os/adapter-cloudflare 0.4.4 → 0.4.6

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 (46) hide show
  1. package/dist/codegen/wrangler.js +1 -1
  2. package/package.json +4 -3
  3. package/src/codegen/wrangler.ts +1 -1
  4. package/template/CLAUDE.md +8 -21
  5. package/template/README.md +30 -107
  6. package/template/client/__tests__/app.test.tsx +6 -57
  7. package/template/client/components.json +25 -0
  8. package/template/client/index.html +1 -1
  9. package/template/client/package.json +10 -2
  10. package/template/client/src/app.tsx +42 -33
  11. package/template/client/src/components/ui/cn.ts +6 -0
  12. package/template/client/src/styles.css +71 -502
  13. package/template/client/vite.config.ts +2 -18
  14. package/template/client/vitest.config.ts +0 -6
  15. package/template/core/README.md +5 -21
  16. package/template/deps.ts +2 -20
  17. package/template/domain.ts +0 -26
  18. package/template/env.ts +1 -17
  19. package/template/icons.ts +4 -25
  20. package/template/integrations/README.md +5 -20
  21. package/template/package.json +7 -5
  22. package/template/pnpm-workspace.yaml +2 -0
  23. package/template/runtime/index.ts +0 -27
  24. package/template/schema/index.ts +0 -20
  25. package/template/simulation/README.md +12 -0
  26. package/template/tsconfig.json +2 -3
  27. package/template/vitest.config.ts +7 -0
  28. package/template/.domain-studio/comments.json +0 -4
  29. package/template/client/README.md +0 -104
  30. package/template/client/__tests__/harness.ts +0 -230
  31. package/template/client/__tests__/shell.test.ts +0 -46
  32. package/template/client/src/shell/client.ts +0 -67
  33. package/template/client/src/shell/index.ts +0 -21
  34. package/template/client/src/shell/invoke.ts +0 -35
  35. package/template/client/src/shell/transformers.ts +0 -75
  36. package/template/client/src/shell/use-async.ts +0 -59
  37. package/template/client/src/shell/use-capability.ts +0 -59
  38. package/template/client/src/shell/use-node.ts +0 -81
  39. package/template/client/src/shell/use-shell.ts +0 -91
  40. package/template/client/src/shell/view-router.tsx +0 -97
  41. package/template/client/src/ui/format.ts +0 -24
  42. package/template/client/src/ui/index.ts +0 -10
  43. package/template/client/src/ui/surface.tsx +0 -56
  44. package/template/client/src/ui/value.tsx +0 -32
  45. package/template/functions/index.ts +0 -22
  46. package/template/views/index.ts +0 -19
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { CLIENT_DIST_DIR } from '../client';
15
15
  import { deepMergeConfig } from './merge';
16
- const COMPATIBILITY_DATE = '2025-03-01';
16
+ const COMPATIBILITY_DATE = '2026-07-12';
17
17
  export function generateWranglerConfig(opts) {
18
18
  const config = {
19
19
  name: opts.workerName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/adapter-cloudflare",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Deploy an Astrale domain as a standalone Cloudflare Worker",
5
5
  "keywords": [
6
6
  "adapter",
@@ -14,7 +14,8 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "src",
17
- "template"
17
+ "template",
18
+ "!template/pnpm-lock.yaml"
18
19
  ],
19
20
  "type": "module",
20
21
  "main": "./src/index.ts",
@@ -32,7 +33,7 @@
32
33
  },
33
34
  "dependencies": {
34
35
  "jose": "^6.1.3",
35
- "@astrale-os/sdk": "^0.4.5"
36
+ "@astrale-os/sdk": "^0.4.10"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@astrale-os/ox": ">=0.1.0 <1.0.0",
@@ -41,7 +41,7 @@ export interface WranglerCodegenOptions {
41
41
  wrangler?: Record<string, unknown>
42
42
  }
43
43
 
44
- const COMPATIBILITY_DATE = '2025-03-01'
44
+ const COMPATIBILITY_DATE = '2026-07-12'
45
45
 
46
46
  export function generateWranglerConfig(opts: WranglerCodegenOptions): string {
47
47
  const config: Record<string, unknown> = {
@@ -1,26 +1,13 @@
1
- # astrale-domain
1
+ # Astrale domain
2
2
 
3
- A standalone Astrale domain a **blank skeleton**: every folder is wired and
4
- ready, with no classes, views, or integrations yet. The authoring guide is not in
5
- this repo — it's the `astrale-domain` agent skill. Load it before changing the
6
- schema or handlers.
7
-
8
- ## Agent skills
9
-
10
- If the `astrale-domain` / `astrale-cli` skills aren't already available in your
11
- harness, install them once (user-level):
3
+ Load the `astrale-domain` skill before changing schema, runtime, views, integrations, security, or
4
+ simulation code. Install it user-level if needed:
12
5
 
13
6
  ```bash
14
- npx skills add astrale-os/cli -g # both skills · or run `astrale setup`
7
+ npx skills add astrale-os/cli -g
15
8
  ```
16
9
 
17
- They install into your agent harness (not this project), so every Astrale
18
- project picks them up. `astrale update` refreshes them alongside the CLI binary
19
- and this project's `@astrale-os/*` dependencies.
20
-
21
- ## Orientation
22
-
23
- See `README.md` for the project layout (`schema/`, `icons.ts`, `core/`,
24
- `integrations/`, `runtime/`, `views/`, `functions/`, `client/`), the dev/deploy
25
- loop, and the signing-identity note. `domain.ts` is the one place the domain
26
- contract is declared.
10
+ This scaffold is intentionally empty. Organize schema, runtime, core, simulation, and client code by
11
+ real bounded contexts. Keep I/O out of `core/`, use one explicit file per method or standalone function,
12
+ wrap nondeterministic handler work in `step`, derive graph keys and paths from the compiled schema, and
13
+ batch graph reads or mutations where possible.
@@ -1,120 +1,43 @@
1
- # astrale-domain
1
+ # Astrale domain
2
2
 
3
- A standalone [Astrale](https://astrale.ai) domain, deployed as an Astrale-managed
4
- service (default) or as a worker on your own Cloudflare account.
5
-
6
- This is a **blank skeleton** — every folder is wired up and ready, but the domain
7
- ships with no classes, interfaces, views, or integrations. Fill in the terrain;
8
- nothing here is a demo you have to delete.
9
-
10
- ## For AI agents
11
-
12
- The complete domain-authoring guide is the `astrale-domain` skill (schema
13
- modeling, handlers, kernel calls, external-API patterns — DI, secrets,
14
- idempotency — views, webhooks, deploys); CLI operations (auth, instances, calls)
15
- are the `astrale-cli` skill. Install both once, user-level:
16
-
17
- ```bash
18
- npx skills add astrale-os/cli -g # both skills · or run `astrale setup`
19
- ```
20
-
21
- Then load `astrale-domain` before making changes. (Skills are installed into your
22
- agent harness, not committed to this project — `astrale update` keeps them, the
23
- CLI, and your `@astrale-os/*` deps current.)
24
-
25
- > **Requires [Bun](https://bun.sh)** — the `astrale-domain` CLI behind `pnpm dev`
26
- > / `pnpm prod` imports `astrale.config.ts` and your ★ files directly, so it
27
- > needs a TypeScript-native runtime.
3
+ A minimal Astrale domain scaffold. It builds, runs, and deploys before you add any domain concepts.
28
4
 
29
5
  ```bash
30
6
  pnpm install
31
- pnpm dev # wrangler dev → prints a local URL
32
- astrale domain install <url> --direct # mount it on an instance (CLI)
33
- pnpm prod # deploy prod → prints a URL
7
+ pnpm dev
8
+ pnpm typecheck
9
+ pnpm test
10
+ pnpm prod
34
11
  ```
35
12
 
36
- A freshly-scaffolded skeleton deploys and installs as-is (an empty domain with no
37
- members) — then you add classes and handlers.
13
+ Load the `astrale-domain` skill before authoring the domain:
38
14
 
39
- ## What you write
40
-
41
- ```
42
- schema/ classes + interfaces + edges (the data model) + compiled accessors
43
- icons.ts class icons (raw SVG), attached via nodeClass({ icon })
44
- core/<context>/ pure, transport-agnostic logic per bounded context
45
- integrations/ external-API ports + adapters + a lazy registry (built into deps)
46
- runtime/ the execute() handlers — the composition root (the methods map)
47
- views/ iframe-mountable UIs (inline HTML or a client/ SPA)
48
- functions/ standalone Functions (e.g. webhooks, a postInstall seed)
49
- client/ the SPA served under /ui
50
- deps.ts env → typed Deps container (what handlers read as ctx.deps)
51
- domain.ts wires it all together — the worker-safe definition
52
- astrale.config.ts binds the domain to its deploy adapter (node-only)
15
+ ```bash
16
+ npx skills add astrale-os/cli -g
53
17
  ```
54
18
 
55
- `schema/`, `runtime/`, `views/`, and `functions/` start with empty registries;
56
- `core/` and `integrations/` are empty folders with a short README each explaining
57
- the pattern. `domain.ts` is the one place the domain contract is declared: it
58
- imports the modules above and passes them to `defineDomain({ schema, methods,
59
- deps, views, functions, })` alongside the identity (`origin`, `requires`,
60
- `postInstall`). Nothing is discovered by folder name — a renamed or mistyped
61
- module is a compile error at that call, never a silently-missing route. Drop a
62
- field (`deps`, `views`, `functions`) when the domain has none. `astrale.config.ts`
63
- then binds that domain to a deploy adapter with `deploy(domain, cloudflare({ … }))`
64
- and chooses, per env, which `client` assets serve mounted `/ui` views — keeping
65
- the node-only adapter (wrangler, filesystem, frontend builds) out of the worker
66
- bundle.
67
-
68
- Everything else — the Worker entry, the wrangler config, the signing identity —
69
- is generated under `.astrale/` (gitignored) by the adapter. You never edit it.
70
-
71
- ## The signing identity (back it up!)
72
-
73
- The first `pnpm dev`/`pnpm prod` generates an Ed25519 keypair at
74
- `.astrale/identity.ts`. That private key **is** this domain's identity: every
75
- instance that installs the domain verifies its credentials against it. The file
76
- is gitignored, so a fresh clone or a CI runner silently mints a **new** key —
77
- and redeploying with a new key breaks verification on every existing install.
78
- Back it up, or move it to a managed secret before deploying from more than one
79
- machine.
80
-
81
- ## Extra bindings (KV, R2, D1, queues, …)
82
-
83
- The adapter owns the wrangler config, but you can declare extra bindings from
84
- `astrale.config.ts` via a `wrangler` block on any env — it's deep-merged into
85
- the generated config:
86
-
87
- ```ts
88
- adapter: cloudflare({
89
- dev: {
90
- secrets: '.env.dev',
91
- wrangler: {
92
- kv_namespaces: [{ binding: 'CACHE', id: '<kv-id>' }],
93
- r2_buckets: [{ binding: 'FILES', bucket_name: 'my-bucket' }],
94
- },
95
- },
96
- })
19
+ ## Shape
20
+
21
+ ```text
22
+ schema/ vocabulary, split by bounded context
23
+ runtime/ method implementations, split by bounded context and one file per method
24
+ core/ pure deterministic logic, paths, defaults, and domain errors
25
+ integrations/ external-system ports and adapters
26
+ simulation/ samples and business scenarios
27
+ client/ React views built on @astrale-os/shell-react
28
+ icons.ts shared SVG wrapper; named icons stay with their context
29
+ env.ts worker bindings and secrets
30
+ deps.ts env-to-runtime dependency composition
31
+ domain.ts worker-safe domain assembly
97
32
  ```
98
33
 
99
- The merge is additive arrays concat, so your `services` keep the adapter's
100
- `SELF` binding and your `compatibility_flags` keep `nodejs_compat`. The
101
- adapter-owned keys `name`, `main`, `assets`, `routes` are rejected (use
102
- `workerName` and `route`/`zone`); don't reuse the reserved binding names
103
- `SELF`/`ASSETS`. Durable Objects aren't supported through this field yet.
104
-
105
- ## The loop
106
-
107
- - **Edit a handler** (`core/` logic or `runtime/` wiring) → hot-reloads at the same URL. Nothing to reinstall.
108
- - **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale domain install <url> --direct`.
109
- - **`postInstall`** — author a standalone `seed` function in `functions/`, reference
110
- it from `domain.ts` (`postInstall: functions.seed`), and the kernel runs it once
111
- after install, as the system identity, so the domain can lay down its initial
112
- graph and set its own grants. Functions are first-class domain members,
113
- addressable at `/:origin:function.seed`.
34
+ Keep `schema/index.ts` and `runtime/index.ts` as composition roots. Put standalone functions in the
35
+ bounded context that owns them, then wire them from `domain.ts`; do not create a generic functions
36
+ junk drawer. The same rule applies to views and client code.
114
37
 
115
- ## Identity
38
+ The client includes Tailwind CSS v4 and an empty shadcn `radix-nova` setup. Add only the components a
39
+ real view needs with `pnpm exec shadcn add <component>`; component source stays in the project and can
40
+ be adapted to the domain's visual language.
116
41
 
117
- `origin` (in `astrale.config.ts`, defaulting to `schema.domain`) is the domain's
118
- stable name the same in every env; only the URL changes. The deployed Worker
119
- computes its own `binding.remoteUrl` at install from its public URL, so there's
120
- no hardcoded URL anywhere in your code.
42
+ The adapter generates worker plumbing under `.astrale/`. Do not edit it. The signing identity in
43
+ `.astrale/identity.ts` is gitignored; back it up before deploying from more than one machine.
@@ -1,66 +1,15 @@
1
- import { cleanup, render, renderHook, screen, waitFor } from '@testing-library/react'
2
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
1
+ import { cleanup, render, screen } from '@testing-library/react'
2
+ import { afterEach, describe, expect, it } from 'vitest'
3
3
 
4
4
  import { App } from '@/app'
5
- import { useShell } from '@/shell'
6
-
7
- import { installFakeParent, type FakeParent } from './harness'
8
-
9
- /**
10
- * The router picks a view from `window.location.pathname`. This skeleton has no
11
- * views registered, so every path falls through to the fallback — that's the
12
- * test below. The second block exercises the shell handshake plumbing (via the
13
- * fake parent) so you know the seam works the moment you add a real view.
14
- *
15
- * `replaceState` rewrites the happy-dom location without a reload.
16
- */
17
- function mountAt(path: string) {
18
- window.history.replaceState({}, '', path)
19
- }
20
-
21
- beforeEach(() => {
22
- mountAt('/ui/anything')
23
- })
24
5
 
25
6
  afterEach(cleanup)
26
7
 
27
- describe('router fallback (no views registered)', () => {
28
- it('renders the self-describing fallback for any path', () => {
8
+ describe('domain client', () => {
9
+ it('renders a standalone preview outside the Astrale shell', () => {
29
10
  render(<App />)
30
- expect(screen.getByText('No view here')).toBeTruthy()
31
- expect(screen.getByText(/No view registered for/)).toBeTruthy()
32
- })
33
- })
34
-
35
- describe('shell handshake plumbing', () => {
36
- let parent: FakeParent | undefined
37
-
38
- afterEach(() => {
39
- parent?.restore()
40
- parent = undefined
41
- })
42
-
43
- it('reaches `ready` with a kernel session after the parent handshake', async () => {
44
- parent = installFakeParent({
45
- windowId: 'w-1',
46
- kernelUrl: 'https://k.example.test/api',
47
- functionId: 'fn-1',
48
- delegationToken: 'tok-A',
49
- tokenExpiresAt: Date.now() + 60_000,
50
- targetNodeId: 'node-1',
51
- })
52
-
53
- const { result } = renderHook(() => useShell())
54
- // waitFor polls inside `act`, so the handshake's async state updates land
55
- // wrapped — the parent's `ack` promise need not be awaited separately.
56
- await waitFor(() => expect(result.current.status).toBe('ready'))
57
- expect(result.current.session).not.toBeNull()
58
- expect(result.current.nodeId).toBe('node-1')
59
- })
60
11
 
61
- it('falls back to a standalone preview with no parent', async () => {
62
- const { result } = renderHook(() => useShell())
63
- await waitFor(() => expect(result.current.status).toBe('standalone'))
64
- expect(result.current.session).toBeNull()
12
+ expect(screen.getByText('Domain client')).toBeTruthy()
13
+ expect(screen.getByRole('heading', { name: 'Ready to build.' })).toBeTruthy()
65
14
  })
66
15
  })
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-nova",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/styles.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/components/ui/cn",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>astrale-domain · view</title>
6
+ <title>Domain view</title>
7
7
  </head>
8
8
  <body>
9
9
  <div id="root"></div>
@@ -11,16 +11,24 @@
11
11
  "test": "vitest run"
12
12
  },
13
13
  "dependencies": {
14
- "@astrale-os/shell": ">=0.3.1 <1.0.0",
14
+ "@astrale-os/shell-react": ">=0.2.5 <1.0.0",
15
+ "@fontsource-variable/instrument-sans": "^5.2.8",
16
+ "@fontsource/ibm-plex-mono": "^5.2.7",
17
+ "clsx": "^2.1.1",
15
18
  "react": "^19.2.0",
16
- "react-dom": "^19.2.0"
19
+ "react-dom": "^19.2.0",
20
+ "shadcn": "^4.13.0",
21
+ "tailwind-merge": "^3.6.0",
22
+ "tw-animate-css": "^1.4.0"
17
23
  },
18
24
  "devDependencies": {
25
+ "@tailwindcss/vite": "^4.3.2",
19
26
  "@testing-library/react": "^16.3.0",
20
27
  "@types/react": "^19.2.0",
21
28
  "@types/react-dom": "^19.2.0",
22
29
  "@vitejs/plugin-react": "^5.0.4",
23
30
  "happy-dom": "^20.0.0",
31
+ "tailwindcss": "^4.3.2",
24
32
  "vite": "^7.1.7",
25
33
  "vitest": "^3.2.4"
26
34
  }
@@ -1,43 +1,52 @@
1
- /**
2
- * Path-based view router for the domain's SPA bundle. The shell mounts each Astrale
3
- * View in its own iframe at its own mount path, so inside the iframe
4
- * `window.location.pathname` IS that path. `App` reads it (via `resolveView`) and
5
- * renders the matching view; an unregistered path shows a self-describing fallback.
6
- *
7
- * `ROUTES` starts EMPTY — this is a blank skeleton, so every path falls through to
8
- * `UnknownView`. Add a view by
9
- * 1. writing a `ViewComponent` (usually wrapping `ViewFrame` — see `@/shell`),
10
- * 2. adding a `ROUTES` entry keyed by its mount path,
11
- * 3. registering a matching `defineView({ mount: '/ui/<path>' })` in the domain's
12
- * `views/`.
13
- */
1
+ import { ShellProvider, useTargetNode, type ShellConfig } from '@astrale-os/shell-react'
14
2
 
15
- import { resolveView, useShell, type ViewRoutes } from '@/shell'
16
-
17
- const ROUTES: ViewRoutes = {}
3
+ const SHELL_CONFIG: ShellConfig = { mode: 'sandboxed', initTimeoutMs: 8_000 }
18
4
 
19
5
  export function App() {
20
- const shell = useShell()
21
- const View = resolveView(ROUTES)
6
+ if (typeof window === 'undefined' || window.parent === window) return <Preview />
7
+ return (
8
+ <ShellProvider config={SHELL_CONFIG} fallback={<Loading />}>
9
+ <DomainView />
10
+ </ShellProvider>
11
+ )
12
+ }
13
+
14
+ function DomainView() {
15
+ const target = useTargetNode()
22
16
 
23
- if (!View) return <UnknownView />
17
+ return (
18
+ <main className="mx-auto flex min-h-screen w-full max-w-5xl flex-col justify-center gap-5 px-6 py-20">
19
+ <p className="font-mono text-xs font-semibold tracking-[0.16em] text-primary uppercase">
20
+ Domain view
21
+ </p>
22
+ <h1 className="max-w-3xl text-5xl leading-[0.95] font-semibold tracking-[-0.055em] md:text-7xl">
23
+ {target.pending ? 'Loading target…' : 'Ready to build.'}
24
+ </h1>
25
+ <p className="max-w-xl text-base text-muted-foreground">
26
+ Replace this component with the view for your first bounded context.
27
+ </p>
28
+ </main>
29
+ )
30
+ }
24
31
 
25
- return <>{View(shell)}</>
32
+ function Preview() {
33
+ return (
34
+ <main className="mx-auto flex min-h-screen w-full max-w-5xl flex-col justify-center gap-5 px-6 py-20">
35
+ <p className="font-mono text-xs font-semibold tracking-[0.16em] text-primary uppercase">
36
+ Domain client
37
+ </p>
38
+ <h1 className="max-w-3xl text-5xl leading-[0.95] font-semibold tracking-[-0.055em] md:text-7xl">
39
+ Ready to build.
40
+ </h1>
41
+ <p className="max-w-xl text-base text-muted-foreground">
42
+ Views run inside the Astrale shell. Start with a bounded context, then add its view.
43
+ </p>
44
+ </main>
45
+ )
26
46
  }
27
47
 
28
- /** Fallback when the iframe's mount path has no registered view. */
29
- function UnknownView() {
48
+ function Loading() {
30
49
  return (
31
- <div className="wrap">
32
- <div className="card">
33
- <h1 className="title">No view here</h1>
34
- <p className="subline">Astrale view SPA</p>
35
- <div className="banner">
36
- No view registered for {window.location.pathname}. Add a ROUTES entry in{' '}
37
- <code>src/app.tsx</code> (and a matching <code>defineView({'{ mount }'})</code> in the
38
- domain's <code>views/</code>).
39
- </div>
40
- </div>
41
- </div>
50
+ <main className="grid min-h-screen place-items-center text-muted-foreground">Connecting…</main>
42
51
  )
43
52
  }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }