@astrale-os/adapter-cloudflare 0.5.0-beta.17 → 0.5.0-beta.170

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 (243) hide show
  1. package/README.md +230 -2
  2. package/dist/adapter.d.ts +24 -0
  3. package/dist/adapter.js +59 -0
  4. package/dist/build/bundle/admit.d.ts +3 -0
  5. package/dist/build/bundle/admit.js +46 -0
  6. package/dist/build/bundle/compiled-wasm-loader.d.ts +8 -0
  7. package/dist/build/bundle/compiled-wasm-loader.js +21 -0
  8. package/dist/build/bundler.d.ts +21 -0
  9. package/dist/build/bundler.js +96 -0
  10. package/dist/build/closure.d.ts +2 -0
  11. package/dist/build/closure.js +22 -0
  12. package/dist/build/codegen/execution.d.ts +3 -0
  13. package/dist/build/codegen/execution.js +2 -0
  14. package/dist/build/codegen/merge.d.ts +0 -1
  15. package/dist/build/codegen/merge.js +0 -1
  16. package/dist/build/codegen/wrangler.d.ts +32 -6
  17. package/dist/build/codegen/wrangler.js +77 -21
  18. package/dist/build/configuration/invocation-timeout.d.ts +2 -0
  19. package/dist/build/configuration/invocation-timeout.js +9 -0
  20. package/dist/build/configuration/request-limit.d.ts +2 -0
  21. package/dist/build/configuration/request-limit.js +9 -0
  22. package/dist/build/configuration/router.d.ts +7 -0
  23. package/dist/build/configuration/router.js +11 -0
  24. package/dist/build/configuration/settings.d.ts +6 -0
  25. package/dist/build/configuration/settings.js +42 -0
  26. package/dist/build/configuration.d.ts +4 -0
  27. package/dist/build/configuration.js +4 -0
  28. package/dist/build/frontend/files.d.ts +19 -0
  29. package/dist/build/frontend/files.js +99 -0
  30. package/dist/build/frontend/readiness.d.ts +10 -0
  31. package/dist/build/frontend/readiness.js +21 -0
  32. package/dist/build/frontend/revision.d.ts +5 -0
  33. package/dist/build/frontend/revision.js +14 -0
  34. package/dist/build/frontend/vite.d.ts +14 -0
  35. package/dist/build/frontend/vite.js +42 -0
  36. package/dist/build/frontend/vite.process.d.ts +7 -0
  37. package/dist/build/frontend/vite.process.js +65 -0
  38. package/dist/build/frontend/vite.runner.d.ts +1 -0
  39. package/dist/build/frontend/vite.runner.js +155 -0
  40. package/dist/build/index.d.ts +16 -14
  41. package/dist/build/index.js +15 -14
  42. package/dist/build/material.d.ts +10 -0
  43. package/dist/build/material.js +25 -0
  44. package/dist/build/metafile.d.ts +5 -0
  45. package/dist/build/metafile.js +14 -0
  46. package/dist/build/qualification.d.ts +5 -0
  47. package/dist/build/qualification.js +7 -0
  48. package/dist/build/roots.d.ts +2 -0
  49. package/dist/build/roots.js +34 -0
  50. package/dist/build/worker/artifact.d.ts +40 -0
  51. package/dist/build/worker/artifact.js +1 -0
  52. package/dist/build/worker/artifact.load.d.ts +3 -0
  53. package/dist/build/worker/artifact.load.js +308 -0
  54. package/dist/build/worker/artifact.manifest.d.ts +48 -0
  55. package/dist/build/worker/artifact.manifest.js +87 -0
  56. package/dist/build/worker/artifact.paths.d.ts +4 -0
  57. package/dist/build/worker/artifact.paths.js +25 -0
  58. package/dist/build/worker/codegen/entry.d.ts +10 -0
  59. package/dist/build/worker/codegen/entry.js +65 -0
  60. package/dist/build/worker/codegen/literal.d.ts +28 -0
  61. package/dist/build/worker/codegen/literal.js +136 -0
  62. package/dist/build/worker/codegen/service.d.ts +14 -0
  63. package/dist/build/worker/codegen/service.js +58 -0
  64. package/dist/build/worker/execution.prepare.d.ts +20 -0
  65. package/dist/build/worker/execution.prepare.js +129 -0
  66. package/dist/build/worker/index.d.ts +2 -0
  67. package/dist/build/worker/index.js +1 -0
  68. package/dist/build/worker/modules.d.ts +17 -0
  69. package/dist/build/worker/modules.js +32 -0
  70. package/dist/build/worker/runtime.d.ts +10 -0
  71. package/dist/build/worker/runtime.js +35 -0
  72. package/dist/build/worker/specifier.d.ts +5 -0
  73. package/dist/build/worker/specifier.js +15 -0
  74. package/dist/build/worker/upload.d.ts +39 -0
  75. package/dist/build/worker/upload.js +80 -0
  76. package/dist/cloudflare.d.ts +1 -45
  77. package/dist/cloudflare.js +1 -202
  78. package/dist/identity/binding.d.ts +2 -0
  79. package/dist/identity/binding.js +2 -0
  80. package/dist/identity/material.d.ts +7 -0
  81. package/dist/identity/material.js +42 -0
  82. package/dist/index.d.ts +16 -13
  83. package/dist/index.js +15 -12
  84. package/dist/legacy/direct/addressing.d.ts +2 -0
  85. package/dist/legacy/direct/addressing.js +17 -0
  86. package/dist/legacy/direct/deploy.d.ts +17 -0
  87. package/dist/legacy/direct/deploy.js +243 -0
  88. package/dist/legacy/direct/identity/index.d.ts +11 -0
  89. package/dist/legacy/direct/identity/index.js +11 -0
  90. package/dist/legacy/direct/identity/secret.d.ts +3 -0
  91. package/dist/legacy/direct/identity/secret.js +7 -0
  92. package/dist/legacy/direct/identity/source.d.ts +26 -0
  93. package/dist/legacy/direct/identity/source.js +127 -0
  94. package/dist/legacy/direct/index.d.ts +27 -0
  95. package/dist/legacy/direct/index.js +27 -0
  96. package/dist/legacy/direct/materialize.d.ts +4 -0
  97. package/dist/legacy/direct/materialize.js +10 -0
  98. package/dist/legacy/direct/placement.d.ts +4 -0
  99. package/dist/legacy/direct/placement.js +91 -0
  100. package/dist/legacy/direct/prepare.d.ts +7 -0
  101. package/dist/legacy/direct/prepare.js +158 -0
  102. package/dist/legacy/direct/readiness.d.ts +34 -0
  103. package/dist/legacy/direct/readiness.js +95 -0
  104. package/dist/legacy/direct/vars.d.ts +27 -0
  105. package/dist/legacy/direct/vars.js +53 -0
  106. package/dist/legacy/direct/worker-name.d.ts +2 -0
  107. package/dist/legacy/direct/worker-name.js +18 -0
  108. package/dist/legacy/direct/worker.d.ts +37 -0
  109. package/dist/legacy/direct/worker.js +58 -0
  110. package/dist/legacy/direct/wrangler/deploy.d.ts +24 -0
  111. package/dist/legacy/direct/wrangler/deploy.js +44 -0
  112. package/dist/legacy/direct/wrangler/output.d.ts +2 -0
  113. package/dist/legacy/direct/wrangler/output.js +10 -0
  114. package/dist/legacy/direct/wrangler/rollout.d.ts +27 -0
  115. package/dist/legacy/direct/wrangler/rollout.js +75 -0
  116. package/dist/legacy/direct/wrangler/secrets.d.ts +2 -0
  117. package/dist/legacy/direct/wrangler/secrets.js +24 -0
  118. package/dist/legacy/direct/wrangler/status.d.ts +18 -0
  119. package/dist/legacy/direct/wrangler/status.js +75 -0
  120. package/dist/legacy/direct/wrangler/triggers.d.ts +5 -0
  121. package/dist/legacy/direct/wrangler/triggers.js +12 -0
  122. package/dist/legacy/direct/wrangler/versions.d.ts +13 -0
  123. package/dist/legacy/direct/wrangler/versions.js +38 -0
  124. package/dist/namespace/api.d.ts +71 -0
  125. package/dist/namespace/api.js +265 -0
  126. package/dist/namespace/calls.d.ts +29 -0
  127. package/dist/namespace/calls.js +104 -0
  128. package/dist/namespace/configure.d.ts +26 -0
  129. package/dist/namespace/configure.js +199 -0
  130. package/dist/namespace/coordinates.d.ts +44 -0
  131. package/dist/namespace/coordinates.js +99 -0
  132. package/dist/namespace/deploy-release.d.ts +56 -0
  133. package/dist/namespace/deploy-release.js +403 -0
  134. package/dist/namespace/index.d.ts +15 -0
  135. package/dist/namespace/index.js +15 -0
  136. package/dist/namespace/list.d.ts +33 -0
  137. package/dist/namespace/list.js +139 -0
  138. package/dist/namespace/readiness.d.ts +40 -0
  139. package/dist/namespace/readiness.js +81 -0
  140. package/dist/namespace/routing.d.ts +55 -0
  141. package/dist/namespace/routing.js +165 -0
  142. package/dist/namespace/script.d.ts +106 -0
  143. package/dist/namespace/script.js +278 -0
  144. package/dist/params.d.ts +118 -40
  145. package/dist/params.js +0 -1
  146. package/dist/release/build.d.ts +35 -0
  147. package/dist/release/build.js +44 -0
  148. package/dist/release/deployment.d.ts +44 -0
  149. package/dist/release/deployment.js +141 -0
  150. package/dist/release/index.d.ts +3 -0
  151. package/dist/release/index.js +3 -0
  152. package/dist/release/staged-worker-name.d.ts +3 -0
  153. package/dist/release/staged-worker-name.js +10 -0
  154. package/dist/worker/build-digest.d.ts +16 -0
  155. package/dist/worker/build-digest.js +34 -0
  156. package/dist/worker/durable.d.ts +11 -0
  157. package/dist/worker/durable.js +24 -0
  158. package/dist/worker/entry.d.ts +36 -0
  159. package/dist/worker/entry.js +138 -0
  160. package/dist/worker/fetch.d.ts +0 -1
  161. package/dist/worker/fetch.js +0 -1
  162. package/dist/worker/front.d.ts +75 -0
  163. package/dist/worker/front.js +64 -0
  164. package/dist/worker/frontend.d.ts +4 -0
  165. package/dist/worker/frontend.js +83 -0
  166. package/dist/worker/identity.d.ts +7 -0
  167. package/dist/worker/identity.js +23 -0
  168. package/dist/worker/index.d.ts +6 -61
  169. package/dist/worker/index.js +6 -221
  170. package/dist/worker/restate.d.ts +6 -0
  171. package/dist/worker/restate.js +6 -0
  172. package/dist/worker/settings.d.ts +48 -0
  173. package/dist/worker/settings.js +161 -0
  174. package/dist/wrangler/bundle.d.ts +10 -0
  175. package/dist/wrangler/bundle.js +27 -0
  176. package/dist/wrangler/index.d.ts +3 -0
  177. package/dist/wrangler/index.js +3 -0
  178. package/dist/wrangler/process.d.ts +15 -0
  179. package/dist/wrangler/process.js +102 -0
  180. package/dist/wrangler/type-generation.d.ts +6 -0
  181. package/dist/wrangler/type-generation.js +19 -0
  182. package/package.json +45 -20
  183. package/template/astrale.config.ts +29 -30
  184. package/dist/build/codegen/identity.d.ts +0 -10
  185. package/dist/build/codegen/identity.d.ts.map +0 -1
  186. package/dist/build/codegen/identity.js +0 -32
  187. package/dist/build/codegen/identity.js.map +0 -1
  188. package/dist/build/codegen/merge.d.ts.map +0 -1
  189. package/dist/build/codegen/merge.js.map +0 -1
  190. package/dist/build/codegen/worker.d.ts +0 -12
  191. package/dist/build/codegen/worker.d.ts.map +0 -1
  192. package/dist/build/codegen/worker.js +0 -40
  193. package/dist/build/codegen/worker.js.map +0 -1
  194. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  195. package/dist/build/codegen/wrangler.js.map +0 -1
  196. package/dist/build/frontend.d.ts +0 -27
  197. package/dist/build/frontend.d.ts.map +0 -1
  198. package/dist/build/frontend.js +0 -252
  199. package/dist/build/frontend.js.map +0 -1
  200. package/dist/build/index.d.ts.map +0 -1
  201. package/dist/build/index.js.map +0 -1
  202. package/dist/build/parse-output.d.ts +0 -27
  203. package/dist/build/parse-output.d.ts.map +0 -1
  204. package/dist/build/parse-output.js +0 -58
  205. package/dist/build/parse-output.js.map +0 -1
  206. package/dist/build/prepare.d.ts +0 -18
  207. package/dist/build/prepare.d.ts.map +0 -1
  208. package/dist/build/prepare.js +0 -138
  209. package/dist/build/prepare.js.map +0 -1
  210. package/dist/build/worker.d.ts +0 -15
  211. package/dist/build/worker.d.ts.map +0 -1
  212. package/dist/build/worker.js +0 -21
  213. package/dist/build/worker.js.map +0 -1
  214. package/dist/build/wrangler-cli.d.ts +0 -88
  215. package/dist/build/wrangler-cli.d.ts.map +0 -1
  216. package/dist/build/wrangler-cli.js +0 -283
  217. package/dist/build/wrangler-cli.js.map +0 -1
  218. package/dist/cloudflare.d.ts.map +0 -1
  219. package/dist/cloudflare.js.map +0 -1
  220. package/dist/index.d.ts.map +0 -1
  221. package/dist/index.js.map +0 -1
  222. package/dist/params.d.ts.map +0 -1
  223. package/dist/params.js.map +0 -1
  224. package/dist/worker/fetch.d.ts.map +0 -1
  225. package/dist/worker/fetch.js.map +0 -1
  226. package/dist/worker/index.d.ts.map +0 -1
  227. package/dist/worker/index.js.map +0 -1
  228. package/src/build/codegen/identity.ts +0 -40
  229. package/src/build/codegen/merge.ts +0 -92
  230. package/src/build/codegen/worker.ts +0 -53
  231. package/src/build/codegen/wrangler.ts +0 -128
  232. package/src/build/frontend.ts +0 -298
  233. package/src/build/index.ts +0 -29
  234. package/src/build/parse-output.ts +0 -60
  235. package/src/build/prepare.ts +0 -189
  236. package/src/build/worker.ts +0 -37
  237. package/src/build/wrangler-cli.ts +0 -372
  238. package/src/cloudflare.ts +0 -275
  239. package/src/index.ts +0 -21
  240. package/src/params.ts +0 -92
  241. package/src/worker/fetch.ts +0 -46
  242. package/src/worker/index.ts +0 -335
  243. package/src/worker/tsconfig.json +0 -10
@@ -12,17 +12,27 @@
12
12
  * (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
13
13
  */
14
14
  import { deepMergeConfig } from './merge.js';
15
- // Wrangler 4.121.0's pinned workerd accepts dates through 2026-08-11.
16
- const COMPATIBILITY_DATE = '2026-08-11';
15
+ /**
16
+ * The Workers runtime every generated Worker runs on, wherever it is deployed: its compatibility
17
+ * date, kept on the runtime baseline qualified with Wrangler 4.123.0, and its compatibility flags.
18
+ * Domain execution calls its Kernel through the invocation's public issuer, so Cloudflare must route
19
+ * same-zone callbacks through the public front door instead of bypassing Worker routes
20
+ * (`global_fetch_strictly_public`). An immutable deployment freezes it into its configuration.
21
+ */
22
+ export const GENERATED_WORKER_RUNTIME = Object.freeze({
23
+ compatibilityDate: '2026-08-11',
24
+ compatibilityFlags: Object.freeze(['nodejs_compat', 'global_fetch_strictly_public']),
25
+ });
17
26
  export function generateWranglerConfig(opts) {
18
27
  const config = {
19
28
  name: opts.workerName,
20
- main: './worker.gen.ts',
21
- compatibility_date: COMPATIBILITY_DATE,
22
- compatibility_flags: ['nodejs_compat'],
23
- workers_dev: true,
29
+ main: opts.main ?? './worker.gen.ts',
30
+ compatibility_date: GENERATED_WORKER_RUNTIME.compatibilityDate,
31
+ compatibility_flags: [...GENERATED_WORKER_RUNTIME.compatibilityFlags],
32
+ workers_dev: opts.route === undefined,
24
33
  version_metadata: { binding: 'ASTRALE_VERSION_METADATA' },
25
34
  observability: { enabled: true },
35
+ minify: true,
26
36
  };
27
37
  if (opts.route) {
28
38
  config.routes = [{ pattern: opts.route, custom_domain: true }];
@@ -31,19 +41,10 @@ export function generateWranglerConfig(opts) {
31
41
  config.assets = {
32
42
  directory: opts.frontend.directory,
33
43
  binding: 'ASSETS',
34
- run_worker_first: opts.frontend.dev
35
- ? true
36
- : [
37
- '/invoke',
38
- '/invoke/*',
39
- '/install',
40
- '/install/*',
41
- '/health',
42
- '/health/*',
43
- '/.well-known',
44
- '/.well-known/*',
45
- ...opts.frontend.routes,
46
- ],
44
+ html_handling: 'none',
45
+ // Match the portable Worker artifact: frontend routing and sandbox-safe asset
46
+ // response headers are owned by the Worker for both deployment adapters.
47
+ run_worker_first: true,
47
48
  };
48
49
  }
49
50
  const services = [];
@@ -57,6 +58,18 @@ export function generateWranglerConfig(opts) {
57
58
  if (opts.vars && Object.keys(opts.vars).length > 0) {
58
59
  config.vars = opts.vars;
59
60
  }
61
+ if (opts.secretBindings && opts.secretBindings.length > 0) {
62
+ config.secrets = { required: [...opts.secretBindings] };
63
+ }
64
+ if (opts.modules !== undefined) {
65
+ config.find_additional_modules = true;
66
+ config.base_dir = opts.modules.baseDir;
67
+ config.rules = opts.modules.rules.map((rule) => ({
68
+ type: rule.type,
69
+ globs: [...rule.globs],
70
+ fallthrough: true,
71
+ }));
72
+ }
60
73
  const merged = opts.wrangler ? mergeUserConfig(config, opts.wrangler) : config;
61
74
  return JSON.stringify(merged, null, 2) + '\n';
62
75
  }
@@ -67,11 +80,43 @@ const ADAPTER_OWNED_KEYS = [
67
80
  'assets',
68
81
  'routes',
69
82
  'compatibility_date',
83
+ 'workers_dev',
70
84
  'version_metadata',
85
+ 'secrets',
86
+ 'find_additional_modules',
87
+ 'base_dir',
71
88
  ];
89
+ /**
90
+ * Keys Wrangler's bundler reads. Bundling sees the adapter base configuration only, so that every
91
+ * Environment of one source bundles the same bytes; an Environment's overlay never sets these.
92
+ */
93
+ const BUNDLING_KEYS = [
94
+ 'alias',
95
+ 'build',
96
+ 'define',
97
+ 'jsx_factory',
98
+ 'jsx_fragment',
99
+ 'keep_names',
100
+ 'minify',
101
+ 'no_bundle',
102
+ 'node_compat',
103
+ 'preserve_file_names',
104
+ 'rules',
105
+ 'tsconfig',
106
+ 'upload_source_maps',
107
+ ];
108
+ /** Compatibility flags Wrangler's bundler reads: Node.js polyfills, `navigator`, fetch typing, Python. */
109
+ const BUNDLING_FLAG = /nodejs|navigator|fetch_iterable_type_support|python_workers/u;
110
+ /**
111
+ * Whether a compatibility flag shapes the bundle (Wrangler's bundler reads it), so that no
112
+ * Environment may add it: bundling takes no Environment.
113
+ */
114
+ export function isBundlingCompatibilityFlag(flag) {
115
+ return BUNDLING_FLAG.test(flag);
116
+ }
72
117
  /**
73
118
  * Deep-merge the dev's raw wrangler overlay over the generated config, after
74
- * refusing any attempt to override an adapter-owned key. Throwing (rather than
119
+ * refusing any attempt to override an adapter-owned key or a bundler input. Throwing (rather than
75
120
  * silently dropping) keeps a stray `main` from breaking the Worker unnoticed.
76
121
  */
77
122
  function mergeUserConfig(base, user) {
@@ -81,6 +126,17 @@ function mergeUserConfig(base, user) {
81
126
  'Use `workerName` for the worker name and `route` for routing; ' +
82
127
  '`main` and `assets` are generated by the adapter.');
83
128
  }
129
+ const baseFlags = Array.isArray(base.compatibility_flags) ? base.compatibility_flags : [];
130
+ const bundling = [
131
+ ...BUNDLING_KEYS.filter((k) => k in user),
132
+ ...(Array.isArray(user.compatibility_flags) ? user.compatibility_flags : [])
133
+ .filter((flag) => typeof flag === 'string' && BUNDLING_FLAG.test(flag))
134
+ .filter((flag) => !baseFlags.includes(flag))
135
+ .map((flag) => `compatibility_flags ${String(flag)}`),
136
+ ];
137
+ if (bundling.length > 0) {
138
+ throw new Error(`wrangler: ${bundling.join(', ')} shape(s) the bundle, which takes no Environment. ` +
139
+ 'Bundling uses the adapter base configuration only.');
140
+ }
84
141
  return deepMergeConfig(base, user);
85
142
  }
86
- //# sourceMappingURL=wrangler.js.map
@@ -0,0 +1,2 @@
1
+ import type { CloudflareParams } from '../../params.js';
2
+ export declare function workerInvocationTimeout(value: CloudflareParams['invocationTimeoutMs']): number | undefined;
@@ -0,0 +1,9 @@
1
+ import { MAXIMUM_INVOCATION_TIMEOUT_MS } from '../../worker/settings.js';
2
+ export function workerInvocationTimeout(value) {
3
+ if (value === undefined)
4
+ return undefined;
5
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAXIMUM_INVOCATION_TIMEOUT_MS) {
6
+ throw new TypeError('Cloudflare invocationTimeoutMs must be a positive integer within the platform timer range.');
7
+ }
8
+ return value;
9
+ }
@@ -0,0 +1,2 @@
1
+ import type { CloudflareParams } from '../../params.js';
2
+ export declare function workerRequestLimit(value: CloudflareParams['maximumRequestBytes']): number | undefined;
@@ -0,0 +1,9 @@
1
+ import { MAXIMUM_REQUEST_BYTES } from '../../worker/settings.js';
2
+ export function workerRequestLimit(value) {
3
+ if (value === undefined)
4
+ return undefined;
5
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAXIMUM_REQUEST_BYTES) {
6
+ throw new TypeError('Cloudflare maximumRequestBytes must be a positive integer at most 16 MiB.');
7
+ }
8
+ return value;
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { CloudflareParams } from '../../params.js';
2
+ export declare function resolveRouter(input: CloudflareParams['router']): {
3
+ readonly binding: string;
4
+ readonly service: string;
5
+ readonly hostSuffix: string;
6
+ readonly minLabels: number;
7
+ } | undefined;
@@ -0,0 +1,11 @@
1
+ export function resolveRouter(input) {
2
+ if (input === false || input === undefined)
3
+ return undefined;
4
+ const value = input;
5
+ return Object.freeze({
6
+ binding: value.binding ?? 'ROUTER',
7
+ service: value.service ?? 'admin-router',
8
+ hostSuffix: value.hostSuffix ?? '.astrale.ai',
9
+ minLabels: value.minLabels ?? 4,
10
+ });
11
+ }
@@ -0,0 +1,6 @@
1
+ import type { CloudflareParams } from '../../params.js';
2
+ import type { WorkerSettings } from '../../worker/settings.js';
3
+ /** Whether a binding name is one the adapter owns, so no author var or secret may take it. */
4
+ export declare function isAdapterVar(name: string): boolean;
5
+ /** Resolve one Environment's execution settings, defaults included, as its deployment binds them. */
6
+ export declare function workerSettings(parameters: CloudflareParams): WorkerSettings;
@@ -0,0 +1,42 @@
1
+ import { maximumExchangeTtl } from '@astrale-os/sdk/execution';
2
+ import { BUILD_DIGEST_BINDING } from '../../worker/build-digest.js';
3
+ import { captureDurableOptions, DEFAULT_INVOCATION_TIMEOUT_MS, DEFAULT_MAXIMUM_REQUEST_BYTES, SETTINGS_BINDING, } from '../../worker/settings.js';
4
+ import { workerInvocationTimeout } from './invocation-timeout.js';
5
+ import { workerRequestLimit } from './request-limit.js';
6
+ import { resolveRouter } from './router.js';
7
+ /**
8
+ * Plain bindings the adapter owns on a deployment, bound or not; no author var or secret takes
9
+ * these names. `IDENTITY_ISS` and `ASTRALE_PROVIDER_ASSIGNED_WORKER` belong to legacy direct mode.
10
+ */
11
+ const ADAPTER_VARS = new Set([
12
+ 'WORKER_URL',
13
+ BUILD_DIGEST_BINDING,
14
+ SETTINGS_BINDING,
15
+ 'IDENTITY_ISS',
16
+ 'ASTRALE_PROVIDER_ASSIGNED_WORKER',
17
+ ]);
18
+ /** Whether a binding name is one the adapter owns, so no author var or secret may take it. */
19
+ export function isAdapterVar(name) {
20
+ return ADAPTER_VARS.has(name);
21
+ }
22
+ /** Resolve one Environment's execution settings, defaults included, as its deployment binds them. */
23
+ export function workerSettings(parameters) {
24
+ const router = resolveRouter(parameters.router);
25
+ return Object.freeze({
26
+ ...(parameters.durable === undefined
27
+ ? {}
28
+ : { durableOptions: captureDurableOptions(parameters.durable) }),
29
+ maximumRequestBytes: workerRequestLimit(parameters.maximumRequestBytes) ?? DEFAULT_MAXIMUM_REQUEST_BYTES,
30
+ invocationTimeoutMs: workerInvocationTimeout(parameters.invocationTimeoutMs) ?? DEFAULT_INVOCATION_TIMEOUT_MS,
31
+ tokenExchange: parameters.tokenExchange === false
32
+ ? false
33
+ : Object.freeze({ maximumLifetimeSeconds: maximumExchangeTtl(parameters.tokenExchange) }),
34
+ router: router === undefined
35
+ ? false
36
+ : Object.freeze({
37
+ binding: router.binding,
38
+ hostSuffix: router.hostSuffix,
39
+ minLabels: router.minLabels,
40
+ }),
41
+ });
42
+ }
@@ -0,0 +1,4 @@
1
+ export { workerInvocationTimeout } from './configuration/invocation-timeout.js';
2
+ export { workerRequestLimit } from './configuration/request-limit.js';
3
+ export { resolveRouter } from './configuration/router.js';
4
+ export { isAdapterVar, workerSettings } from './configuration/settings.js';
@@ -0,0 +1,4 @@
1
+ export { workerInvocationTimeout } from './configuration/invocation-timeout.js';
2
+ export { workerRequestLimit } from './configuration/request-limit.js';
3
+ export { resolveRouter } from './configuration/router.js';
4
+ export { isAdapterVar, workerSettings } from './configuration/settings.js';
@@ -0,0 +1,19 @@
1
+ import type { Build } from '@astrale-os/sdk/deployment/build';
2
+ import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
3
+ /** The served frontend, and which of its files a browser may keep without revalidating. */
4
+ export interface FrontendOutput {
5
+ readonly files: readonly frontendBuild.FrontendFile[];
6
+ /**
7
+ * Served paths the bundler content-addressed, so their bytes can never change under a given name.
8
+ * A frontend Astrale does not build leaves this empty, which is what keeps the absence of evidence
9
+ * indistinguishable from evidence of mutability.
10
+ */
11
+ readonly addressed: readonly string[];
12
+ }
13
+ export declare function frontendFiles(input: {
14
+ readonly frontend: Build['frontend'];
15
+ readonly projectDir: string;
16
+ readonly output: string;
17
+ readonly recordSource?: (path: string) => void;
18
+ readonly signal: AbortSignal;
19
+ }): Promise<FrontendOutput>;
@@ -0,0 +1,99 @@
1
+ import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
2
+ import { readFile, readdir, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { buildViteFrontend } from './vite.js';
5
+ /**
6
+ * Root names that Cloudflare Workers Static Assets reads as configuration rather than serving.
7
+ *
8
+ * Wrangler withholds them from a direct upload and applies them, while the managed upload through
9
+ * Services serves them as ordinary public files, so the same output would behave differently per
10
+ * adapter. The Worker runs first on every request and owns frontend response headers and routing for
11
+ * both, which leaves these files nothing to configure.
12
+ */
13
+ const RESERVED_ROOT_NAMES = new Set([
14
+ '_headers',
15
+ '_redirects',
16
+ '.assetsignore',
17
+ ]);
18
+ /**
19
+ * Name of a Cloudflare Pages worker script, the server-side code of a Pages project.
20
+ *
21
+ * Wrangler refuses to upload a root asset under this name, since that would publish the code, unless
22
+ * an `.assetsignore` exists, and the reserved names above already exclude one. A direct deployment
23
+ * would therefore fail at upload, while the managed upload through Services would publish the code.
24
+ * Neither adapter runs a Pages worker: the Worker is the only server-side code for both.
25
+ */
26
+ const PAGES_WORKER_NAME = '_worker.js';
27
+ export async function frontendFiles(input) {
28
+ const frontend = input.frontend;
29
+ if (frontend === undefined)
30
+ return { files: [], addressed: [] };
31
+ if (frontend.source.kind === 'external') {
32
+ return { files: frontendBuild.files(frontend, []), addressed: [] };
33
+ }
34
+ await rm(input.output, { recursive: true, force: true });
35
+ const built = await buildViteFrontend({
36
+ projectDir: input.projectDir,
37
+ output: input.output,
38
+ ...(frontend.source.config === undefined ? {} : { config: frontend.source.config }),
39
+ recordSource: input.recordSource,
40
+ signal: input.signal,
41
+ });
42
+ const files = frontendBuild.files(frontend, await readFiles(input.output));
43
+ refuseReservedNames(files);
44
+ refusePagesWorker(files);
45
+ // A name the bundler reported but that never reached disk is not a served path; keeping only the
46
+ // intersection means the evidence describes exactly what this deployment will answer with.
47
+ const served = new Set(files.map((file) => file.path));
48
+ return { files, addressed: built.addressed.filter((path) => served.has(path)) };
49
+ }
50
+ /**
51
+ * Refuse output that Cloudflare would read as asset configuration instead of serving it.
52
+ *
53
+ * Only the root is reserved: a nested `docs/_headers` stays an ordinary file. The match ignores case
54
+ * and covers directories, because Wrangler's upload filter withholds any root entry under these names
55
+ * regardless of case, and a case-insensitive filesystem hands it `_Headers` when it reads `_headers`.
56
+ */
57
+ function refuseReservedNames(files) {
58
+ const reserved = new Set(files
59
+ .map((file) => file.path.split('/')[0])
60
+ .filter((name) => RESERVED_ROOT_NAMES.has(name.toLowerCase())));
61
+ if (reserved.size === 0)
62
+ return;
63
+ const names = [...reserved].join(', ');
64
+ throw new TypeError(`Frontend output contains reserved Cloudflare asset configuration name(s) at its root: ${names}. ` +
65
+ 'A direct cloudflare() deployment would apply them as Workers Static Assets configuration, while a managed astrale() deployment would serve them as public files. ' +
66
+ 'The Worker owns frontend response headers and routing for both adapters: remove them from the Vite public directory, or stop the build from emitting them.');
67
+ }
68
+ /**
69
+ * Refuse output that Wrangler would decline to upload as a Cloudflare Pages worker script.
70
+ *
71
+ * Wrangler tests the relative path with a plain `startsWith`, not through its case-insensitive upload
72
+ * filter, so the match here is a case-sensitive prefix of the root name too. It covers a `_worker.js`
73
+ * file or directory and siblings such as `_worker.js.map`, which Wrangler refuses alike, while both
74
+ * adapters serve `_Worker.js` or a nested `docs/_worker.js` as an ordinary file.
75
+ */
76
+ function refusePagesWorker(files) {
77
+ const workers = new Set(files
78
+ .map((file) => file.path.split('/')[0])
79
+ .filter((name) => name.startsWith(PAGES_WORKER_NAME)));
80
+ if (workers.size === 0)
81
+ return;
82
+ const names = [...workers].join(', ');
83
+ throw new TypeError(`Frontend output contains Cloudflare Pages worker name(s) at its root: ${names}. ` +
84
+ `A direct cloudflare() deployment would fail because Wrangler refuses to upload a root name starting with ${PAGES_WORKER_NAME} as possible server-side code, while a managed astrale() deployment would serve them as public files. ` +
85
+ 'The Worker is the only server-side code for both adapters: remove or rename them in the Vite public directory, or stop the build from emitting them.');
86
+ }
87
+ async function readFiles(root, current = '') {
88
+ const entries = await readdir(join(root, current), { withFileTypes: true });
89
+ const files = [];
90
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
91
+ const path = current === '' ? entry.name : `${current}/${entry.name}`;
92
+ if (entry.isDirectory())
93
+ files.push(...(await readFiles(root, path)));
94
+ else if (entry.isFile()) {
95
+ files.push({ path, content: new Uint8Array(await readFile(join(root, path))) });
96
+ }
97
+ }
98
+ return files;
99
+ }
@@ -0,0 +1,10 @@
1
+ import type { legacy } from '@astrale-os/sdk/deployment/release';
2
+ import type { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
3
+ import type { WorkerArtifact } from '../worker/artifact.js';
4
+ type ReadinessFetch = NonNullable<NonNullable<Parameters<typeof verifyReadiness>[1]>['fetch']>;
5
+ /** Publication equality alone cannot prove a frontend-only deployment has propagated. */
6
+ export declare function verifyWorkerFrontend(release: legacy.Release, worker: Pick<WorkerArtifact, 'assets'>, options?: {
7
+ readonly fetch?: ReadinessFetch;
8
+ readonly signal?: AbortSignal;
9
+ }): Promise<void>;
10
+ export {};
@@ -0,0 +1,21 @@
1
+ import { frontendRevision } from './revision.js';
2
+ /** Publication equality alone cannot prove a frontend-only deployment has propagated. */
3
+ export async function verifyWorkerFrontend(release, worker, options = {}) {
4
+ const frontend = release.build.frontend;
5
+ if (frontend === undefined || frontend.source.kind === 'external')
6
+ return;
7
+ if (worker.assets === undefined)
8
+ throw new Error('Prepared Worker frontend assets are missing.');
9
+ const expected = frontendRevision(worker.assets.files);
10
+ const fetcher = options.fetch ?? globalThis.fetch;
11
+ for (const route of frontend.routes) {
12
+ const response = await fetcher(new URL(route.path, release.addressing.publicUrl).href, {
13
+ redirect: 'error',
14
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
15
+ });
16
+ const matches = response.status === 200 && response.headers.get('X-Astrale-Frontend') === expected;
17
+ await response.body?.cancel();
18
+ if (!matches)
19
+ throw new Error(`Frontend ${route.path} is not serving the prepared browser build yet.`);
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ /** Bind the generated Worker to its exact browser files, independently of Schema revision. */
2
+ export declare function frontendRevision(files: readonly {
3
+ readonly path: string;
4
+ bytes(): Uint8Array;
5
+ }[]): string;
@@ -0,0 +1,14 @@
1
+ import { createHash } from 'node:crypto';
2
+ /** Bind the generated Worker to its exact browser files, independently of Schema revision. */
3
+ export function frontendRevision(files) {
4
+ const hash = createHash('sha256');
5
+ const ordered = files
6
+ .map((file) => ({ path: file.path.replace(/^\//u, ''), bytes: file.bytes }))
7
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
8
+ for (const file of ordered) {
9
+ const bytes = file.bytes();
10
+ hash.update(JSON.stringify([file.path, bytes.byteLength]));
11
+ hash.update(bytes);
12
+ }
13
+ return `sha256:${hash.digest('hex')}`;
14
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Build a Vite frontend in an isolated process and report its authored inputs, along with the
3
+ * outputs the bundler content-addressed. Those are the files a browser may keep forever, and only
4
+ * the build that named them can say which they are.
5
+ */
6
+ export declare function buildViteFrontend(input: {
7
+ readonly projectDir: string;
8
+ readonly output: string;
9
+ readonly config?: string;
10
+ readonly recordSource?: (path: string) => void;
11
+ readonly signal: AbortSignal;
12
+ }): Promise<{
13
+ readonly addressed: readonly string[];
14
+ }>;
@@ -0,0 +1,42 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { runViteProcess } from './vite.process.js';
6
+ /**
7
+ * Build a Vite frontend in an isolated process and report its authored inputs, along with the
8
+ * outputs the bundler content-addressed. Those are the files a browser may keep forever, and only
9
+ * the build that named them can say which they are.
10
+ */
11
+ export async function buildViteFrontend(input) {
12
+ const parent = dirname(input.output);
13
+ await mkdir(parent, { recursive: true });
14
+ const scratch = await mkdtemp(join(parent, 'vite-run-'));
15
+ const evidence = join(scratch, 'sources.json');
16
+ const addressed = join(scratch, 'addressed.json');
17
+ try {
18
+ await runViteProcess({
19
+ cwd: input.projectDir,
20
+ arguments: [runnerPath(), input.output, evidence, input.config ?? '', addressed],
21
+ signal: input.signal,
22
+ });
23
+ const sources = JSON.parse(await readFile(evidence, 'utf8'));
24
+ if (!Array.isArray(sources) || sources.some((path) => typeof path !== 'string')) {
25
+ throw new TypeError('Vite preparation source evidence is invalid.');
26
+ }
27
+ for (const path of sources)
28
+ input.recordSource?.(path);
29
+ const named = JSON.parse(await readFile(addressed, 'utf8'));
30
+ if (!Array.isArray(named) || named.some((path) => typeof path !== 'string')) {
31
+ throw new TypeError('Vite preparation output evidence is invalid.');
32
+ }
33
+ return { addressed: Object.freeze([...named]) };
34
+ }
35
+ finally {
36
+ await rm(scratch, { recursive: true, force: true });
37
+ }
38
+ }
39
+ function runnerPath() {
40
+ const built = fileURLToPath(new URL('./vite.runner.js', import.meta.url));
41
+ return existsSync(built) ? built : fileURLToPath(new URL('./vite.runner.ts', import.meta.url));
42
+ }
@@ -0,0 +1,7 @@
1
+ /** Run one bounded, cancellable Vite subprocess. */
2
+ export declare function runViteProcess(input: {
3
+ readonly cwd: string;
4
+ readonly arguments: readonly string[];
5
+ readonly signal: AbortSignal;
6
+ readonly timeoutMs?: number;
7
+ }): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { spawn } from 'node:child_process';
2
+ const MAXIMUM_OUTPUT_BYTES = 1024 * 1024;
3
+ const TERMINATION_GRACE_MS = 1_000;
4
+ const VITE_TIMEOUT_MS = 300_000;
5
+ /** Run one bounded, cancellable Vite subprocess. */
6
+ export function runViteProcess(input) {
7
+ if (input.signal.aborted)
8
+ return Promise.reject(abortReason(input.signal));
9
+ return new Promise((resolve, reject) => {
10
+ const child = spawn(process.execPath, input.arguments, {
11
+ cwd: input.cwd,
12
+ stdio: ['ignore', 'pipe', 'pipe'],
13
+ windowsHide: true,
14
+ });
15
+ let output = '';
16
+ let outputBytes = 0;
17
+ let terminating;
18
+ let escalation;
19
+ const timeout = setTimeout(() => terminate(new Error('Vite process timed out.')), input.timeoutMs ?? VITE_TIMEOUT_MS);
20
+ const abort = () => terminate(abortReason(input.signal));
21
+ input.signal.addEventListener('abort', abort, { once: true });
22
+ const cleanup = () => {
23
+ clearTimeout(timeout);
24
+ if (escalation !== undefined)
25
+ clearTimeout(escalation);
26
+ input.signal.removeEventListener('abort', abort);
27
+ };
28
+ function terminate(cause) {
29
+ if (terminating !== undefined)
30
+ return;
31
+ terminating = cause;
32
+ child.kill('SIGTERM');
33
+ escalation = setTimeout(() => {
34
+ if (child.exitCode === null && child.signalCode === null)
35
+ child.kill('SIGKILL');
36
+ }, TERMINATION_GRACE_MS);
37
+ }
38
+ const collect = (buffer) => {
39
+ outputBytes += buffer.byteLength;
40
+ if (outputBytes > MAXIMUM_OUTPUT_BYTES) {
41
+ terminate(new Error('Vite process exceeded its output limit.'));
42
+ return;
43
+ }
44
+ output += buffer.toString();
45
+ };
46
+ child.stdout.on('data', collect);
47
+ child.stderr.on('data', collect);
48
+ child.once('error', (cause) => {
49
+ cleanup();
50
+ reject(terminating ?? cause);
51
+ });
52
+ child.once('close', (code) => {
53
+ cleanup();
54
+ if (terminating !== undefined)
55
+ reject(terminating);
56
+ else if (code === 0)
57
+ resolve();
58
+ else
59
+ reject(new Error(`Vite failed (code ${code ?? 1}):\n${output.slice(-1500)}`));
60
+ });
61
+ });
62
+ }
63
+ function abortReason(signal) {
64
+ return signal.reason instanceof Error ? signal.reason : new Error('Vite process cancelled.');
65
+ }
@@ -0,0 +1 @@
1
+ export {};