@astrale-os/adapter-cloudflare 0.5.0-beta.7 → 0.5.0-beta.71

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 (174) hide show
  1. package/CHANGELOG.md +887 -0
  2. package/README.md +25 -0
  3. package/dist/adapter.d.ts +17 -0
  4. package/dist/adapter.js +177 -0
  5. package/dist/build/artifact.d.ts +7 -0
  6. package/dist/build/artifact.js +163 -0
  7. package/dist/build/bundle/admit.d.ts +1 -0
  8. package/dist/build/bundle/admit.js +9 -0
  9. package/dist/build/closure.d.ts +2 -0
  10. package/dist/build/closure.js +22 -0
  11. package/dist/build/codegen/execution.d.ts +3 -0
  12. package/dist/build/codegen/execution.js +2 -0
  13. package/dist/build/codegen/merge.d.ts +0 -1
  14. package/dist/build/codegen/merge.js +0 -1
  15. package/dist/build/codegen/wrangler.d.ts +13 -1
  16. package/dist/build/codegen/wrangler.js +32 -17
  17. package/dist/build/configuration/addressing.d.ts +2 -0
  18. package/dist/build/configuration/addressing.js +11 -0
  19. package/dist/build/configuration/invocation-timeout.d.ts +2 -0
  20. package/dist/build/configuration/invocation-timeout.js +9 -0
  21. package/dist/build/configuration/request-limit.d.ts +2 -0
  22. package/dist/build/configuration/request-limit.js +9 -0
  23. package/dist/build/configuration/router.d.ts +7 -0
  24. package/dist/build/configuration/router.js +11 -0
  25. package/dist/build/configuration/worker-name.d.ts +2 -0
  26. package/dist/build/configuration/worker-name.js +18 -0
  27. package/dist/build/configuration.d.ts +5 -0
  28. package/dist/build/configuration.js +5 -0
  29. package/dist/build/direct.d.ts +4 -0
  30. package/dist/build/direct.js +93 -0
  31. package/dist/build/frontend/routes.d.ts +2 -0
  32. package/dist/build/frontend/routes.js +11 -0
  33. package/dist/build/frontend-v1.d.ts +16 -0
  34. package/dist/build/frontend-v1.js +133 -0
  35. package/dist/build/index.d.ts +10 -16
  36. package/dist/build/index.js +9 -16
  37. package/dist/build/materialize.d.ts +4 -0
  38. package/dist/build/materialize.js +10 -0
  39. package/dist/build/metafile.d.ts +5 -0
  40. package/dist/build/metafile.js +14 -0
  41. package/dist/build/parse-output.d.ts +0 -1
  42. package/dist/build/parse-output.js +0 -1
  43. package/dist/build/qualification.d.ts +5 -0
  44. package/dist/build/qualification.js +7 -0
  45. package/dist/build/roots.d.ts +2 -0
  46. package/dist/build/roots.js +34 -0
  47. package/dist/build/worker/artifact.d.ts +40 -0
  48. package/dist/build/worker/artifact.js +1 -0
  49. package/dist/build/worker/artifact.load.d.ts +3 -0
  50. package/dist/build/worker/artifact.load.js +308 -0
  51. package/dist/build/worker/artifact.manifest.d.ts +48 -0
  52. package/dist/build/worker/artifact.manifest.js +87 -0
  53. package/dist/build/worker/artifact.paths.d.ts +4 -0
  54. package/dist/build/worker/artifact.paths.js +25 -0
  55. package/dist/build/worker/codegen/entry.d.ts +2 -0
  56. package/dist/build/worker/codegen/entry.js +28 -0
  57. package/dist/build/worker/codegen/service.d.ts +16 -0
  58. package/dist/build/worker/codegen/service.js +52 -0
  59. package/dist/build/worker/execution.prepare.d.ts +18 -0
  60. package/dist/build/worker/execution.prepare.js +109 -0
  61. package/dist/build/worker/index.d.ts +2 -0
  62. package/dist/build/worker/index.js +1 -0
  63. package/dist/build/worker/modules.d.ts +11 -0
  64. package/dist/build/worker/modules.js +26 -0
  65. package/dist/build/worker/runtime.d.ts +10 -0
  66. package/dist/build/worker/runtime.js +35 -0
  67. package/dist/build/worker/specifier.d.ts +1 -0
  68. package/dist/build/worker/specifier.js +5 -0
  69. package/dist/build/wrangler/bundle.d.ts +10 -0
  70. package/dist/build/wrangler/bundle.js +29 -0
  71. package/dist/build/wrangler/deploy.d.ts +24 -0
  72. package/dist/build/wrangler/deploy.js +44 -0
  73. package/dist/build/wrangler/development.d.ts +17 -0
  74. package/dist/build/wrangler/development.js +136 -0
  75. package/dist/build/wrangler/process.d.ts +17 -0
  76. package/dist/build/wrangler/process.js +115 -0
  77. package/dist/build/wrangler/secrets.d.ts +6 -0
  78. package/dist/build/wrangler/secrets.js +58 -0
  79. package/dist/build/wrangler/status.d.ts +14 -0
  80. package/dist/build/wrangler/status.js +56 -0
  81. package/dist/build/wrangler/type-generation.d.ts +6 -0
  82. package/dist/build/wrangler/type-generation.js +19 -0
  83. package/dist/build/wrangler-cli.d.ts +6 -86
  84. package/dist/build/wrangler-cli.js +6 -275
  85. package/dist/cloudflare.d.ts +1 -34
  86. package/dist/cloudflare.js +1 -205
  87. package/dist/deploy/deploy.d.ts +15 -0
  88. package/dist/deploy/deploy.js +204 -0
  89. package/dist/deploy/index.d.ts +1 -0
  90. package/dist/deploy/index.js +1 -0
  91. package/dist/identity/binding.d.ts +2 -0
  92. package/dist/identity/binding.js +2 -0
  93. package/dist/identity/material.d.ts +7 -0
  94. package/dist/identity/material.js +42 -0
  95. package/dist/identity/secret.d.ts +3 -0
  96. package/dist/identity/secret.js +7 -0
  97. package/dist/identity/source.d.ts +26 -0
  98. package/dist/identity/source.js +98 -0
  99. package/dist/index.d.ts +3 -4
  100. package/dist/index.js +3 -4
  101. package/dist/params.d.ts +30 -5
  102. package/dist/params.js +0 -1
  103. package/dist/release/deployment.d.ts +44 -0
  104. package/dist/release/deployment.js +141 -0
  105. package/dist/release/index.d.ts +2 -0
  106. package/dist/release/index.js +2 -0
  107. package/dist/release/staged-worker-name.d.ts +3 -0
  108. package/dist/release/staged-worker-name.js +10 -0
  109. package/dist/worker/entry.d.ts +43 -0
  110. package/dist/worker/entry.js +181 -0
  111. package/dist/worker/fetch.d.ts +0 -1
  112. package/dist/worker/fetch.js +0 -1
  113. package/dist/worker/identity.d.ts +7 -0
  114. package/dist/worker/identity.js +23 -0
  115. package/dist/worker/index.d.ts +2 -61
  116. package/dist/worker/index.js +2 -216
  117. package/package.json +30 -19
  118. package/template/astrale.config.ts +19 -11
  119. package/dist/build/codegen/identity.d.ts +0 -10
  120. package/dist/build/codegen/identity.d.ts.map +0 -1
  121. package/dist/build/codegen/identity.js +0 -32
  122. package/dist/build/codegen/identity.js.map +0 -1
  123. package/dist/build/codegen/merge.d.ts.map +0 -1
  124. package/dist/build/codegen/merge.js.map +0 -1
  125. package/dist/build/codegen/worker.d.ts +0 -12
  126. package/dist/build/codegen/worker.d.ts.map +0 -1
  127. package/dist/build/codegen/worker.js +0 -40
  128. package/dist/build/codegen/worker.js.map +0 -1
  129. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  130. package/dist/build/codegen/wrangler.js.map +0 -1
  131. package/dist/build/frontend.d.ts +0 -26
  132. package/dist/build/frontend.d.ts.map +0 -1
  133. package/dist/build/frontend.js +0 -263
  134. package/dist/build/frontend.js.map +0 -1
  135. package/dist/build/index.d.ts.map +0 -1
  136. package/dist/build/index.js.map +0 -1
  137. package/dist/build/parse-output.d.ts.map +0 -1
  138. package/dist/build/parse-output.js.map +0 -1
  139. package/dist/build/prepare.d.ts +0 -18
  140. package/dist/build/prepare.d.ts.map +0 -1
  141. package/dist/build/prepare.js +0 -117
  142. package/dist/build/prepare.js.map +0 -1
  143. package/dist/build/worker.d.ts +0 -15
  144. package/dist/build/worker.d.ts.map +0 -1
  145. package/dist/build/worker.js +0 -21
  146. package/dist/build/worker.js.map +0 -1
  147. package/dist/build/wrangler-cli.d.ts.map +0 -1
  148. package/dist/build/wrangler-cli.js.map +0 -1
  149. package/dist/cloudflare.d.ts.map +0 -1
  150. package/dist/cloudflare.js.map +0 -1
  151. package/dist/index.d.ts.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/params.d.ts.map +0 -1
  154. package/dist/params.js.map +0 -1
  155. package/dist/worker/fetch.d.ts.map +0 -1
  156. package/dist/worker/fetch.js.map +0 -1
  157. package/dist/worker/index.d.ts.map +0 -1
  158. package/dist/worker/index.js.map +0 -1
  159. package/src/build/codegen/identity.ts +0 -40
  160. package/src/build/codegen/merge.ts +0 -92
  161. package/src/build/codegen/worker.ts +0 -53
  162. package/src/build/codegen/wrangler.ts +0 -127
  163. package/src/build/frontend.ts +0 -324
  164. package/src/build/index.ts +0 -29
  165. package/src/build/parse-output.ts +0 -60
  166. package/src/build/prepare.ts +0 -161
  167. package/src/build/worker.ts +0 -37
  168. package/src/build/wrangler-cli.ts +0 -362
  169. package/src/cloudflare.ts +0 -251
  170. package/src/index.ts +0 -21
  171. package/src/params.ts +0 -92
  172. package/src/worker/fetch.ts +0 -46
  173. package/src/worker/index.ts +0 -329
  174. package/src/worker/tsconfig.json +0 -10
@@ -0,0 +1,308 @@
1
+ import { isPreparedArtifact } from '@astrale-os/sdk/deployment/verify';
2
+ import { createHash } from 'node:crypto';
3
+ import { workerDeployment } from '../../release/deployment.js';
4
+ import { WORKER_ARTIFACT_FORMAT, WORKER_ARTIFACT_VERSION } from './artifact.manifest.js';
5
+ import { artifactPath, assetPath, comparePath, WORKER_MANIFEST_PATH } from './artifact.paths.js';
6
+ const decoder = new TextDecoder('utf-8', { fatal: true });
7
+ const REVISION = /^sha256:[0-9a-f]{64}$/u;
8
+ const BINDING = /^[A-Za-z_$][A-Za-z0-9_$]{0,127}$/u;
9
+ const DATE = /^\d{4}-\d{2}-\d{2}$/u;
10
+ const KINDS = new Set(['esm', 'wasm', 'text', 'data']);
11
+ export function loadWorkerArtifact(artifact) {
12
+ if (!isPreparedArtifact(artifact))
13
+ throw new TypeError('PreparedArtifact is not admitted.');
14
+ const manifestFile = requiredFile(artifact, WORKER_MANIFEST_PATH);
15
+ let decoded;
16
+ try {
17
+ decoded = JSON.parse(decoder.decode(manifestFile.bytes()));
18
+ }
19
+ catch (cause) {
20
+ throw new TypeError('Cloudflare Worker artifact manifest is invalid JSON.', { cause });
21
+ }
22
+ const manifest = admitManifest(decoded);
23
+ if (artifact.entrypoint !== manifest.release.entrypoint) {
24
+ throw new TypeError('Cloudflare Worker artifact entrypoint differs from its manifest.');
25
+ }
26
+ const origin = artifact.build.schema.compiled.root.origin;
27
+ const revision = artifact.build.schema.compiled.root.revision;
28
+ if (manifest.origin !== origin || manifest.revision !== revision) {
29
+ throw new TypeError('Cloudflare Worker artifact identity differs from its Build.');
30
+ }
31
+ const claimed = new Set([WORKER_MANIFEST_PATH]);
32
+ const release = executable(artifact, manifest.release, claimed, 'release');
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
+ const deployment = workerDeployment({
46
+ release: {
47
+ entrypoint: release.entrypoint,
48
+ modules: release.modules.map((module) => ({
49
+ path: module.path,
50
+ kind: module.kind,
51
+ content: module.bytes(),
52
+ })),
53
+ },
54
+ runtime: manifest.runtime,
55
+ bindings: manifest.bindings,
56
+ ...(manifest.assets === undefined
57
+ ? {}
58
+ : {
59
+ assets: {
60
+ files: assets.map((file) => ({
61
+ path: file.path,
62
+ content: file.bytes(),
63
+ ...(file.contentType === undefined ? {} : { contentType: file.contentType }),
64
+ })),
65
+ notFound: manifest.assets.notFound,
66
+ runWorkerFirst: manifest.assets.runWorkerFirst,
67
+ ...(manifest.assets.htmlHandling === undefined
68
+ ? {}
69
+ : { htmlHandling: manifest.assets.htmlHandling }),
70
+ },
71
+ }),
72
+ });
73
+ if (deployment !== manifest.deployment) {
74
+ throw new TypeError('Cloudflare Worker deployment identity differs from its material.');
75
+ }
76
+ return Object.freeze({
77
+ origin,
78
+ revision,
79
+ deployment,
80
+ release,
81
+ runtime: Object.freeze({
82
+ compatibilityDate: manifest.runtime.compatibilityDate,
83
+ compatibilityFlags: Object.freeze([...manifest.runtime.compatibilityFlags]),
84
+ }),
85
+ bindings: Object.freeze({
86
+ vars: Object.freeze({ ...manifest.bindings.vars }),
87
+ services: Object.freeze(manifest.bindings.services.map((binding) => Object.freeze({ ...binding }))),
88
+ requiredSecrets: Object.freeze([...manifest.bindings.requiredSecrets]),
89
+ }),
90
+ ...(manifest.assets === undefined
91
+ ? {}
92
+ : {
93
+ assets: Object.freeze({
94
+ files: Object.freeze(assets),
95
+ notFound: manifest.assets.notFound,
96
+ runWorkerFirst: manifest.assets.runWorkerFirst,
97
+ ...(manifest.assets.htmlHandling === undefined
98
+ ? {}
99
+ : { htmlHandling: manifest.assets.htmlHandling }),
100
+ }),
101
+ }),
102
+ });
103
+ }
104
+ function executable(artifact, manifest, claimed, label) {
105
+ const modules = manifest.modules.map((module) => {
106
+ claim(claimed, module.path);
107
+ const source = requiredFile(artifact, module.path);
108
+ const content = source.bytes();
109
+ verifyContent(content, module.digest, module.path);
110
+ return Object.freeze({
111
+ path: module.path,
112
+ kind: module.kind,
113
+ bytes: () => new Uint8Array(content),
114
+ });
115
+ });
116
+ if (!manifest.modules.some((module) => module.path === manifest.entrypoint)) {
117
+ throw new TypeError(`Cloudflare Worker ${label} entrypoint is not a module.`);
118
+ }
119
+ if (manifest.modules.find((module) => module.path === manifest.entrypoint)?.kind !== 'esm') {
120
+ throw new TypeError(`Cloudflare Worker ${label} entrypoint is not an ES module.`);
121
+ }
122
+ return Object.freeze({ entrypoint: manifest.entrypoint, modules: Object.freeze(modules) });
123
+ }
124
+ function admitManifest(input) {
125
+ const value = record(input, [
126
+ 'format',
127
+ 'version',
128
+ 'origin',
129
+ 'revision',
130
+ 'deployment',
131
+ 'release',
132
+ 'runtime',
133
+ 'bindings',
134
+ 'assets',
135
+ ]);
136
+ if (value.format !== WORKER_ARTIFACT_FORMAT || value.version !== WORKER_ARTIFACT_VERSION) {
137
+ throw new TypeError('Cloudflare Worker artifact format or version is unsupported.');
138
+ }
139
+ if (typeof value.origin !== 'string' ||
140
+ value.origin.length === 0 ||
141
+ !REVISION.test(String(value.revision)) ||
142
+ !REVISION.test(String(value.deployment))) {
143
+ invalid();
144
+ }
145
+ const release = admitExecutable(value.release);
146
+ const runtime = record(value.runtime, ['compatibilityDate', 'compatibilityFlags']);
147
+ if (typeof runtime.compatibilityDate !== 'string' || !DATE.test(runtime.compatibilityDate))
148
+ invalid();
149
+ const compatibilityFlags = uniqueStrings(runtime.compatibilityFlags, 'compatibility flag');
150
+ const bindings = record(value.bindings, ['vars', 'services', 'requiredSecrets']);
151
+ const vars = stringRecord(bindings.vars);
152
+ const services = admitServices(bindings.services);
153
+ const requiredSecrets = uniqueStrings(bindings.requiredSecrets, 'secret binding');
154
+ const names = new Set();
155
+ for (const name of [
156
+ ...Object.keys(vars),
157
+ ...services.map((item) => item.name),
158
+ ...requiredSecrets,
159
+ ]) {
160
+ if (!BINDING.test(name) || names.has(name))
161
+ throw new TypeError(`Worker binding ${name} is invalid or duplicated.`);
162
+ names.add(name);
163
+ }
164
+ const assets = value.assets === undefined ? undefined : admitAssets(value.assets);
165
+ return Object.freeze({
166
+ format: WORKER_ARTIFACT_FORMAT,
167
+ version: WORKER_ARTIFACT_VERSION,
168
+ origin: value.origin,
169
+ revision: value.revision,
170
+ deployment: value.deployment,
171
+ release,
172
+ runtime: Object.freeze({
173
+ compatibilityDate: runtime.compatibilityDate,
174
+ compatibilityFlags,
175
+ }),
176
+ bindings: Object.freeze({ vars, services, requiredSecrets }),
177
+ ...(assets === undefined ? {} : { assets }),
178
+ });
179
+ }
180
+ function admitExecutable(input) {
181
+ const value = record(input, ['entrypoint', 'modules']);
182
+ const entrypoint = artifactPath(value.entrypoint, 'Worker entrypoint');
183
+ if (!Array.isArray(value.modules) || value.modules.length === 0)
184
+ invalid();
185
+ const paths = new Set();
186
+ const modules = value.modules.map((input) => {
187
+ const module = record(input, ['path', 'kind', 'digest']);
188
+ const path = artifactPath(module.path, 'Worker module path');
189
+ if (paths.has(path) ||
190
+ !KINDS.has(module.kind) ||
191
+ typeof module.digest !== 'string' ||
192
+ !REVISION.test(module.digest))
193
+ invalid();
194
+ paths.add(path);
195
+ return Object.freeze({ path, kind: module.kind, digest: module.digest });
196
+ });
197
+ canonical(modules.map((module) => module.path));
198
+ return Object.freeze({ entrypoint, modules: Object.freeze(modules) });
199
+ }
200
+ function admitServices(input) {
201
+ if (!Array.isArray(input))
202
+ invalid();
203
+ const names = new Set();
204
+ const services = input.map((item) => {
205
+ const value = record(item, ['name', 'service']);
206
+ if (typeof value.name !== 'string' ||
207
+ typeof value.service !== 'string' ||
208
+ value.service.length === 0 ||
209
+ names.has(value.name))
210
+ invalid();
211
+ names.add(value.name);
212
+ return Object.freeze({ name: value.name, service: value.service });
213
+ });
214
+ canonical(services.map((service) => service.name));
215
+ return Object.freeze(services);
216
+ }
217
+ function admitAssets(input) {
218
+ const value = record(input, ['files', 'notFound', 'runWorkerFirst', 'htmlHandling']);
219
+ if (!Array.isArray(value.files) ||
220
+ !['none', '404-page', 'single-page-application'].includes(String(value.notFound)) ||
221
+ typeof value.runWorkerFirst !== 'boolean' ||
222
+ (value.htmlHandling !== undefined && value.htmlHandling !== 'none'))
223
+ invalid();
224
+ const paths = new Set();
225
+ const files = value.files.map((input) => {
226
+ const file = record(input, ['path', 'artifactPath', 'digest', 'contentType']);
227
+ const path = assetPath(file.path);
228
+ if (paths.has(path))
229
+ invalid();
230
+ paths.add(path);
231
+ if (file.contentType !== undefined &&
232
+ (typeof file.contentType !== 'string' ||
233
+ file.contentType.length === 0 ||
234
+ file.contentType.length > 256))
235
+ invalid();
236
+ if (typeof file.digest !== 'string' || !REVISION.test(file.digest))
237
+ invalid();
238
+ return Object.freeze({
239
+ path,
240
+ artifactPath: artifactPath(file.artifactPath, 'Worker asset artifact path'),
241
+ digest: file.digest,
242
+ ...(file.contentType === undefined ? {} : { contentType: file.contentType }),
243
+ });
244
+ });
245
+ canonical(files.map((file) => file.path));
246
+ return Object.freeze({
247
+ files: Object.freeze(files),
248
+ notFound: value.notFound,
249
+ runWorkerFirst: value.runWorkerFirst,
250
+ ...(value.htmlHandling === undefined ? {} : { htmlHandling: 'none' }),
251
+ });
252
+ }
253
+ function record(input, allowed) {
254
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
255
+ invalid();
256
+ const value = input;
257
+ if (Reflect.ownKeys(value).some((key) => typeof key !== 'string' || !allowed.includes(key)))
258
+ invalid();
259
+ return value;
260
+ }
261
+ function stringRecord(input) {
262
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
263
+ invalid();
264
+ const value = input;
265
+ if (Reflect.ownKeys(value).some((key) => typeof key !== 'string'))
266
+ invalid();
267
+ if (Object.values(value).some((item) => typeof item !== 'string'))
268
+ invalid();
269
+ const entries = Object.entries(value);
270
+ canonical(entries.map(([name]) => name));
271
+ return Object.freeze(Object.fromEntries(entries));
272
+ }
273
+ function uniqueStrings(input, label) {
274
+ if (!Array.isArray(input) || input.some((item) => typeof item !== 'string' || item.length === 0))
275
+ invalid();
276
+ const values = input;
277
+ if (new Set(values).size !== values.length)
278
+ throw new TypeError(`Worker ${label} is duplicated.`);
279
+ canonical(values);
280
+ return Object.freeze([...values]);
281
+ }
282
+ function canonical(values) {
283
+ const sorted = [...values].sort(comparePath);
284
+ if (values.some((value, index) => value !== sorted[index])) {
285
+ throw new TypeError('Cloudflare Worker artifact manifest is not canonical.');
286
+ }
287
+ }
288
+ function claim(claimed, path) {
289
+ if (path === WORKER_MANIFEST_PATH || claimed.has(path)) {
290
+ throw new TypeError(`Cloudflare Worker artifact file ${path} has conflicting roles.`);
291
+ }
292
+ claimed.add(path);
293
+ }
294
+ function requiredFile(artifact, path) {
295
+ const file = artifact.files.find((candidate) => candidate.path === path);
296
+ if (file === undefined)
297
+ throw new TypeError(`Cloudflare Worker artifact is missing ${path}.`);
298
+ return file;
299
+ }
300
+ function verifyContent(content, expected, path) {
301
+ const digest = `sha256:${createHash('sha256').update(content).digest('hex')}`;
302
+ if (digest !== expected) {
303
+ throw new TypeError(`Cloudflare Worker artifact content differs at ${path}.`);
304
+ }
305
+ }
306
+ function invalid() {
307
+ throw new TypeError('Cloudflare Worker artifact manifest is invalid.');
308
+ }
@@ -0,0 +1,48 @@
1
+ import type { WorkerDeploymentId } from '../../release/deployment.js';
2
+ import type { WorkerModuleKind } from './artifact.js';
3
+ import { WORKER_MANIFEST_PATH } from './artifact.paths.js';
4
+ export declare const WORKER_ARTIFACT_FORMAT = "astrale.cloudflare.worker";
5
+ export declare const WORKER_ARTIFACT_VERSION = 2;
6
+ export interface WorkerArtifactManifestV2 {
7
+ readonly format: typeof WORKER_ARTIFACT_FORMAT;
8
+ readonly version: typeof WORKER_ARTIFACT_VERSION;
9
+ readonly origin: string;
10
+ readonly revision: string;
11
+ readonly deployment: WorkerDeploymentId;
12
+ readonly release: ManifestExecutable;
13
+ readonly runtime: {
14
+ readonly compatibilityDate: string;
15
+ readonly compatibilityFlags: readonly string[];
16
+ };
17
+ readonly bindings: {
18
+ readonly vars: Readonly<Record<string, string>>;
19
+ readonly services: readonly {
20
+ readonly name: string;
21
+ readonly service: string;
22
+ }[];
23
+ readonly requiredSecrets: readonly string[];
24
+ };
25
+ readonly assets?: {
26
+ readonly files: readonly {
27
+ readonly path: string;
28
+ readonly artifactPath: string;
29
+ readonly digest: string;
30
+ readonly contentType?: string;
31
+ }[];
32
+ readonly notFound: 'none' | '404-page' | 'single-page-application';
33
+ readonly runWorkerFirst: boolean;
34
+ readonly htmlHandling?: 'none';
35
+ };
36
+ }
37
+ interface ManifestExecutable {
38
+ readonly entrypoint: string;
39
+ readonly modules: readonly {
40
+ readonly path: string;
41
+ readonly kind: WorkerModuleKind;
42
+ readonly digest: string;
43
+ }[];
44
+ }
45
+ export declare function encodeWorkerArtifactManifest(input: Omit<WorkerArtifactManifestV2, 'format' | 'version'>): Uint8Array;
46
+ export declare function workerContentDigest(input: Uint8Array): string;
47
+ export declare function inferMediaType(path: string): string | undefined;
48
+ 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 = 2;
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
+ deployment: input.deployment,
13
+ release: canonicalExecutable(input.release),
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,16 @@
1
+ import type { Build } from '@astrale-os/sdk/deployment/build';
2
+ export interface ExecutionCodegenOptions {
3
+ readonly origin: string;
4
+ readonly runtimeSpecifier: string;
5
+ readonly workerSpecifier?: string;
6
+ readonly build: Build;
7
+ readonly maximumRequestBytes?: number;
8
+ readonly invocationTimeoutMs?: number;
9
+ readonly providerAssignedWorker?: string;
10
+ readonly router?: {
11
+ readonly binding: string;
12
+ readonly hostSuffix: string;
13
+ readonly minLabels: number;
14
+ };
15
+ }
16
+ export declare function generateExecutionService(options: ExecutionCodegenOptions): string;
@@ -0,0 +1,52 @@
1
+ import { generatedMaterial } from '@astrale-os/sdk/deployment/build';
2
+ export function generateExecutionService(options) {
3
+ const workerSpecifier = options.workerSpecifier ?? '@astrale-os/adapter-cloudflare/worker';
4
+ const generated = generatedMaterial(options.build);
5
+ const router = options.router;
6
+ const routerPredicate = router === undefined
7
+ ? ''
8
+ : `
9
+ function isInstanceHost(host: string): boolean {
10
+ return host.endsWith(${JSON.stringify(router.hostSuffix)}) && host.split('.').length >= ${router.minLabels}
11
+ }
12
+ `;
13
+ const routeSubrequest = router === undefined
14
+ ? ''
15
+ : `
16
+ routeSubrequest: (url, env) => {
17
+ const labels = url.hostname.split('.')
18
+ if (env.DISPATCHER && labels[1] === 'svc') return env.DISPATCHER.get(labels[0]!)
19
+ if (env.${router.binding} && isInstanceHost(url.hostname)) return env.${router.binding}
20
+ return null
21
+ },`;
22
+ const frontend = generated.material.frontend === undefined
23
+ ? ''
24
+ : ` frontend: ${JSON.stringify(generated.material.frontend)} as const satisfies NonNullable<BuildMaterial['frontend']>,\n`;
25
+ return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
26
+ // Domain origin: ${options.origin}
27
+ import type { BuildMaterial, GeneratedBuildMaterial } from '@astrale-os/sdk/deployment/build'
28
+ import type { CloudflareAdapterBindings } from ${JSON.stringify(workerSpecifier)}
29
+
30
+ import { generatedCloudflareWorkerEntry } from ${JSON.stringify(workerSpecifier)}
31
+ import { decodeSigningIdentity, runtimeBindings } from ${JSON.stringify(workerSpecifier)}
32
+
33
+ type WorkerEnv = CloudflareEnv & CloudflareAdapterBindings & {
34
+ readonly ASTRALE_SIGNING_IDENTITY: string
35
+ }
36
+ type RuntimeEnv = Omit<WorkerEnv, 'ASTRALE_SIGNING_IDENTITY'>
37
+ ${routerPredicate}
38
+ export default generatedCloudflareWorkerEntry<WorkerEnv, RuntimeEnv>()({
39
+ ${frontend} load: async () => ({
40
+ runtime: (await import(${JSON.stringify(options.runtimeSpecifier)})).default,
41
+ material: ${JSON.stringify(generated)} as unknown as GeneratedBuildMaterial,
42
+ }),
43
+ privateKey: (env) => decodeSigningIdentity(env.ASTRALE_SIGNING_IDENTITY),
44
+ resolveEnvironment: runtimeBindings,
45
+ resolveUrl: (env, requestOrigin) => env.WORKER_URL ?? env.IDENTITY_ISS ?? requestOrigin,
46
+ ${options.maximumRequestBytes === undefined ? '' : `maximumRequestBytes: ${options.maximumRequestBytes},`}
47
+ ${options.invocationTimeoutMs === undefined ? '' : `invocationTimeoutMs: ${options.invocationTimeoutMs},`}
48
+ ${options.providerAssignedWorker === undefined ? '' : `providerAssignedWorker: ${JSON.stringify(options.providerAssignedWorker)},`}
49
+ selfBinding: (env) => env.SELF,${routeSubrequest}
50
+ })
51
+ `;
52
+ }
@@ -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>;