@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/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker.
3
+ * Cloudflare Worker on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
6
  *
@@ -11,8 +11,11 @@
11
11
  *
12
12
  * `dev` runs `wrangler dev` locally; an env with no `route` ships to
13
13
  * `*.workers.dev`; an env with a `route` ships to that custom domain.
14
+ *
15
+ * Prefer a managed deploy (no Cloudflare account)? Use
16
+ * `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
17
+ * `/build` toolkit and ships it through the Astrale platform.
14
18
  */
15
- export { astrale } from './astrale';
16
19
  export { cloudflare } from './cloudflare';
17
- export type { AstraleParams, CloudflareParams } from './params';
20
+ export type { CloudflareParams } from './params';
18
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker.
3
+ * Cloudflare Worker on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
6
  *
@@ -11,7 +11,10 @@
11
11
  *
12
12
  * `dev` runs `wrangler dev` locally; an env with no `route` ships to
13
13
  * `*.workers.dev`; an env with a `route` ships to that custom domain.
14
+ *
15
+ * Prefer a managed deploy (no Cloudflare account)? Use
16
+ * `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
17
+ * `/build` toolkit and ships it through the Astrale platform.
14
18
  */
15
- export { astrale } from './astrale';
16
19
  export { cloudflare } from './cloudflare';
17
20
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
package/dist/params.d.ts CHANGED
@@ -55,37 +55,37 @@ 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
- /** Params for the Astrale-managed adapter (`astrale(envs)`). */
60
- export interface AstraleParams {
61
58
  /**
62
- * Target instance slug the domain installs onto (e.g. `'bryan-e2e5'`).
63
- * Required for `deploy` (managed publish + install); ignored by `watch`
64
- * (local dev)so dev-only envs may omit it.
65
- */
66
- instance?: string;
67
- /** Catalog package name. Default: the origin's first DNS label. */
68
- name?: string;
69
- /** Admin kernel URL the publish calls target. Default the platform admin. */
70
- adminUrl?: string;
71
- /** Routing region of the `.svc` service URL. Default `'eu'`. */
72
- region?: string;
73
- /** CLI identity (`--as`) for the publish calls. Default: the CLI's active identity. */
74
- identity?: string;
75
- /** 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). */
76
- secrets?: string;
77
- /** Local dev port for `wrangler dev`. Default 8787. */
78
- port?: number;
79
- /**
80
- * Public URL the dev server is reached through (tunnel / sandbox preview /
81
- * reverse proxy), e.g. `'https://my-box.preview.dev'`. Sets BOTH effects an
82
- * off-localhost dev needs: wrangler binds 0.0.0.0 (reachable from outside)
83
- * and `WORKER_URL` is pinned to this URL so the worker's per-request-Host
84
- * identity (`iss`) doesn't drift to the proxy's internal hostname.
85
- * 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
86
79
  */
87
- host?: string;
88
- /** Extra plain vars for local `watch`. */
89
- vars?: Record<string, string>;
80
+ router?: false | {
81
+ /** Router Worker the binding targets. Default `'admin-router'`. */
82
+ service?: string;
83
+ /** Service-binding name the worker reads. Default `'ROUTER'`. */
84
+ binding?: string;
85
+ /** Host suffix that marks an instance host. Default `'.astrale.ai'`. */
86
+ hostSuffix?: string;
87
+ /** Min dot-separated label count for an instance host. Default `4` (`<slug>.<region>.astrale.ai`). */
88
+ minLabels?: number;
89
+ };
90
90
  }
91
91
  //# sourceMappingURL=params.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wNAAwN;IACxN,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9B"}
1
+ {"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,MAAM,CAAC,EACH,KAAK,GACL;QACE,mEAAmE;QACnE,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,iEAAiE;QACjE,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,wEAAwE;QACxE,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,sGAAsG;QACtG,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACN"}
@@ -10,7 +10,7 @@
10
10
  * The fallback (when no explicit "Ready on" line) only accepts a localhost URL
11
11
  * on the EXPECTED port — wrangler may print unrelated localhost URLs (e.g. an
12
12
  * inspector/devtools endpoint on a different port) before readiness, and taking
13
- * one of those would point `astrale instance install <url>` at a dead endpoint.
13
+ * one of those would point `astrale domain install <url>` at a dead endpoint.
14
14
  */
15
15
  export declare function parseDevReadyUrl(text: string, port: number): string | undefined;
16
16
  /**
@@ -12,7 +12,7 @@ const WORKERS_DEV_RE = /(https:\/\/[^\s]+\.workers\.dev)/i;
12
12
  * The fallback (when no explicit "Ready on" line) only accepts a localhost URL
13
13
  * on the EXPECTED port — wrangler may print unrelated localhost URLs (e.g. an
14
14
  * inspector/devtools endpoint on a different port) before readiness, and taking
15
- * one of those would point `astrale instance install <url>` at a dead endpoint.
15
+ * one of those would point `astrale domain install <url>` at a dead endpoint.
16
16
  */
17
17
  export function parseDevReadyUrl(text, port) {
18
18
  const ready = READY_RE.exec(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/adapter-cloudflare",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Deploy an Astrale domain as a standalone Cloudflare Worker",
5
5
  "keywords": [
6
6
  "adapter",
@@ -24,11 +24,15 @@
24
24
  "types": "./dist/index.d.ts",
25
25
  "import": "./dist/index.js"
26
26
  },
27
+ "./build": {
28
+ "types": "./dist/build.d.ts",
29
+ "import": "./dist/build.js"
30
+ },
27
31
  "./package.json": "./package.json"
28
32
  },
29
33
  "dependencies": {
30
34
  "jose": "^6.1.3",
31
- "@astrale-os/devkit": "^0.1.6"
35
+ "@astrale-os/sdk": "^0.1.7"
32
36
  },
33
37
  "devDependencies": {
34
38
  "@astrale-os/ox": ">=0.1.0 <1.0.0",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pack a built client SPA (`dist-client/`) into the host box's asset-archive
2
+ * Pack a built client SPA (the `CLIENT_DIST_DIR` build) into the host box's asset-archive
3
3
  * format: `gzip(JSON [{ path, contentBase64 }])` — the exact shape
4
4
  * `downloadAssets` on the box consumes (kernel/host workerd service layout).
5
5
  */
package/src/build.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@astrale-os/adapter-cloudflare/build` — the shared Cloudflare-Workers build
3
+ * toolkit, exposed for sibling deploy adapters (e.g.
4
+ * `@astrale-os/adapter-astrale`).
5
+ *
6
+ * This is the "compile a `defineDomain` into a Cloudflare Workers bundle + run
7
+ * local dev" surface that every Cloudflare-Workers deploy strategy shares,
8
+ * regardless of WHERE the bundle ultimately ships. It is NOT part of the public
9
+ * adapter API (`@astrale-os/adapter-cloudflare`) — it's the seam two adapters
10
+ * build on so the codegen/bundling/dev logic lives in exactly one place.
11
+ */
12
+
13
+ export { logTo, prepare } from './cloudflare'
14
+ export { buildClient, CLIENT_DIST_DIR } from './client'
15
+ export { runWranglerBundle, runWranglerDev } from './wrangler-cli'
package/src/client.ts CHANGED
@@ -8,6 +8,16 @@ import { spawn } from 'node:child_process'
8
8
  import { existsSync } from 'node:fs'
9
9
  import { join } from 'node:path'
10
10
 
11
+ /**
12
+ * Where the adapter expects the built client SPA to land, relative to the
13
+ * project root. This is a contract: the domain's Vite config MUST emit here
14
+ * (`build.outDir`), and the wrangler `assets.directory` binding + the managed
15
+ * deploy's asset reader both look here. Dot-prefixed so the build output sorts
16
+ * out of the way with the other generated dirs (`.astrale`) instead of
17
+ * polluting the middle of the project listing.
18
+ */
19
+ export const CLIENT_DIST_DIR = '.dist'
20
+
11
21
  export async function buildClient(
12
22
  clientDir: string,
13
23
  projectDir: string,
@@ -19,7 +29,7 @@ export async function buildClient(
19
29
  ].find((p) => existsSync(p))
20
30
  if (!viteBin) {
21
31
  // The project has a client/ but its deps aren't installed — wrangler would
22
- // otherwise fail cryptically on the missing dist-client assets dir. Fail
32
+ // otherwise fail cryptically on the missing built-assets dir. Fail
23
33
  // loud with the fix (the client is a workspace package — one install covers it).
24
34
  throw new Error(
25
35
  `client build: vite not found in ${clientDir}. Run \`pnpm install\` at the project root ` +
package/src/cloudflare.ts CHANGED
@@ -5,13 +5,13 @@
5
5
  * config (the dev never sees a `wrangler.jsonc`), builds the optional client
6
6
  * SPA, and shells out to `wrangler`. `watch` → `wrangler dev` (local URL);
7
7
  * `deploy` → `wrangler deploy` (workers.dev or a custom-domain URL); secrets →
8
- * `wrangler secret bulk`. Both `watch` and `deploy` return the URL the devkit
9
- * prints and `astrale instance install <url>` consumes.
8
+ * `wrangler secret bulk`. Both `watch` and `deploy` return the URL the CLI
9
+ * prints and `astrale domain install <url>` consumes.
10
10
  */
11
11
 
12
- import type { DeployCtx, DomainAdapter, WatchCtx } from '@astrale-os/devkit'
12
+ import type { DeployCtx, DomainAdapter, WatchCtx } from '@astrale-os/sdk'
13
13
 
14
- import { defineAdapter, hasStarModule } from '@astrale-os/devkit'
14
+ import { defineAdapter } from '@astrale-os/sdk'
15
15
  import { mkdir, rename, writeFile } from 'node:fs/promises'
16
16
  import { join } from 'node:path'
17
17
 
@@ -83,7 +83,7 @@ export function cloudflare(
83
83
  })
84
84
  }
85
85
  // A first deploy on a fresh `*.workers.dev` host can take ~30-60s to
86
- // propagate; an `astrale instance install <url>` issued right away would
86
+ // propagate; an `astrale domain install <url>` issued right away would
87
87
  // 404 (Cloudflare 1042). Block until the URL actually serves so the
88
88
  // install line we print is immediately actionable.
89
89
  await waitUntilLive(url, logTo())
@@ -108,23 +108,35 @@ export async function prepare(
108
108
  await ensureIdentity(astraleDir, ctx.domain.origin)
109
109
 
110
110
  const name = workerName(params, ctx.domain.origin)
111
- // Shared ★-files probe: must agree with the codegen's `import { views } from
112
- // '../views'` resolution (folder index OR sibling file) a folder-only
113
- // existsSync would deploy a worker whose graph silently lacks a sibling-file
114
- // module the diagnostic spec includes.
115
- const hasViews = hasStarModule(ctx.projectDir, 'views')
116
- const hasFunctions = hasStarModule(ctx.projectDir, 'functions')
117
- const hasClient = Boolean(ctx.clientDir)
111
+ // Instance-router service binding (on by default admin-router): lets the
112
+ // worker reach other instances' hosts internally, so a cross-instance JWKS
113
+ // fetch doesn't 522. Resolved once, fed to BOTH codegens below.
114
+ const router = resolveRouter(params.router)
115
+ // Two distinct client signals: the WORKER's `/ui` hook is present whenever the
116
+ // domain declares a client (`ctx.domain.hasClient`) — including managed
117
+ // deploys, which ship the built assets separately and pass no `clientDir`; the
118
+ // wrangler `assets.directory` binding is wired only when there's a local dir to
119
+ // point at (`ctx.clientDir` — direct Cloudflare deploys + local dev).
120
+ const servesClient = ctx.domain.hasClient
121
+ const bundleAssets = Boolean(ctx.clientDir)
118
122
 
119
123
  await writeFileAtomic(
120
124
  join(astraleDir, 'worker.gen.ts'),
125
+ // Everything else — schema/methods/deps/views/functions/requires/postInstall —
126
+ // rides the `...domain` spread the generated entry imports from `../domain`;
127
+ // the only remaining build-time signal is whether to wire the /ui asset hook.
121
128
  generateWorkerEntry({
122
129
  origin: ctx.domain.origin,
123
- ...(ctx.domain.postInstall ? { postInstall: ctx.domain.postInstall } : {}),
124
- requires: ctx.domain.requires,
125
- hasViews,
126
- hasFunctions,
127
- hasClient,
130
+ hasClient: servesClient,
131
+ ...(router
132
+ ? {
133
+ router: {
134
+ binding: router.binding,
135
+ hostSuffix: router.hostSuffix,
136
+ minLabels: router.minLabels,
137
+ },
138
+ }
139
+ : {}),
128
140
  }),
129
141
  )
130
142
 
@@ -154,8 +166,12 @@ export async function prepare(
154
166
  const baseConfig = {
155
167
  workerName: name,
156
168
  ...(mode === 'deploy' && params.route ? { route: params.route } : {}),
157
- hasClient,
169
+ hasClient: bundleAssets,
158
170
  vars,
171
+ // Router service binding flows into BOTH the SELF and SELF-less fallback
172
+ // configs — the first-deploy two-pass must keep it. `admin-router` already
173
+ // exists, so unlike SELF it always resolves (no first-deploy chicken-egg).
174
+ ...(router ? { router: { binding: router.binding, service: router.service } } : {}),
159
175
  // Escape hatch: extra bindings (KV/R2/D1/queues/…) deep-merged on top. Same
160
176
  // overlay in dev and deploy — it's infra, not env-specific plumbing — and it
161
177
  // flows into both the SELF and the SELF-less fallback config below.
@@ -254,6 +270,25 @@ export function workerName(params: CloudflareParams, origin: string): string {
254
270
  .replace(/^-+|-+$/g, '')
255
271
  }
256
272
 
273
+ /**
274
+ * Resolve the instance-router config. ON by default (targets `admin-router`) so
275
+ * every adapter-deployed worker can reach other instances' hosts internally and
276
+ * verify cross-instance credentials without a 522; `false` disables it. The
277
+ * returned shape carries every value the worker- and wrangler-codegen need.
278
+ */
279
+ export function resolveRouter(
280
+ ir: CloudflareParams['router'],
281
+ ): { binding: string; service: string; hostSuffix: string; minLabels: number } | undefined {
282
+ if (ir === false) return undefined
283
+ const cfg = ir ?? {}
284
+ return {
285
+ binding: cfg.binding ?? 'ROUTER',
286
+ service: cfg.service ?? 'admin-router',
287
+ hostSuffix: cfg.hostSuffix ?? '.astrale.ai',
288
+ minLabels: cfg.minLabels ?? 4,
289
+ }
290
+ }
291
+
257
292
  export function logTo(): (line: string) => void {
258
293
  return (line: string) => process.stderr.write(`\x1b[2m ${line}\x1b[0m\n`)
259
294
  }
@@ -1,8 +1,20 @@
1
1
  /**
2
2
  * Codegen for the Cloudflare Worker entry (`.astrale/worker.gen.ts`).
3
3
  *
4
- * The dev writes only schema/methods/views/functions; this generates the
5
- * `export default { fetch }` plumbing the adapter owns. Key properties:
4
+ * The dev wires the domain ONCE in a worker-safe `domain.ts`
5
+ * (`export const domain = defineDomain({ schema, methods, deps, views,
6
+ * functions, client })`) and binds it to an adapter in `astrale.config.ts`
7
+ * (`deploy(domain, cloudflare({ … }))`). This generates the
8
+ * `export default { fetch }` plumbing the adapter owns by importing that single
9
+ * `domain` and SPREADING it into `domainWorkerEntry` — so the author's folder
10
+ * layout is irrelevant (any internal organization works as long as `domain.ts`
11
+ * re-exports the wired definition) and the node-only deploy adapter, which lives
12
+ * in `astrale.config.ts`, never enters this worker bundle.
13
+ *
14
+ * Everything the entry needs — schema/methods/deps/views/functions/requires/
15
+ * postInstall — rides on the spread; the only build-time signal left is whether
16
+ * the domain serves a client SPA (`hasClient`), which gates the `/ui` asset hook
17
+ * and its import. Key properties:
6
18
  *
7
19
  * • Per-request `url` = the live serving origin (`scheme://host`). It is passed
8
20
  * only to `createRemoteServer({ url })`; the SDK stamps every
@@ -22,49 +34,83 @@
22
34
 
23
35
  export interface WorkerCodegenOptions {
24
36
  origin: string
25
- postInstall?: string
26
- requires: readonly string[]
27
- hasViews: boolean
28
- hasFunctions: boolean
29
37
  hasClient: boolean
38
+ /**
39
+ * When set, the worker routes subrequests to platform INSTANCE hostnames
40
+ * through `<binding>` (a service binding to a router Worker) instead of the
41
+ * public edge — a same-zone Worker→Worker public fetch 522s, which otherwise
42
+ * blocks fetching an instance kernel's JWKS to verify a cross-instance
43
+ * credential.
44
+ */
45
+ router?: { binding: string; hostSuffix: string; minLabels: number }
30
46
  }
31
47
 
32
48
  export function generateWorkerEntry(opts: WorkerCodegenOptions): string {
33
- const optionalImports = [
34
- opts.hasViews ? `import { views } from '../views'` : `const views = undefined`,
35
- opts.hasFunctions ? `import { functions } from '../functions'` : `const functions = undefined`,
36
- ].join('\n')
49
+ // The asset-serving hook is a library helper (`assets`), not inlined string
50
+ // logic imported only when the domain declares a `client` binding.
51
+ const serverImport = opts.hasClient
52
+ ? `import { assets, domainWorkerEntry } from '@astrale-os/sdk/server'`
53
+ : `import { domainWorkerEntry } from '@astrale-os/sdk/server'`
54
+
55
+ const clientHook = opts.hasClient
56
+ ? `
57
+ // Client assets under /ui/* — served from the ASSETS binding (or proxied to
58
+ // Vite in dev via VIEW_DEV_URL). The matching/stripping logic lives in the SDK helper.
59
+ before: assets({ binding: (env) => env.ASSETS, devProxy: (env) => env.VIEW_DEV_URL }),`
60
+ : ''
37
61
 
38
- const postInstallLine =
39
- opts.postInstall !== undefined
40
- ? `const POST_INSTALL = ${JSON.stringify(opts.postInstall)}`
41
- : `const POST_INSTALL = undefined`
62
+ const router = opts.router
63
+ // Service binding for instance-host subrequest routing (Env field + helper +
64
+ // routeSubrequest), wired only when the adapter's `router` is set.
65
+ const routerEnvField = router
66
+ ? `
67
+ ${router.binding}?: { fetch(request: Request): Promise<Response> }`
68
+ : ''
69
+ const routerHelper = router
70
+ ? `
71
+ // A platform INSTANCE hostname (e.g. <slug>.<region>${router.hostSuffix}) is served
72
+ // by a router Worker on the SAME zone — a direct Worker→Worker public fetch 522s.
73
+ // Route those subrequests (e.g. fetching an instance kernel's JWKS to verify a
74
+ // cross-instance credential) through the ${router.binding} service binding instead.
75
+ function isInstanceHost(host) {
76
+ return host.endsWith('${router.hostSuffix}') && host.split('.').length >= ${router.minLabels}
77
+ }
78
+ `
79
+ : ''
80
+ const routerHook = router
81
+ ? `
82
+ // Divert instance-host subrequests through ${router.binding} (CF 522s a same-zone
83
+ // Worker→Worker public fetch). No-op when the binding is absent.
84
+ routeSubrequest: (url, env) => (env.${router.binding} && isInstanceHost(url.hostname) ? env.${router.binding} : null),`
85
+ : ''
42
86
 
43
87
  return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
44
- // Regenerated on every \`astrale-domain dev|deploy\`. Edit schema/methods/views
45
- // instead. (origin: ${opts.origin})
46
- import { defineRemoteDomain } from '@astrale-os/sdk'
47
- import { createWorkerEntry } from '@astrale-os/sdk/server'
88
+ // Regenerated on every \`astrale-domain dev|deploy\`. Edit your domain.ts (the
89
+ // wired \`defineDomain\`) and astrale.config.ts (the \`deploy(domain, …)\`).
90
+ // (origin: ${opts.origin})
91
+ ${serverImport}
48
92
 
49
- import { schema } from '../schema'
50
- import { methods } from '../methods'
51
- ${optionalImports}
93
+ // The worker-safe domain definition schema/methods/deps/views/functions plus
94
+ // origin/requires/postInstall wired by the author in domain.ts. Spreading it
95
+ // keeps this entry layout-agnostic; the deploy adapter stays in astrale.config.ts.
96
+ import { domain } from '../domain'
52
97
  import { PRIVATE_JWK } from './identity'
53
98
 
54
- const REQUIRES = ${JSON.stringify(opts.requires)}
55
- ${postInstallLine}
56
-
57
99
  interface Env {
58
100
  WORKER_URL?: string
59
101
  ASSETS?: { fetch(request: Request): Promise<Response> }
60
102
  SELF?: { fetch(request: Request): Promise<Response> }
61
- VIEW_DEV_URL?: string
103
+ VIEW_DEV_URL?: string${routerEnvField}
62
104
  [key: string]: unknown
63
105
  }
64
-
65
- // All the shared worker plumbing JWKS self-fetch shim, SELF-binding routing,
66
- // per-URL app cache, canonical iss resolution lives in createWorkerEntry.
67
- export default createWorkerEntry<Env>({
106
+ ${routerHelper}
107
+ // \`domainWorkerEntry\` folds the runtime-domain compile + server build + the
108
+ // shared worker plumbing (JWKS self-resolution, SELF-binding routing, per-URL
109
+ // app cache, canonical iss resolution) into one declaration. \`deps\` (when the
110
+ // domain declares one) rides the spread and runs per cold isolate / serving URL.
111
+ export default domainWorkerEntry<Env>()({
112
+ ...domain,
113
+ privateKey: PRIVATE_JWK,
68
114
  // The worker's serving URL = its identity (iss). Prefer the adapter-injected
69
115
  // WORKER_URL (pins one canonical host for routed deploys, so iss stays stable
70
116
  // even when also reachable via *.workers.dev); fall back to the per-request
@@ -73,36 +119,7 @@ export default createWorkerEntry<Env>({
73
119
  // upgrades the fallback origin via X-Forwarded-Proto, so iss = the public
74
120
  // https URL, not the raw http one workerd sees.
75
121
  resolveUrl: (env, requestOrigin) => env.WORKER_URL ?? requestOrigin,
76
- selfBinding: (env) => env.SELF,
77
- build: (url, env) => {
78
- const domain = defineRemoteDomain()({
79
- schema,
80
- methods,
81
- ...(views ? { views } : {}),
82
- ...(functions ? { remoteFunctions: functions } : {}),
83
- })
84
- return {
85
- domain,
86
- deps: env,
87
- url,
88
- privateKey: PRIVATE_JWK,
89
- ...(POST_INSTALL ? { postInstall: POST_INSTALL } : {}),
90
- ...(REQUIRES.length ? { requires: REQUIRES } : {}),
91
- }
92
- },
93
- // SPA under /ui/* (views with a client renderer).
94
- before: (env, url, request) => {
95
- if (env.ASSETS && (url.pathname === '/ui' || url.pathname.startsWith('/ui/'))) {
96
- if (env.VIEW_DEV_URL) {
97
- const devBase = env.VIEW_DEV_URL.replace(/\\/$/, '')
98
- return fetch(new Request(\`\${devBase}\${url.pathname}\${url.search}\`, request))
99
- }
100
- const stripped = url.pathname.replace(/^\\/ui\\/?/, '/')
101
- const rewritten = new URL(stripped + url.search, url.origin)
102
- return env.ASSETS.fetch(new Request(rewritten, request))
103
- }
104
- return undefined
105
- },
122
+ selfBinding: (env) => env.SELF,${routerHook}${clientHook}
106
123
  })
107
124
  `
108
125
  }
@@ -3,8 +3,8 @@
3
3
  * `wrangler.jsonc` that used to be hand-copied into every domain — now an
4
4
  * internal adapter detail the dev never sees.
5
5
  *
6
- * `main` is `./worker.gen.ts` (same dir). `assets.directory` is `../dist-client`
7
- * (the project's Vite build). A `SELF` service binding enables autobinding
6
+ * `main` is `./worker.gen.ts` (same dir). `assets.directory` points at the
7
+ * project's Vite build (`CLIENT_DIST_DIR`). A `SELF` service binding enables autobinding
8
8
  * (a handler calling back into its own domain). Custom-domain deploys attach a
9
9
  * `custom_domain` route; otherwise the Worker ships to `*.workers.dev`.
10
10
  *
@@ -12,6 +12,7 @@
12
12
  * (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
13
13
  */
14
14
 
15
+ import { CLIENT_DIST_DIR } from '../client'
15
16
  import { deepMergeConfig } from './merge'
16
17
 
17
18
  export interface WranglerCodegenOptions {
@@ -24,6 +25,12 @@ export interface WranglerCodegenOptions {
24
25
  vars?: Record<string, string>
25
26
  /** Add the SELF service binding (autobinding). */
26
27
  selfBinding: boolean
28
+ /**
29
+ * Add a service binding to a router Worker for instance-host subrequest
30
+ * routing (see `CloudflareParams.router`). Pairs with the worker
31
+ * entry's `routeSubrequest`.
32
+ */
33
+ router?: { binding: string; service: string }
27
34
  /**
28
35
  * Raw wrangler config to deep-merge over the generated base — the escape
29
36
  * hatch for extra bindings (KV, R2, D1, queues, service bindings, extra
@@ -52,16 +59,19 @@ export function generateWranglerConfig(opts: WranglerCodegenOptions): string {
52
59
 
53
60
  if (opts.hasClient) {
54
61
  config.assets = {
55
- directory: '../dist-client',
62
+ directory: `../${CLIENT_DIST_DIR}`,
56
63
  binding: 'ASSETS',
57
64
  not_found_handling: 'single-page-application',
58
65
  run_worker_first: true,
59
66
  }
60
67
  }
61
68
 
62
- if (opts.selfBinding) {
63
- config.services = [{ binding: 'SELF', service: opts.workerName }]
69
+ const services: Array<{ binding: string; service: string }> = []
70
+ if (opts.selfBinding) services.push({ binding: 'SELF', service: opts.workerName })
71
+ if (opts.router) {
72
+ services.push({ binding: opts.router.binding, service: opts.router.service })
64
73
  }
74
+ if (services.length > 0) config.services = services
65
75
 
66
76
  if (opts.vars && Object.keys(opts.vars).length > 0) {
67
77
  config.vars = opts.vars
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker.
3
+ * Cloudflare Worker on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
6
  *
@@ -11,8 +11,11 @@
11
11
  *
12
12
  * `dev` runs `wrangler dev` locally; an env with no `route` ships to
13
13
  * `*.workers.dev`; an env with a `route` ships to that custom domain.
14
+ *
15
+ * Prefer a managed deploy (no Cloudflare account)? Use
16
+ * `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
17
+ * `/build` toolkit and ships it through the Astrale platform.
14
18
  */
15
19
 
16
- export { astrale } from './astrale'
17
20
  export { cloudflare } from './cloudflare'
18
- export type { AstraleParams, CloudflareParams } from './params'
21
+ export type { CloudflareParams } from './params'