@astrale-os/adapter-cloudflare 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) 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 +73 -21
  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 +70 -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 +77 -23
  36. package/src/codegen/worker.ts +79 -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 +60 -32
  43. package/template/.env.example +8 -0
  44. package/template/README.md +26 -10
  45. package/template/astrale.config.ts +22 -29
  46. package/template/client/README.md +2 -2
  47. package/template/client/src/styles.css +4 -1
  48. package/template/client/tsconfig.json +1 -1
  49. package/template/client/vite.config.ts +3 -3
  50. package/template/client/vitest.config.ts +1 -1
  51. package/template/core/keys.ts +28 -0
  52. package/template/{methods → core}/note.ts +42 -25
  53. package/template/deps.ts +25 -0
  54. package/template/domain.ts +33 -0
  55. package/template/env.ts +11 -0
  56. package/template/integrations/summary/heuristic.ts +25 -0
  57. package/template/integrations/summary/http.ts +69 -0
  58. package/template/integrations/summary/port.ts +21 -0
  59. package/template/integrations/summary/registry.ts +52 -0
  60. package/template/package.json +2 -3
  61. package/template/runtime/index.ts +62 -0
  62. package/template/schema/index.ts +2 -0
  63. package/template/schema/note.ts +5 -2
  64. package/template/tsconfig.json +13 -2
  65. package/template/views/note.ts +1 -1
  66. package/dist/astrale.d.ts +0 -27
  67. package/dist/astrale.d.ts.map +0 -1
  68. package/dist/astrale.js +0 -212
  69. package/dist/astrale.js.map +0 -1
  70. package/src/astrale.ts +0 -244
  71. package/template/methods/index.ts +0 -66
  72. package/template/schema/compiled.ts +0 -14
@@ -3,14 +3,15 @@
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
  *
11
11
  * A dev's `params.wrangler` escape hatch is deep-merged on top of this base
12
12
  * (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
13
13
  */
14
+ import { CLIENT_DIST_DIR } from '../client';
14
15
  import { deepMergeConfig } from './merge';
15
16
  const COMPATIBILITY_DATE = '2025-03-01';
16
17
  export function generateWranglerConfig(opts) {
@@ -27,15 +28,20 @@ export function generateWranglerConfig(opts) {
27
28
  }
28
29
  if (opts.hasClient) {
29
30
  config.assets = {
30
- directory: '../dist-client',
31
+ directory: `../${CLIENT_DIST_DIR}`,
31
32
  binding: 'ASSETS',
32
33
  not_found_handling: 'single-page-application',
33
34
  run_worker_first: true,
34
35
  };
35
36
  }
36
- if (opts.selfBinding) {
37
- config.services = [{ binding: 'SELF', service: opts.workerName }];
37
+ const services = [];
38
+ if (opts.selfBinding)
39
+ services.push({ binding: 'SELF', service: opts.workerName });
40
+ if (opts.router) {
41
+ services.push({ binding: opts.router.binding, service: opts.router.service });
38
42
  }
43
+ if (services.length > 0)
44
+ config.services = services;
39
45
  if (opts.vars && Object.keys(opts.vars).length > 0) {
40
46
  config.vars = opts.vars;
41
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wrangler.js","sourceRoot":"","sources":["../../src/codegen/wrangler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAsBzC,MAAM,kBAAkB,GAAG,YAAY,CAAA;AAEvC,MAAM,UAAU,sBAAsB,CAAC,IAA4B;IACjE,MAAM,MAAM,GAA4B;QACtC,IAAI,EAAE,IAAI,CAAC,UAAU;QACrB,IAAI,EAAE,iBAAiB;QACvB,kBAAkB,EAAE,kBAAkB;QACtC,mBAAmB,EAAE,CAAC,eAAe,EAAE,oCAAoC,CAAC;QAC5E,WAAW,EAAE,IAAI;QACjB,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;KACjC,CAAA;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,CAAC,MAAM,GAAG;YACd,SAAS,EAAE,gBAAgB;YAC3B,OAAO,EAAE,QAAQ;YACjB,kBAAkB,EAAE,yBAAyB;YAC7C,gBAAgB,EAAE,IAAI;SACvB,CAAA;IACH,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,CAAC,QAAQ,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACzB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAC9E,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;AAC/C,CAAC;AAED,qEAAqE;AACrE,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE/D;;;;GAIG;AACH,SAAS,eAAe,CACtB,IAA6B,EAC7B,IAA6B;IAE7B,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAA;IAC9D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,qDAAqD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC5E,gEAAgE;YAChE,mDAAmD,CACtD,CAAA;IACH,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AACpC,CAAC"}
1
+ {"version":3,"file":"wrangler.js","sourceRoot":"","sources":["../../src/codegen/wrangler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AA4BzC,MAAM,kBAAkB,GAAG,YAAY,CAAA;AAEvC,MAAM,UAAU,sBAAsB,CAAC,IAA4B;IACjE,MAAM,MAAM,GAA4B;QACtC,IAAI,EAAE,IAAI,CAAC,UAAU;QACrB,IAAI,EAAE,iBAAiB;QACvB,kBAAkB,EAAE,kBAAkB;QACtC,mBAAmB,EAAE,CAAC,eAAe,EAAE,oCAAoC,CAAC;QAC5E,WAAW,EAAE,IAAI;QACjB,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;KACjC,CAAA;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,CAAC,MAAM,GAAG;YACd,SAAS,EAAE,MAAM,eAAe,EAAE;YAClC,OAAO,EAAE,QAAQ;YACjB,kBAAkB,EAAE,yBAAyB;YAC7C,gBAAgB,EAAE,IAAI;SACvB,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAgD,EAAE,CAAA;IAChE,IAAI,IAAI,CAAC,WAAW;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAA;IAClF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/E,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAEnD,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACzB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAC9E,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;AAC/C,CAAC;AAED,qEAAqE;AACrE,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE/D;;;;GAIG;AACH,SAAS,eAAe,CACtB,IAA6B,EAC7B,IAA6B;IAE7B,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAA;IAC9D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,qDAAqD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC5E,gEAAgE;YAChE,mDAAmD,CACtD,CAAA;IACH,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AACpC,CAAC"}
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.7",
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.5"
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,14 +5,14 @@
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'
15
- import { mkdir, writeFile } from 'node:fs/promises'
14
+ import { defineAdapter } from '@astrale-os/sdk'
15
+ import { mkdir, rename, writeFile } from 'node:fs/promises'
16
16
  import { join } from 'node:path'
17
17
 
18
18
  import type { CloudflareParams } from './params'
@@ -50,6 +50,15 @@ export function cloudflare(
50
50
  return { url: params.host ?? handle.url, stop: handle.stop }
51
51
  },
52
52
 
53
+ // Config hot-regen: rewrite the generated files only — the running
54
+ // `wrangler dev` watches its `--config` (and the worker entry) and reloads
55
+ // them itself, so a `astrale.config.ts` edit lands without a restart.
56
+ // Mirrors `watch`'s prep exactly (codegen + client build), minus the spawn.
57
+ async regenerate(params, ctx) {
58
+ await prepare(params, ctx, 'dev')
59
+ if (ctx.clientDir) await buildClient(ctx.clientDir, ctx.projectDir, logTo())
60
+ },
61
+
53
62
  async deploy(params, ctx) {
54
63
  const {
55
64
  configPath,
@@ -74,7 +83,7 @@ export function cloudflare(
74
83
  })
75
84
  }
76
85
  // A first deploy on a fresh `*.workers.dev` host can take ~30-60s to
77
- // propagate; an `astrale instance install <url>` issued right away would
86
+ // propagate; an `astrale domain install <url>` issued right away would
78
87
  // 404 (Cloudflare 1042). Block until the URL actually serves so the
79
88
  // install line we print is immediately actionable.
80
89
  await waitUntilLive(url, logTo())
@@ -99,23 +108,35 @@ export async function prepare(
99
108
  await ensureIdentity(astraleDir, ctx.domain.origin)
100
109
 
101
110
  const name = workerName(params, ctx.domain.origin)
102
- // Shared ★-files probe: must agree with the codegen's `import { views } from
103
- // '../views'` resolution (folder index OR sibling file) a folder-only
104
- // existsSync would deploy a worker whose graph silently lacks a sibling-file
105
- // module the diagnostic spec includes.
106
- const hasViews = hasStarModule(ctx.projectDir, 'views')
107
- const hasFunctions = hasStarModule(ctx.projectDir, 'functions')
108
- const hasClient = Boolean(ctx.clientDir)
109
-
110
- await writeFile(
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)
122
+
123
+ await writeFileAtomic(
111
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.
112
128
  generateWorkerEntry({
113
129
  origin: ctx.domain.origin,
114
- ...(ctx.domain.postInstall ? { postInstall: ctx.domain.postInstall } : {}),
115
- requires: ctx.domain.requires,
116
- hasViews,
117
- hasFunctions,
118
- hasClient,
130
+ hasClient: servesClient,
131
+ ...(router
132
+ ? {
133
+ router: {
134
+ binding: router.binding,
135
+ hostSuffix: router.hostSuffix,
136
+ minLabels: router.minLabels,
137
+ },
138
+ }
139
+ : {}),
119
140
  }),
120
141
  )
121
142
 
@@ -145,8 +166,12 @@ export async function prepare(
145
166
  const baseConfig = {
146
167
  workerName: name,
147
168
  ...(mode === 'deploy' && params.route ? { route: params.route } : {}),
148
- hasClient,
169
+ hasClient: bundleAssets,
149
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 } } : {}),
150
175
  // Escape hatch: extra bindings (KV/R2/D1/queues/…) deep-merged on top. Same
151
176
  // overlay in dev and deploy — it's infra, not env-specific plumbing — and it
152
177
  // flows into both the SELF and the SELF-less fallback config below.
@@ -158,12 +183,12 @@ export async function prepare(
158
183
  // worker can't (the script doesn't exist yet), so we also emit a SELF-less
159
184
  // fallback config that `runWranglerDeploy` uses for a one-time two-pass deploy.
160
185
  const configPath = join(astraleDir, 'wrangler.gen.jsonc')
161
- await writeFile(configPath, generateWranglerConfig({ ...baseConfig, selfBinding: true }))
186
+ await writeFileAtomic(configPath, generateWranglerConfig({ ...baseConfig, selfBinding: true }))
162
187
 
163
188
  let fallbackConfigPath: string | undefined
164
189
  if (mode === 'deploy') {
165
190
  fallbackConfigPath = join(astraleDir, 'wrangler.no-self.gen.jsonc')
166
- await writeFile(
191
+ await writeFileAtomic(
167
192
  fallbackConfigPath,
168
193
  generateWranglerConfig({ ...baseConfig, selfBinding: false }),
169
194
  )
@@ -177,6 +202,16 @@ export async function prepare(
177
202
  }
178
203
  }
179
204
 
205
+ /**
206
+ * Write-then-rename: hot-regen rewrites these files while a live `wrangler dev`
207
+ * watches them — a plain `writeFile` can be read truncated mid-write.
208
+ */
209
+ async function writeFileAtomic(path: string, content: string): Promise<void> {
210
+ const tmp = `${path}.tmp`
211
+ await writeFile(tmp, content)
212
+ await rename(tmp, path)
213
+ }
214
+
180
215
  /**
181
216
  * Poll the deployed URL until the worker answers (anything but the Cloudflare
182
217
  * 1042 "no script on this host" 404), or give up after `timeoutMs`. Resolves
@@ -235,6 +270,25 @@ export function workerName(params: CloudflareParams, origin: string): string {
235
270
  .replace(/^-+|-+$/g, '')
236
271
  }
237
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
+
238
292
  export function logTo(): (line: string) => void {
239
293
  return (line: string) => process.stderr.write(`\x1b[2m ${line}\x1b[0m\n`)
240
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,84 +34,92 @@
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
71
117
  // host for dev / workers.dev-only (single-host → it IS the canonical URL).
118
+ // Behind a TLS-terminating proxy (cloudflared tunnel → wrangler dev) the SDK
119
+ // upgrades the fallback origin via X-Forwarded-Proto, so iss = the public
120
+ // https URL, not the raw http one workerd sees.
72
121
  resolveUrl: (env, requestOrigin) => env.WORKER_URL ?? requestOrigin,
73
- selfBinding: (env) => env.SELF,
74
- build: (url, env) => {
75
- const domain = defineRemoteDomain()({
76
- schema,
77
- methods,
78
- ...(views ? { views } : {}),
79
- ...(functions ? { remoteFunctions: functions } : {}),
80
- })
81
- return {
82
- domain,
83
- deps: env,
84
- url,
85
- privateKey: PRIVATE_JWK,
86
- ...(POST_INSTALL ? { postInstall: POST_INSTALL } : {}),
87
- ...(REQUIRES.length ? { requires: REQUIRES } : {}),
88
- }
89
- },
90
- // SPA under /ui/* (views with a client renderer).
91
- before: (env, url, request) => {
92
- if (env.ASSETS && (url.pathname === '/ui' || url.pathname.startsWith('/ui/'))) {
93
- if (env.VIEW_DEV_URL) {
94
- const devBase = env.VIEW_DEV_URL.replace(/\\/$/, '')
95
- return fetch(new Request(\`\${devBase}\${url.pathname}\${url.search}\`, request))
96
- }
97
- const stripped = url.pathname.replace(/^\\/ui\\/?/, '/')
98
- const rewritten = new URL(stripped + url.search, url.origin)
99
- return env.ASSETS.fetch(new Request(rewritten, request))
100
- }
101
- return undefined
102
- },
122
+ selfBinding: (env) => env.SELF,${routerHook}${clientHook}
103
123
  })
104
124
  `
105
125
  }