@astrale-os/adapter-cloudflare 0.5.0-beta.4 → 0.5.0-beta.41

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 (180) hide show
  1. package/README.md +11 -0
  2. package/dist/adapter.d.ts +17 -0
  3. package/dist/adapter.js +178 -0
  4. package/dist/build/artifact.d.ts +7 -0
  5. package/dist/build/artifact.js +151 -0
  6. package/dist/build/bundle/admit.d.ts +1 -0
  7. package/dist/build/bundle/admit.js +9 -0
  8. package/dist/build/closure.d.ts +2 -0
  9. package/dist/build/closure.js +22 -0
  10. package/dist/build/codegen/execution.d.ts +3 -0
  11. package/dist/build/codegen/execution.js +2 -0
  12. package/dist/build/codegen/gateway.d.ts +1 -0
  13. package/dist/build/codegen/gateway.js +1 -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 +18 -1
  17. package/dist/build/codegen/wrangler.js +36 -17
  18. package/dist/build/configuration/addressing.d.ts +2 -0
  19. package/dist/build/configuration/addressing.js +11 -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/worker-name.d.ts +2 -0
  25. package/dist/build/configuration/worker-name.js +18 -0
  26. package/dist/build/configuration.d.ts +4 -0
  27. package/dist/build/configuration.js +4 -0
  28. package/dist/build/direct.d.ts +4 -0
  29. package/dist/build/direct.js +91 -0
  30. package/dist/build/frontend/routes.d.ts +2 -0
  31. package/dist/build/frontend/routes.js +11 -0
  32. package/dist/build/frontend-v1.d.ts +16 -0
  33. package/dist/build/frontend-v1.js +133 -0
  34. package/dist/build/gateway/prepare.d.ts +8 -0
  35. package/dist/build/gateway/prepare.js +33 -0
  36. package/dist/build/gateway/source.d.ts +1 -0
  37. package/dist/build/gateway/source.js +7 -0
  38. package/dist/build/index.d.ts +10 -16
  39. package/dist/build/index.js +9 -16
  40. package/dist/build/materialize.d.ts +4 -0
  41. package/dist/build/materialize.js +10 -0
  42. package/dist/build/metafile.d.ts +5 -0
  43. package/dist/build/metafile.js +14 -0
  44. package/dist/build/parse-output.d.ts +0 -1
  45. package/dist/build/parse-output.js +0 -1
  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 +39 -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 +277 -0
  54. package/dist/build/worker/artifact.manifest.d.ts +47 -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 +2 -0
  59. package/dist/build/worker/codegen/entry.js +28 -0
  60. package/dist/build/worker/codegen/service.d.ts +15 -0
  61. package/dist/build/worker/codegen/service.js +46 -0
  62. package/dist/build/worker/execution.prepare.d.ts +18 -0
  63. package/dist/build/worker/execution.prepare.js +108 -0
  64. package/dist/build/worker/index.d.ts +2 -0
  65. package/dist/build/worker/index.js +1 -0
  66. package/dist/build/worker/modules.d.ts +11 -0
  67. package/dist/build/worker/modules.js +26 -0
  68. package/dist/build/worker/runtime.d.ts +10 -0
  69. package/dist/build/worker/runtime.js +35 -0
  70. package/dist/build/worker/specifier.d.ts +1 -0
  71. package/dist/build/worker/specifier.js +5 -0
  72. package/dist/build/wrangler/bundle.d.ts +10 -0
  73. package/dist/build/wrangler/bundle.js +29 -0
  74. package/dist/build/wrangler/deploy.d.ts +23 -0
  75. package/dist/build/wrangler/deploy.js +35 -0
  76. package/dist/build/wrangler/development.d.ts +15 -0
  77. package/dist/build/wrangler/development.js +135 -0
  78. package/dist/build/wrangler/namespace.d.ts +10 -0
  79. package/dist/build/wrangler/namespace.js +26 -0
  80. package/dist/build/wrangler/process.d.ts +17 -0
  81. package/dist/build/wrangler/process.js +115 -0
  82. package/dist/build/wrangler/release.d.ts +11 -0
  83. package/dist/build/wrangler/release.js +27 -0
  84. package/dist/build/wrangler/secrets.d.ts +6 -0
  85. package/dist/build/wrangler/secrets.js +58 -0
  86. package/dist/build/wrangler/status.d.ts +14 -0
  87. package/dist/build/wrangler/status.js +56 -0
  88. package/dist/build/wrangler/type-generation.d.ts +6 -0
  89. package/dist/build/wrangler/type-generation.js +19 -0
  90. package/dist/build/wrangler-cli.d.ts +8 -86
  91. package/dist/build/wrangler-cli.js +8 -275
  92. package/dist/cloudflare.d.ts +1 -34
  93. package/dist/cloudflare.js +1 -205
  94. package/dist/deploy/deploy.d.ts +17 -0
  95. package/dist/deploy/deploy.js +234 -0
  96. package/dist/deploy/index.d.ts +1 -0
  97. package/dist/deploy/index.js +1 -0
  98. package/dist/identity/binding.d.ts +2 -0
  99. package/dist/identity/binding.js +2 -0
  100. package/dist/identity/material.d.ts +7 -0
  101. package/dist/identity/material.js +42 -0
  102. package/dist/identity/secret.d.ts +3 -0
  103. package/dist/identity/secret.js +7 -0
  104. package/dist/identity/source.d.ts +26 -0
  105. package/dist/identity/source.js +98 -0
  106. package/dist/index.d.ts +3 -4
  107. package/dist/index.js +3 -4
  108. package/dist/params.d.ts +24 -5
  109. package/dist/params.js +0 -1
  110. package/dist/release/index.d.ts +2 -0
  111. package/dist/release/index.js +2 -0
  112. package/dist/release/namespace.d.ts +2 -0
  113. package/dist/release/namespace.js +6 -0
  114. package/dist/release/target.d.ts +2 -0
  115. package/dist/release/target.js +9 -0
  116. package/dist/worker/entry.d.ts +39 -0
  117. package/dist/worker/entry.js +162 -0
  118. package/dist/worker/fetch.d.ts +11 -0
  119. package/dist/worker/fetch.js +31 -0
  120. package/dist/worker/gateway.d.ts +15 -0
  121. package/dist/worker/gateway.js +26 -0
  122. package/dist/worker/identity.d.ts +7 -0
  123. package/dist/worker/identity.js +23 -0
  124. package/dist/worker/index.d.ts +3 -59
  125. package/dist/worker/index.js +3 -209
  126. package/package.json +21 -18
  127. package/template/astrale.config.ts +19 -11
  128. package/dist/build/codegen/identity.d.ts +0 -10
  129. package/dist/build/codegen/identity.d.ts.map +0 -1
  130. package/dist/build/codegen/identity.js +0 -32
  131. package/dist/build/codegen/identity.js.map +0 -1
  132. package/dist/build/codegen/merge.d.ts.map +0 -1
  133. package/dist/build/codegen/merge.js.map +0 -1
  134. package/dist/build/codegen/worker.d.ts +0 -12
  135. package/dist/build/codegen/worker.d.ts.map +0 -1
  136. package/dist/build/codegen/worker.js +0 -40
  137. package/dist/build/codegen/worker.js.map +0 -1
  138. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  139. package/dist/build/codegen/wrangler.js.map +0 -1
  140. package/dist/build/frontend.d.ts +0 -26
  141. package/dist/build/frontend.d.ts.map +0 -1
  142. package/dist/build/frontend.js +0 -263
  143. package/dist/build/frontend.js.map +0 -1
  144. package/dist/build/index.d.ts.map +0 -1
  145. package/dist/build/index.js.map +0 -1
  146. package/dist/build/parse-output.d.ts.map +0 -1
  147. package/dist/build/parse-output.js.map +0 -1
  148. package/dist/build/prepare.d.ts +0 -18
  149. package/dist/build/prepare.d.ts.map +0 -1
  150. package/dist/build/prepare.js +0 -117
  151. package/dist/build/prepare.js.map +0 -1
  152. package/dist/build/worker.d.ts +0 -15
  153. package/dist/build/worker.d.ts.map +0 -1
  154. package/dist/build/worker.js +0 -21
  155. package/dist/build/worker.js.map +0 -1
  156. package/dist/build/wrangler-cli.d.ts.map +0 -1
  157. package/dist/build/wrangler-cli.js.map +0 -1
  158. package/dist/cloudflare.d.ts.map +0 -1
  159. package/dist/cloudflare.js.map +0 -1
  160. package/dist/index.d.ts.map +0 -1
  161. package/dist/index.js.map +0 -1
  162. package/dist/params.d.ts.map +0 -1
  163. package/dist/params.js.map +0 -1
  164. package/dist/worker/index.d.ts.map +0 -1
  165. package/dist/worker/index.js.map +0 -1
  166. package/src/build/codegen/identity.ts +0 -40
  167. package/src/build/codegen/merge.ts +0 -92
  168. package/src/build/codegen/worker.ts +0 -53
  169. package/src/build/codegen/wrangler.ts +0 -127
  170. package/src/build/frontend.ts +0 -324
  171. package/src/build/index.ts +0 -29
  172. package/src/build/parse-output.ts +0 -60
  173. package/src/build/prepare.ts +0 -161
  174. package/src/build/worker.ts +0 -37
  175. package/src/build/wrangler-cli.ts +0 -362
  176. package/src/cloudflare.ts +0 -251
  177. package/src/index.ts +0 -21
  178. package/src/params.ts +0 -92
  179. package/src/worker/index.ts +0 -314
  180. package/src/worker/tsconfig.json +0 -10
@@ -0,0 +1,277 @@
1
+ import { isPreparedArtifact } from '@astrale-os/sdk/deployment/verify';
2
+ import { createHash } from 'node:crypto';
3
+ import { WORKER_ARTIFACT_FORMAT, WORKER_ARTIFACT_VERSION } from './artifact.manifest.js';
4
+ import { artifactPath, assetPath, comparePath, WORKER_MANIFEST_PATH } from './artifact.paths.js';
5
+ const decoder = new TextDecoder('utf-8', { fatal: true });
6
+ const REVISION = /^sha256:[0-9a-f]{64}$/u;
7
+ const BINDING = /^[A-Za-z_$][A-Za-z0-9_$]{0,127}$/u;
8
+ const DATE = /^\d{4}-\d{2}-\d{2}$/u;
9
+ const KINDS = new Set(['esm', 'wasm', 'text', 'data']);
10
+ export function loadWorkerArtifact(artifact) {
11
+ if (!isPreparedArtifact(artifact))
12
+ throw new TypeError('PreparedArtifact is not admitted.');
13
+ const manifestFile = requiredFile(artifact, WORKER_MANIFEST_PATH);
14
+ let decoded;
15
+ try {
16
+ decoded = JSON.parse(decoder.decode(manifestFile.bytes()));
17
+ }
18
+ catch (cause) {
19
+ throw new TypeError('Cloudflare Worker artifact manifest is invalid JSON.', { cause });
20
+ }
21
+ const manifest = admitManifest(decoded);
22
+ if (artifact.entrypoint !== manifest.release.entrypoint) {
23
+ throw new TypeError('Cloudflare Worker artifact entrypoint differs from its manifest.');
24
+ }
25
+ const origin = artifact.build.schema.compiled.root.origin;
26
+ const revision = artifact.build.schema.compiled.root.revision;
27
+ if (manifest.origin !== origin || manifest.revision !== revision) {
28
+ throw new TypeError('Cloudflare Worker artifact identity differs from its Build.');
29
+ }
30
+ const claimed = new Set([WORKER_MANIFEST_PATH]);
31
+ const release = executable(artifact, manifest.release, claimed, 'release');
32
+ const gateway = executable(artifact, manifest.gateway, claimed, 'gateway');
33
+ const assets = manifest.assets?.files.map((file) => {
34
+ claim(claimed, file.artifactPath);
35
+ const source = requiredFile(artifact, file.artifactPath);
36
+ const content = source.bytes();
37
+ verifyContent(content, file.digest, file.artifactPath);
38
+ return Object.freeze({
39
+ path: file.path,
40
+ artifactPath: file.artifactPath,
41
+ ...(file.contentType === undefined ? {} : { contentType: file.contentType }),
42
+ bytes: () => new Uint8Array(content),
43
+ });
44
+ });
45
+ return Object.freeze({
46
+ origin,
47
+ revision,
48
+ release,
49
+ gateway,
50
+ runtime: Object.freeze({
51
+ compatibilityDate: manifest.runtime.compatibilityDate,
52
+ compatibilityFlags: Object.freeze([...manifest.runtime.compatibilityFlags]),
53
+ }),
54
+ bindings: Object.freeze({
55
+ vars: Object.freeze({ ...manifest.bindings.vars }),
56
+ services: Object.freeze(manifest.bindings.services.map((binding) => Object.freeze({ ...binding }))),
57
+ requiredSecrets: Object.freeze([...manifest.bindings.requiredSecrets]),
58
+ }),
59
+ ...(manifest.assets === undefined
60
+ ? {}
61
+ : {
62
+ assets: Object.freeze({
63
+ files: Object.freeze(assets),
64
+ notFound: manifest.assets.notFound,
65
+ runWorkerFirst: manifest.assets.runWorkerFirst,
66
+ ...(manifest.assets.htmlHandling === undefined
67
+ ? {}
68
+ : { htmlHandling: manifest.assets.htmlHandling }),
69
+ }),
70
+ }),
71
+ });
72
+ }
73
+ function executable(artifact, manifest, claimed, label) {
74
+ const modules = manifest.modules.map((module) => {
75
+ claim(claimed, module.path);
76
+ const source = requiredFile(artifact, module.path);
77
+ const content = source.bytes();
78
+ verifyContent(content, module.digest, module.path);
79
+ return Object.freeze({
80
+ path: module.path,
81
+ kind: module.kind,
82
+ bytes: () => new Uint8Array(content),
83
+ });
84
+ });
85
+ if (!manifest.modules.some((module) => module.path === manifest.entrypoint)) {
86
+ throw new TypeError(`Cloudflare Worker ${label} entrypoint is not a module.`);
87
+ }
88
+ if (manifest.modules.find((module) => module.path === manifest.entrypoint)?.kind !== 'esm') {
89
+ throw new TypeError(`Cloudflare Worker ${label} entrypoint is not an ES module.`);
90
+ }
91
+ return Object.freeze({ entrypoint: manifest.entrypoint, modules: Object.freeze(modules) });
92
+ }
93
+ function admitManifest(input) {
94
+ const value = record(input, [
95
+ 'format',
96
+ 'version',
97
+ 'origin',
98
+ 'revision',
99
+ 'release',
100
+ 'gateway',
101
+ 'runtime',
102
+ 'bindings',
103
+ 'assets',
104
+ ]);
105
+ if (value.format !== WORKER_ARTIFACT_FORMAT || value.version !== WORKER_ARTIFACT_VERSION) {
106
+ throw new TypeError('Cloudflare Worker artifact format or version is unsupported.');
107
+ }
108
+ if (typeof value.origin !== 'string' ||
109
+ value.origin.length === 0 ||
110
+ !REVISION.test(String(value.revision))) {
111
+ invalid();
112
+ }
113
+ const release = admitExecutable(value.release);
114
+ const gateway = admitExecutable(value.gateway);
115
+ const runtime = record(value.runtime, ['compatibilityDate', 'compatibilityFlags']);
116
+ if (typeof runtime.compatibilityDate !== 'string' || !DATE.test(runtime.compatibilityDate))
117
+ invalid();
118
+ const compatibilityFlags = uniqueStrings(runtime.compatibilityFlags, 'compatibility flag');
119
+ const bindings = record(value.bindings, ['vars', 'services', 'requiredSecrets']);
120
+ const vars = stringRecord(bindings.vars);
121
+ const services = admitServices(bindings.services);
122
+ const requiredSecrets = uniqueStrings(bindings.requiredSecrets, 'secret binding');
123
+ const names = new Set();
124
+ for (const name of [
125
+ ...Object.keys(vars),
126
+ ...services.map((item) => item.name),
127
+ ...requiredSecrets,
128
+ ]) {
129
+ if (!BINDING.test(name) || names.has(name))
130
+ throw new TypeError(`Worker binding ${name} is invalid or duplicated.`);
131
+ names.add(name);
132
+ }
133
+ const assets = value.assets === undefined ? undefined : admitAssets(value.assets);
134
+ return Object.freeze({
135
+ format: WORKER_ARTIFACT_FORMAT,
136
+ version: WORKER_ARTIFACT_VERSION,
137
+ origin: value.origin,
138
+ revision: value.revision,
139
+ release,
140
+ gateway,
141
+ runtime: Object.freeze({
142
+ compatibilityDate: runtime.compatibilityDate,
143
+ compatibilityFlags,
144
+ }),
145
+ bindings: Object.freeze({ vars, services, requiredSecrets }),
146
+ ...(assets === undefined ? {} : { assets }),
147
+ });
148
+ }
149
+ function admitExecutable(input) {
150
+ const value = record(input, ['entrypoint', 'modules']);
151
+ const entrypoint = artifactPath(value.entrypoint, 'Worker entrypoint');
152
+ if (!Array.isArray(value.modules) || value.modules.length === 0)
153
+ invalid();
154
+ const paths = new Set();
155
+ const modules = value.modules.map((input) => {
156
+ const module = record(input, ['path', 'kind', 'digest']);
157
+ const path = artifactPath(module.path, 'Worker module path');
158
+ if (paths.has(path) ||
159
+ !KINDS.has(module.kind) ||
160
+ typeof module.digest !== 'string' ||
161
+ !REVISION.test(module.digest))
162
+ invalid();
163
+ paths.add(path);
164
+ return Object.freeze({ path, kind: module.kind, digest: module.digest });
165
+ });
166
+ canonical(modules.map((module) => module.path));
167
+ return Object.freeze({ entrypoint, modules: Object.freeze(modules) });
168
+ }
169
+ function admitServices(input) {
170
+ if (!Array.isArray(input))
171
+ invalid();
172
+ const names = new Set();
173
+ const services = input.map((item) => {
174
+ const value = record(item, ['name', 'service']);
175
+ if (typeof value.name !== 'string' ||
176
+ typeof value.service !== 'string' ||
177
+ value.service.length === 0 ||
178
+ names.has(value.name))
179
+ invalid();
180
+ names.add(value.name);
181
+ return Object.freeze({ name: value.name, service: value.service });
182
+ });
183
+ canonical(services.map((service) => service.name));
184
+ return Object.freeze(services);
185
+ }
186
+ function admitAssets(input) {
187
+ const value = record(input, ['files', 'notFound', 'runWorkerFirst', 'htmlHandling']);
188
+ if (!Array.isArray(value.files) ||
189
+ !['none', '404-page', 'single-page-application'].includes(String(value.notFound)) ||
190
+ typeof value.runWorkerFirst !== 'boolean' ||
191
+ (value.htmlHandling !== undefined && value.htmlHandling !== 'none'))
192
+ invalid();
193
+ const paths = new Set();
194
+ const files = value.files.map((input) => {
195
+ const file = record(input, ['path', 'artifactPath', 'digest', 'contentType']);
196
+ const path = assetPath(file.path);
197
+ if (paths.has(path))
198
+ invalid();
199
+ paths.add(path);
200
+ if (file.contentType !== undefined &&
201
+ (typeof file.contentType !== 'string' ||
202
+ file.contentType.length === 0 ||
203
+ file.contentType.length > 256))
204
+ invalid();
205
+ if (typeof file.digest !== 'string' || !REVISION.test(file.digest))
206
+ invalid();
207
+ return Object.freeze({
208
+ path,
209
+ artifactPath: artifactPath(file.artifactPath, 'Worker asset artifact path'),
210
+ digest: file.digest,
211
+ ...(file.contentType === undefined ? {} : { contentType: file.contentType }),
212
+ });
213
+ });
214
+ canonical(files.map((file) => file.path));
215
+ return Object.freeze({
216
+ files: Object.freeze(files),
217
+ notFound: value.notFound,
218
+ runWorkerFirst: value.runWorkerFirst,
219
+ ...(value.htmlHandling === undefined ? {} : { htmlHandling: 'none' }),
220
+ });
221
+ }
222
+ function record(input, allowed) {
223
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
224
+ invalid();
225
+ const value = input;
226
+ if (Reflect.ownKeys(value).some((key) => typeof key !== 'string' || !allowed.includes(key)))
227
+ invalid();
228
+ return value;
229
+ }
230
+ function stringRecord(input) {
231
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
232
+ invalid();
233
+ const value = input;
234
+ if (Reflect.ownKeys(value).some((key) => typeof key !== 'string'))
235
+ invalid();
236
+ if (Object.values(value).some((item) => typeof item !== 'string'))
237
+ invalid();
238
+ const entries = Object.entries(value);
239
+ canonical(entries.map(([name]) => name));
240
+ return Object.freeze(Object.fromEntries(entries));
241
+ }
242
+ function uniqueStrings(input, label) {
243
+ if (!Array.isArray(input) || input.some((item) => typeof item !== 'string' || item.length === 0))
244
+ invalid();
245
+ const values = input;
246
+ if (new Set(values).size !== values.length)
247
+ throw new TypeError(`Worker ${label} is duplicated.`);
248
+ canonical(values);
249
+ return Object.freeze([...values]);
250
+ }
251
+ function canonical(values) {
252
+ const sorted = [...values].sort(comparePath);
253
+ if (values.some((value, index) => value !== sorted[index])) {
254
+ throw new TypeError('Cloudflare Worker artifact manifest is not canonical.');
255
+ }
256
+ }
257
+ function claim(claimed, path) {
258
+ if (path === WORKER_MANIFEST_PATH || claimed.has(path)) {
259
+ throw new TypeError(`Cloudflare Worker artifact file ${path} has conflicting roles.`);
260
+ }
261
+ claimed.add(path);
262
+ }
263
+ function requiredFile(artifact, path) {
264
+ const file = artifact.files.find((candidate) => candidate.path === path);
265
+ if (file === undefined)
266
+ throw new TypeError(`Cloudflare Worker artifact is missing ${path}.`);
267
+ return file;
268
+ }
269
+ function verifyContent(content, expected, path) {
270
+ const digest = `sha256:${createHash('sha256').update(content).digest('hex')}`;
271
+ if (digest !== expected) {
272
+ throw new TypeError(`Cloudflare Worker artifact content differs at ${path}.`);
273
+ }
274
+ }
275
+ function invalid() {
276
+ throw new TypeError('Cloudflare Worker artifact manifest is invalid.');
277
+ }
@@ -0,0 +1,47 @@
1
+ import type { WorkerModuleKind } from './artifact.js';
2
+ import { WORKER_MANIFEST_PATH } from './artifact.paths.js';
3
+ export declare const WORKER_ARTIFACT_FORMAT = "astrale.cloudflare.worker";
4
+ export declare const WORKER_ARTIFACT_VERSION = 1;
5
+ export interface WorkerArtifactManifestV1 {
6
+ readonly format: typeof WORKER_ARTIFACT_FORMAT;
7
+ readonly version: typeof WORKER_ARTIFACT_VERSION;
8
+ readonly origin: string;
9
+ readonly revision: string;
10
+ readonly release: ManifestExecutable;
11
+ readonly gateway: ManifestExecutable;
12
+ readonly runtime: {
13
+ readonly compatibilityDate: string;
14
+ readonly compatibilityFlags: readonly string[];
15
+ };
16
+ readonly bindings: {
17
+ readonly vars: Readonly<Record<string, string>>;
18
+ readonly services: readonly {
19
+ readonly name: string;
20
+ readonly service: string;
21
+ }[];
22
+ readonly requiredSecrets: readonly string[];
23
+ };
24
+ readonly assets?: {
25
+ readonly files: readonly {
26
+ readonly path: string;
27
+ readonly artifactPath: string;
28
+ readonly digest: string;
29
+ readonly contentType?: string;
30
+ }[];
31
+ readonly notFound: 'none' | '404-page' | 'single-page-application';
32
+ readonly runWorkerFirst: boolean;
33
+ readonly htmlHandling?: 'none';
34
+ };
35
+ }
36
+ interface ManifestExecutable {
37
+ readonly entrypoint: string;
38
+ readonly modules: readonly {
39
+ readonly path: string;
40
+ readonly kind: WorkerModuleKind;
41
+ readonly digest: string;
42
+ }[];
43
+ }
44
+ export declare function encodeWorkerArtifactManifest(input: Omit<WorkerArtifactManifestV1, 'format' | 'version'>): Uint8Array;
45
+ export declare function workerContentDigest(input: Uint8Array): string;
46
+ export declare function inferMediaType(path: string): string | undefined;
47
+ export { WORKER_MANIFEST_PATH };
@@ -0,0 +1,87 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { artifactPath, assetPath, comparePath, WORKER_MANIFEST_PATH } from './artifact.paths.js';
3
+ export const WORKER_ARTIFACT_FORMAT = 'astrale.cloudflare.worker';
4
+ export const WORKER_ARTIFACT_VERSION = 1;
5
+ const encoder = new TextEncoder();
6
+ export function encodeWorkerArtifactManifest(input) {
7
+ const manifest = {
8
+ format: WORKER_ARTIFACT_FORMAT,
9
+ version: WORKER_ARTIFACT_VERSION,
10
+ origin: input.origin,
11
+ revision: input.revision,
12
+ release: canonicalExecutable(input.release),
13
+ gateway: canonicalExecutable(input.gateway),
14
+ runtime: {
15
+ compatibilityDate: input.runtime.compatibilityDate,
16
+ compatibilityFlags: Object.freeze([...input.runtime.compatibilityFlags].sort()),
17
+ },
18
+ bindings: {
19
+ vars: Object.fromEntries(Object.entries(input.bindings.vars).sort(([a], [b]) => comparePath(a, b))),
20
+ services: Object.freeze([...input.bindings.services].sort((a, b) => comparePath(a.name, b.name))),
21
+ requiredSecrets: Object.freeze([...input.bindings.requiredSecrets].sort(comparePath)),
22
+ },
23
+ ...(input.assets === undefined
24
+ ? {}
25
+ : {
26
+ assets: {
27
+ files: Object.freeze([...input.assets.files]
28
+ .map((file) => ({
29
+ path: assetPath(file.path),
30
+ artifactPath: artifactPath(file.artifactPath, 'Worker asset artifact path'),
31
+ digest: contentDigest(file.digest),
32
+ ...(file.contentType === undefined ? {} : { contentType: file.contentType }),
33
+ }))
34
+ .sort((a, b) => comparePath(a.path, b.path))),
35
+ notFound: input.assets.notFound,
36
+ runWorkerFirst: input.assets.runWorkerFirst,
37
+ ...(input.assets.htmlHandling === undefined
38
+ ? {}
39
+ : { htmlHandling: input.assets.htmlHandling }),
40
+ },
41
+ }),
42
+ };
43
+ return encoder.encode(`${JSON.stringify(manifest)}\n`);
44
+ }
45
+ function canonicalExecutable(input) {
46
+ const modules = [...input.modules]
47
+ .map((module) => ({
48
+ path: artifactPath(module.path, 'Worker module path'),
49
+ kind: module.kind,
50
+ digest: contentDigest(module.digest),
51
+ }))
52
+ .sort((a, b) => comparePath(a.path, b.path));
53
+ return {
54
+ entrypoint: artifactPath(input.entrypoint, 'Worker entrypoint'),
55
+ modules: Object.freeze(modules),
56
+ };
57
+ }
58
+ export function workerContentDigest(input) {
59
+ return `sha256:${createHash('sha256').update(input).digest('hex')}`;
60
+ }
61
+ function contentDigest(input) {
62
+ if (!/^sha256:[0-9a-f]{64}$/u.test(input)) {
63
+ throw new TypeError('Worker artifact content digest is invalid.');
64
+ }
65
+ return input;
66
+ }
67
+ export function inferMediaType(path) {
68
+ const extension = path.split('.').at(-1)?.toLowerCase();
69
+ return extension === undefined ? undefined : MEDIA_TYPES[extension];
70
+ }
71
+ const MEDIA_TYPES = Object.freeze({
72
+ html: 'text/html; charset=utf-8',
73
+ js: 'text/javascript; charset=utf-8',
74
+ mjs: 'text/javascript; charset=utf-8',
75
+ css: 'text/css; charset=utf-8',
76
+ json: 'application/json; charset=utf-8',
77
+ svg: 'image/svg+xml',
78
+ png: 'image/png',
79
+ jpg: 'image/jpeg',
80
+ jpeg: 'image/jpeg',
81
+ webp: 'image/webp',
82
+ ico: 'image/x-icon',
83
+ woff: 'font/woff',
84
+ woff2: 'font/woff2',
85
+ wasm: 'application/wasm',
86
+ });
87
+ export { WORKER_MANIFEST_PATH };
@@ -0,0 +1,4 @@
1
+ export declare const WORKER_MANIFEST_PATH = "worker/manifest.json";
2
+ export declare function artifactPath(input: unknown, label: string): string;
3
+ export declare function assetPath(input: unknown): string;
4
+ export declare function comparePath(left: string, right: string): number;
@@ -0,0 +1,25 @@
1
+ export const WORKER_MANIFEST_PATH = 'worker/manifest.json';
2
+ export function artifactPath(input, label) {
3
+ if (typeof input !== 'string' ||
4
+ input.length === 0 ||
5
+ input.startsWith('/') ||
6
+ input.includes('\\') ||
7
+ input.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')) {
8
+ throw new TypeError(`${label} must be a canonical relative POSIX path.`);
9
+ }
10
+ return input;
11
+ }
12
+ export function assetPath(input) {
13
+ if (typeof input !== 'string' ||
14
+ !input.startsWith('/') ||
15
+ input.length > 1024 ||
16
+ input.includes('\\') ||
17
+ Array.from(input).some((character) => character.codePointAt(0) <= 0x1f) ||
18
+ input.split('/').some((segment) => segment === '.' || segment === '..')) {
19
+ throw new TypeError('Worker asset path is invalid.');
20
+ }
21
+ return input;
22
+ }
23
+ export function comparePath(left, right) {
24
+ return left < right ? -1 : left > right ? 1 : 0;
25
+ }
@@ -0,0 +1,2 @@
1
+ /** Generate the isolate-startup entry that realizes the heavy Service on first request. */
2
+ export declare function generateExecutionEntry(serviceSpecifier: string, workerSpecifier?: string): string;
@@ -0,0 +1,28 @@
1
+ /** Generate the isolate-startup entry that realizes the heavy Service on first request. */
2
+ export function generateExecutionEntry(serviceSpecifier, workerSpecifier = '@astrale-os/adapter-cloudflare/worker') {
3
+ return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
4
+ import type { CloudflareWorker } from ${JSON.stringify(workerSpecifier)}
5
+
6
+ const SERVICE_SPECIFIER = ${JSON.stringify(serviceSpecifier)}
7
+ let servicePromise: Promise<CloudflareWorker<CloudflareEnv>> | undefined
8
+
9
+ export default {
10
+ async fetch(request: Request, environment: CloudflareEnv, context: ExecutionContext) {
11
+ return (await service()).fetch(request, environment, context)
12
+ },
13
+ }
14
+
15
+ function service(): Promise<CloudflareWorker<CloudflareEnv>> {
16
+ return (servicePromise ??= import(SERVICE_SPECIFIER).then((loaded: unknown) => {
17
+ if (loaded === null || typeof loaded !== 'object') invalid()
18
+ const worker = Reflect.get(loaded, 'default') as unknown
19
+ if (worker === null || typeof worker !== 'object' || typeof Reflect.get(worker, 'fetch') !== 'function') invalid()
20
+ return worker as CloudflareWorker<CloudflareEnv>
21
+ }))
22
+ }
23
+
24
+ function invalid(): never {
25
+ throw new TypeError('Cloudflare execution Service module is invalid.')
26
+ }
27
+ `;
28
+ }
@@ -0,0 +1,15 @@
1
+ import type { BuildMaterial } from '@astrale-os/sdk/deployment/build';
2
+ export interface ExecutionCodegenOptions {
3
+ readonly origin: string;
4
+ readonly runtimeSpecifier: string;
5
+ readonly workerSpecifier?: string;
6
+ readonly material: BuildMaterial;
7
+ readonly maximumRequestBytes?: number;
8
+ readonly providerAssignedWorker?: string;
9
+ readonly router?: {
10
+ readonly binding: string;
11
+ readonly hostSuffix: string;
12
+ readonly minLabels: number;
13
+ };
14
+ }
15
+ export declare function generateExecutionService(options: ExecutionCodegenOptions): string;
@@ -0,0 +1,46 @@
1
+ export function generateExecutionService(options) {
2
+ const workerSpecifier = options.workerSpecifier ?? '@astrale-os/adapter-cloudflare/worker';
3
+ const router = options.router;
4
+ const routerPredicate = router === undefined
5
+ ? ''
6
+ : `
7
+ function isInstanceHost(host: string): boolean {
8
+ return host.endsWith(${JSON.stringify(router.hostSuffix)}) && host.split('.').length >= ${router.minLabels}
9
+ }
10
+ `;
11
+ const routeSubrequest = router === undefined
12
+ ? ''
13
+ : `
14
+ routeSubrequest: (url, env) => {
15
+ const labels = url.hostname.split('.')
16
+ if (env.DISPATCHER && labels[1] === 'svc') return env.DISPATCHER.get(labels[0]!)
17
+ if (env.${router.binding} && isInstanceHost(url.hostname)) return env.${router.binding}
18
+ return null
19
+ },`;
20
+ return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
21
+ // Domain origin: ${options.origin}
22
+ import type { BuildMaterial } from '@astrale-os/sdk/deployment/build'
23
+ import type { CloudflareAdapterBindings } from ${JSON.stringify(workerSpecifier)}
24
+
25
+ import { cloudflareWorkerEntry } from ${JSON.stringify(workerSpecifier)}
26
+ import { decodeSigningIdentity, runtimeBindings } from ${JSON.stringify(workerSpecifier)}
27
+
28
+ type WorkerEnv = CloudflareEnv & CloudflareAdapterBindings & {
29
+ readonly ASTRALE_SIGNING_IDENTITY: string
30
+ }
31
+ type RuntimeEnv = Omit<WorkerEnv, 'ASTRALE_SIGNING_IDENTITY'>
32
+ ${routerPredicate}
33
+ export default cloudflareWorkerEntry<WorkerEnv, RuntimeEnv>()({
34
+ load: async () => ({
35
+ runtime: (await import(${JSON.stringify(options.runtimeSpecifier)})).default,
36
+ material: ${JSON.stringify(options.material)} as const satisfies BuildMaterial,
37
+ }),
38
+ privateKey: (env) => decodeSigningIdentity(env.ASTRALE_SIGNING_IDENTITY),
39
+ resolveEnvironment: runtimeBindings,
40
+ resolveUrl: (env, requestOrigin) => env.WORKER_URL ?? env.IDENTITY_ISS ?? requestOrigin,
41
+ ${options.maximumRequestBytes === undefined ? '' : `maximumRequestBytes: ${options.maximumRequestBytes},`}
42
+ ${options.providerAssignedWorker === undefined ? '' : `providerAssignedWorker: ${JSON.stringify(options.providerAssignedWorker)},`}
43
+ selfBinding: (env) => env.SELF,${routeSubrequest}
44
+ })
45
+ `;
46
+ }
@@ -0,0 +1,18 @@
1
+ import type { PrepareContext } from '@astrale-os/sdk/deployment/adapter';
2
+ import type { CloudflareParams } from '../../params.js';
3
+ import type { resolveRouter } from '../configuration.js';
4
+ export declare const RELEASE_ENTRY = "bundle/index.mjs";
5
+ export declare const RELEASE_SERVICE = "bundle/service.mjs";
6
+ export declare function prepareExecution(input: {
7
+ readonly params: CloudflareParams;
8
+ readonly context: PrepareContext;
9
+ readonly directory: string;
10
+ readonly name: string;
11
+ readonly direct: boolean;
12
+ readonly configuredIssuer?: string;
13
+ readonly router: ReturnType<typeof resolveRouter>;
14
+ }): Promise<{
15
+ readonly entry: Uint8Array;
16
+ readonly service: Uint8Array;
17
+ }>;
18
+ export declare function runtimeVars(params: CloudflareParams, configuredIssuer: string | undefined): Record<string, string>;
@@ -0,0 +1,108 @@
1
+ import { material } from '@astrale-os/sdk/deployment/build';
2
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, join, relative, resolve } from 'node:path';
4
+ import { SIGNING_IDENTITY_BINDING } from '../../identity/binding.js';
5
+ import { admitWorkerBundle } from '../bundle/admit.js';
6
+ import { generateExecutionEntry, generateExecutionService } from '../codegen/execution.js';
7
+ import { generateWranglerConfig } from '../codegen/wrangler.js';
8
+ import { workerRequestLimit } from '../configuration.js';
9
+ import { qualifyWorkerBundle } from '../qualification.js';
10
+ import { runWranglerBundle } from '../wrangler-cli.js';
11
+ import { workerModuleSpecifier } from './specifier.js';
12
+ export const RELEASE_ENTRY = 'bundle/index.mjs';
13
+ export const RELEASE_SERVICE = 'bundle/service.mjs';
14
+ export async function prepareExecution(input) {
15
+ const runtimePath = resolve(input.context.projectDir, input.context.runtime.reference.path);
16
+ const runtimeSpecifier = moduleSpecifier(relative(input.directory, runtimePath));
17
+ const workerSpecifier = workerModuleSpecifier();
18
+ const maximumRequestBytes = workerRequestLimit(input.params.maximumRequestBytes);
19
+ const serviceSource = generateExecutionService({
20
+ origin: input.context.build.schema.compiled.root.origin,
21
+ runtimeSpecifier,
22
+ workerSpecifier,
23
+ material: material(input.context.build),
24
+ ...(maximumRequestBytes === undefined ? {} : { maximumRequestBytes }),
25
+ ...(input.configuredIssuer === undefined && input.direct
26
+ ? { providerAssignedWorker: input.name }
27
+ : {}),
28
+ ...(input.router === undefined
29
+ ? {}
30
+ : {
31
+ router: {
32
+ binding: input.router.binding,
33
+ hostSuffix: input.router.hostSuffix,
34
+ minLabels: input.router.minLabels,
35
+ },
36
+ }),
37
+ });
38
+ const serviceSourcePath = join(input.directory, 'service.gen.ts');
39
+ await writeFile(serviceSourcePath, serviceSource);
40
+ const serviceConfigPath = join(input.directory, 'wrangler.service.prepare.jsonc');
41
+ await writeFile(serviceConfigPath, generateWranglerConfig({
42
+ workerName: input.name,
43
+ main: './service.gen.ts',
44
+ vars: runtimeVars(input.params, input.configuredIssuer),
45
+ secretBindings: [SIGNING_IDENTITY_BINDING],
46
+ ...(input.router === undefined
47
+ ? {}
48
+ : { router: { binding: input.router.binding, service: input.router.service } }),
49
+ ...(input.params.wrangler === undefined ? {} : { wrangler: input.params.wrangler }),
50
+ selfBinding: false,
51
+ }));
52
+ const serviceOutput = join(input.directory, 'dist-service');
53
+ await rm(serviceOutput, { recursive: true, force: true });
54
+ const emittedService = await runWranglerBundle({
55
+ projectDir: input.context.projectDir,
56
+ configPath: serviceConfigPath,
57
+ outDir: serviceOutput,
58
+ });
59
+ await qualifyWorkerBundle({
60
+ metafilePath: emittedService.metafilePath,
61
+ inputRoot: dirname(serviceConfigPath),
62
+ });
63
+ await admitWorkerBundle(emittedService.bundlePath);
64
+ const service = new Uint8Array(await readFile(emittedService.bundlePath));
65
+ await mkdir(dirname(join(input.directory, RELEASE_SERVICE)), { recursive: true });
66
+ await writeFile(join(input.directory, RELEASE_SERVICE), service);
67
+ const sourcePath = join(input.directory, 'worker.gen.ts');
68
+ await writeFile(sourcePath, generateExecutionEntry('./service.mjs', workerSpecifier));
69
+ const configPath = join(input.directory, 'wrangler.prepare.jsonc');
70
+ await writeFile(configPath, generateWranglerConfig({
71
+ workerName: input.name,
72
+ ...(input.params.route === undefined ? {} : { route: input.params.route }),
73
+ vars: runtimeVars(input.params, input.configuredIssuer),
74
+ secretBindings: [SIGNING_IDENTITY_BINDING],
75
+ modules: { baseDir: '.', rules: [{ type: 'ESModule', globs: [RELEASE_SERVICE] }] },
76
+ ...(input.router === undefined
77
+ ? {}
78
+ : { router: { binding: input.router.binding, service: input.router.service } }),
79
+ ...(input.params.wrangler === undefined ? {} : { wrangler: input.params.wrangler }),
80
+ selfBinding: false,
81
+ }));
82
+ const output = join(input.directory, 'dist-prepare');
83
+ await rm(output, { recursive: true, force: true });
84
+ const emitted = await runWranglerBundle({
85
+ projectDir: input.context.projectDir,
86
+ configPath,
87
+ outDir: output,
88
+ });
89
+ await qualifyWorkerBundle({
90
+ metafilePath: emitted.metafilePath,
91
+ inputRoot: dirname(configPath),
92
+ });
93
+ await admitWorkerBundle(emitted.bundlePath);
94
+ return Object.freeze({
95
+ entry: new Uint8Array(await readFile(emitted.bundlePath)),
96
+ service,
97
+ });
98
+ }
99
+ export function runtimeVars(params, configuredIssuer) {
100
+ return {
101
+ ...params.vars,
102
+ ...(configuredIssuer === undefined ? {} : { WORKER_URL: configuredIssuer }),
103
+ };
104
+ }
105
+ function moduleSpecifier(input) {
106
+ const normalized = input.split('\\').join('/');
107
+ return normalized.startsWith('.') ? normalized : `./${normalized}`;
108
+ }
@@ -0,0 +1,2 @@
1
+ export { loadWorkerArtifact } from './artifact.load.js';
2
+ export type { WorkerArtifact } from './artifact.js';
@@ -0,0 +1 @@
1
+ export { loadWorkerArtifact } from './artifact.load.js';