@astrale-os/adapter-cloudflare 0.5.0-beta.165 → 0.5.0-beta.166

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.
package/README.md CHANGED
@@ -23,7 +23,7 @@ must emit the same JavaScript bytes. `cloudflare({ wrangler: { minify: false } }
23
23
  One watcher serializes its own updates. For an existing Worker, it uploads a candidate, reconciles
24
24
  configured triggers, and stages the candidate at 0% while the previous version retains 100% of normal
25
25
  HTTP traffic. Exact-version probes verify Publication, Bundle, and serving identity before promotion;
26
- serving the Publication also initializes the Application Runtime. After promotion, normal requests
26
+ serving the Publication also initializes the Domain Runtime. After promotion, normal requests
27
27
  must prove that exact version again. A failed candidate remains at 0% and can be replaced on retry.
28
28
  Bundled View routes must also serve the prepared browser-file generation before readiness passes.
29
29
  This covers frontend-only changes without changing the Schema or reinstalling the Domain. HTML is
@@ -44,7 +44,7 @@ protects the HTTP implementation, not an atomic rollback of external resources o
44
44
  When an Environment also declares `installation`, the SDK excludes another local checkout that
45
45
  targets the same resolved `Kernel issuer + Domain origin` (including bookmark aliases), reconciles
46
46
  exact installed Release evidence, and opens
47
- the default View when the Application has a frontend. These are Project-tooling behaviors rather
47
+ the default View when the Domain has a frontend. These are Project-tooling behaviors rather
48
48
  than Cloudflare adapter responsibilities.
49
49
 
50
50
  ## Frontend files
@@ -108,6 +108,30 @@ an overlay key the bundler reads (`alias`, `build`, `define`, `jsx_factory`, `js
108
108
  `upload_source_maps`) and a compatibility flag it reads (Node.js, `navigator`, fetch iterable
109
109
  typing, Python) beyond the adapter's own `nodejs_compat`.
110
110
 
111
+ ## Build digest
112
+
113
+ `cloudflare()` and `astrale()` bundle through one bundler, `cloudflare-worker` version `1`, so
114
+ every Environment of either adapter shares one build: `astrale-domain build` prints its build
115
+ digest, and `deploy` places that same Bundle. Two installed versions of this package are two
116
+ copies of the bundler: when their code differs, `build` prints one build digest per copy and warns
117
+ until the adapter versions are aligned.
118
+
119
+ Services receives the placed modules as they are, so there the digest names the bytes the
120
+ deployment runs. Direct mode runs `wrangler deploy`, which bundles the placed modules again: there
121
+ the digest names the portable Worker the deployment was made from, not the bytes Cloudflare runs.
122
+ A host that receives a Worker upload recomputes the digest from the exact upload with the same
123
+ function:
124
+
125
+ ```ts
126
+ import { workerBuildManifest } from '@astrale-os/adapter-cloudflare/release'
127
+ import { buildDigest } from '@astrale-os/sdk/deployment/build/manifest'
128
+
129
+ const digest = buildDigest(workerBuildManifest({ origin, revision, bundle, entry, modules, assets }))
130
+ ```
131
+
132
+ Modules keep their uploaded name and an asset `/<path>` is the file `assets/<path>`. The bundler
133
+ version names this layout, not a package release. Both subpaths are runtime-safe.
134
+
111
135
  ## Material for a custom Worker entrypoint
112
136
 
113
137
  Workers with provider-specific routes or environment initialization can reuse the adapter's
@@ -117,19 +141,19 @@ material emission without adopting the entire generated entrypoint:
117
141
  import { generateBuildMaterialModule } from '@astrale-os/adapter-cloudflare/build'
118
142
  import { writeFile } from 'node:fs/promises'
119
143
 
120
- // build is an admitted Build returned by compile(application).
144
+ // build is an admitted Build returned by compile(domain).
121
145
  await writeFile('src/build-material.gen.ts', generateBuildMaterialModule(build))
122
146
  ```
123
147
 
124
148
  The generated TypeScript module exports `loadBuildMaterial()`. Call it from the
125
- `load` callback of `generatedCloudflareWorkerEntry` alongside the application's Runtime.
149
+ `load` callback of `generatedCloudflareWorkerEntry` alongside the Domain's Runtime.
126
150
  Such an entrypoint states its execution settings (`invocationTimeoutMs`, `maximumRequestBytes`,
127
151
  `tokenExchange`, `routeSubrequest`, `identityIssuer`) in its configuration, or reads them from its
128
152
  deployment bindings with `settings: deploymentSettings`; one Worker never takes both.
129
153
  It constructs the same verified, deduplicated material as the adapter's generated Service;
130
154
  allocation is deferred until the factory is called and the SDK deep-freezes it before use.
131
155
  The generated file needs only a type import from the SDK. Keep its compiler and runtime SDK
132
- versions aligned, and regenerate it when either the Application or that SDK version changes.
156
+ versions aligned, and regenerate it when either the Domain or that SDK version changes.
133
157
 
134
158
  Such an entrypoint can keep the same fast frontend path by answering from
135
159
  `cloudflareFrontDoor()` (`@astrale-os/adapter-cloudflare/worker/front`, which reaches no SDK
package/dist/adapter.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Deployment } from '@astrale-os/sdk/deployment';
2
- import type { Release } from '@astrale-os/sdk/deployment/release';
2
+ import type { LegacyAdapter } from '@astrale-os/sdk/deployment/adapter';
3
+ import type { legacy } from '@astrale-os/sdk/deployment/release';
3
4
  import { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
4
5
  import type { CloudflareParams } from './params.js';
5
6
  import { artifact } from './build/artifact.js';
@@ -9,6 +10,6 @@ export interface CloudflareDependencies extends CloudflareDeployDependencies {
9
10
  readonly artifact: typeof artifact;
10
11
  readonly signingIdentity: typeof signingIdentity;
11
12
  }
12
- export declare function cloudflare(parameters?: CloudflareParams): Deployment<CloudflareParams>;
13
- export declare function cloudflareWithDependencies(parameters: CloudflareParams, dependencies: CloudflareDependencies): Deployment<CloudflareParams>;
14
- export declare function waitForReadiness(verify: typeof verifyReadiness, release: Release, signal?: AbortSignal): Promise<void>;
13
+ export declare function cloudflare(parameters?: CloudflareParams): Deployment<CloudflareParams, LegacyAdapter<CloudflareParams>>;
14
+ export declare function cloudflareWithDependencies(parameters: CloudflareParams, dependencies: CloudflareDependencies): Deployment<CloudflareParams, LegacyAdapter<CloudflareParams>>;
15
+ export declare function waitForReadiness(verify: typeof verifyReadiness, release: legacy.Release, signal?: AbortSignal): Promise<void>;
package/dist/adapter.js CHANGED
@@ -2,6 +2,7 @@ import { defineAdapter, defineDeployment } from '@astrale-os/sdk/deployment/adap
2
2
  import { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
3
3
  import { createRequire } from 'node:module';
4
4
  import { artifact, materialize } from './build/artifact.js';
5
+ import { cloudflareWorkerBundler } from './build/bundler.js';
5
6
  import { verifyWorkerFrontend } from './build/frontend/readiness.js';
6
7
  import { deployCloudflare } from './deploy/index.js';
7
8
  import { versionReadinessFetch } from './deploy/readiness.js';
@@ -31,6 +32,7 @@ export function cloudflareWithDependencies(parameters, dependencies) {
31
32
  const adapter = defineAdapter({
32
33
  name: 'cloudflare',
33
34
  version: packageVersion,
35
+ bundler: cloudflareWorkerBundler,
34
36
  prepare: dependencies.artifact,
35
37
  deploy: (parameters, context) => deployCloudflare(parameters, context, dependencies),
36
38
  secretsFile: (parameters) => parameters.secrets,
@@ -1,16 +1,14 @@
1
1
  import { addressing } from '@astrale-os/sdk/deployment/release';
2
- import { mkdir, mkdtemp, rm } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
2
  import { SIGNING_IDENTITY_BINDING } from '../identity/binding.js';
5
3
  import { workerDeployment } from '../release/index.js';
4
+ import { bundledWorker, bundleWorker } from './bundler.js';
6
5
  import { generateWranglerConfig } from './codegen/wrangler.js';
7
6
  import { deploymentAddressing, deploymentVars, resolveRouter, workerName } from './configuration.js';
8
- import { frontendFiles } from './frontend/files.js';
9
- import { frontendRevision } from './frontend/revision.js';
10
7
  import { encodeWorkerArtifactManifest, inferMediaType, workerContentDigest, WORKER_MANIFEST_PATH, } from './worker/artifact.manifest.js';
11
- import { prepareExecution, RELEASE_ENTRY } from './worker/execution.prepare.js';
8
+ import { RELEASE_ENTRY } from './worker/execution.prepare.js';
12
9
  import { workerModules } from './worker/modules.js';
13
10
  import { admitWorkerRuntimeConfiguration } from './worker/runtime.js';
11
+ import { recordPlacedBundle } from './worker/upload.js';
14
12
  export { materialize } from './materialize.js';
15
13
  const encoder = new TextEncoder();
16
14
  /** Prepare one portable Worker whose public provider address remains stable across builds. */
@@ -22,151 +20,136 @@ export async function prepareWorkerArtifact(params, context) {
22
20
  return prepare(params, context, false);
23
21
  }
24
22
  async function prepare(params, context, direct) {
25
- const stateDirectory = join(context.projectDir, '.astrale');
26
- await mkdir(stateDirectory, { recursive: true });
27
- const directory = await mkdtemp(join(stateDirectory, 'prepare-'));
28
- try {
29
- const router = resolveRouter(params.router);
30
- const name = workerName(params, context.build.schema.compiled.root.origin, context.environment);
31
- const revision = context.build.schema.compiled.root.revision;
32
- const configuredIssuer = deploymentAddressing(params);
33
- // The Environment enters only as the deployment's bindings, never the bundled bytes.
34
- const vars = deploymentVars(params, {
35
- ...(configuredIssuer === undefined ? {} : { configuredIssuer }),
36
- ...(direct && configuredIssuer === undefined ? { providerAssignedWorker: name } : {}),
37
- });
38
- // `frontend` stays the served file list every branch below already reads; `addressed` rides
39
- // alongside it into the Worker, where it decides which of those files a browser may retain.
40
- const { files: frontend, addressed } = await frontendFiles({
41
- frontend: context.build.frontend,
42
- projectDir: context.projectDir,
43
- output: join(directory, 'frontend'),
44
- recordSource: context.recordSource,
45
- signal: context.signal,
46
- });
47
- const modules = await prepareExecution({
48
- context,
49
- directory,
50
- ...(frontend.length === 0 ? {} : { frontendRevision: frontendRevision(frontend) }),
51
- ...(addressed.length === 0 ? {} : { frontendAddressed: addressed }),
52
- });
53
- const release = {
54
- entrypoint: RELEASE_ENTRY,
55
- modules: [
56
- { path: RELEASE_ENTRY, kind: 'esm', content: modules.entry },
57
- ...modules.modules,
58
- ],
59
- };
60
- const releaseConfig = generateWranglerConfig({
61
- workerName: name,
62
- main: `./${RELEASE_ENTRY}`,
63
- ...(params.route === undefined ? {} : { route: params.route }),
64
- vars,
65
- secretBindings: [SIGNING_IDENTITY_BINDING],
66
- modules: workerModules(release),
67
- ...(context.build.frontend?.source.kind === 'vite'
68
- ? {
69
- frontend: {
70
- directory: './frontend',
71
- },
72
- }
73
- : {}),
74
- ...(router === undefined
75
- ? {}
76
- : { router: { binding: router.binding, service: router.service } }),
77
- ...(params.wrangler === undefined ? {} : { wrangler: params.wrangler }),
78
- selfBinding: false,
79
- });
80
- const runtime = admitWorkerRuntimeConfiguration(releaseConfig);
81
- const deployment = workerDeployment({
82
- release,
83
- runtime,
84
- bindings: {
85
- vars: runtime.vars,
86
- services: runtime.services,
87
- requiredSecrets: [SIGNING_IDENTITY_BINDING],
88
- },
89
- ...(frontend.length === 0
90
- ? {}
91
- : {
92
- assets: {
93
- files: frontend.map((file) => {
94
- const contentType = file.mediaType ?? inferMediaType(file.path);
95
- return {
96
- path: `/${file.path}`,
97
- content: file.bytes(),
98
- ...(contentType === undefined ? {} : { contentType }),
99
- };
100
- }),
101
- notFound: 'none',
102
- runWorkerFirst: true,
103
- htmlHandling: 'none',
104
- },
105
- }),
106
- });
107
- const manifest = encodeWorkerArtifactManifest({
108
- origin: context.build.schema.compiled.root.origin,
109
- revision,
110
- deployment,
111
- release: {
112
- entrypoint: release.entrypoint,
113
- modules: release.modules.map(({ path, kind, content }) => ({
114
- path,
115
- kind,
116
- digest: workerContentDigest(content),
117
- })),
118
- },
119
- runtime: {
120
- compatibilityDate: runtime.compatibilityDate,
121
- compatibilityFlags: runtime.compatibilityFlags,
122
- },
123
- bindings: {
124
- vars: runtime.vars,
125
- services: runtime.services,
126
- requiredSecrets: [SIGNING_IDENTITY_BINDING],
127
- },
128
- ...(frontend.length === 0
129
- ? {}
130
- : {
131
- assets: {
132
- files: frontend.map((file) => {
133
- const contentType = file.mediaType ?? inferMediaType(file.path);
134
- return {
135
- path: `/${file.path}`,
136
- artifactPath: `frontend/${file.path}`,
137
- digest: workerContentDigest(file.bytes()),
138
- ...(contentType === undefined ? {} : { contentType }),
139
- };
140
- }),
141
- notFound: 'none',
142
- runWorkerFirst: true,
143
- htmlHandling: 'none',
144
- },
145
- }),
146
- });
147
- const files = [
148
- ...release.modules.map(({ path, content }) => ({ path, content })),
149
- { path: WORKER_MANIFEST_PATH, content: manifest },
150
- ...frontend.map((file) => ({
151
- path: `frontend/${file.path}`,
152
- content: file.bytes(),
23
+ const router = resolveRouter(params.router);
24
+ const name = workerName(params, context.build.schema.compiled.root.origin, context.environment);
25
+ const revision = context.build.schema.compiled.root.revision;
26
+ const configuredIssuer = deploymentAddressing(params);
27
+ // The Environment enters only as the deployment's bindings, never the bundled bytes.
28
+ const vars = deploymentVars(params, {
29
+ ...(configuredIssuer === undefined ? {} : { configuredIssuer }),
30
+ ...(direct && configuredIssuer === undefined ? { providerAssignedWorker: name } : {}),
31
+ });
32
+ // Preparation places the environment-free Worker bytes: the Bundle `deploy` already produced
33
+ // with this adapter's bundler, or the same bundling run here.
34
+ const worker = context.bundle === undefined
35
+ ? await bundleWorker(context)
36
+ : bundledWorker(context.bundle, context.build);
37
+ if (context.bundle !== undefined)
38
+ recordPlacedBundle(context.build);
39
+ const { frontend } = worker;
40
+ const release = {
41
+ entrypoint: RELEASE_ENTRY,
42
+ modules: [
43
+ { path: RELEASE_ENTRY, kind: 'esm', content: worker.entry },
44
+ ...worker.modules,
45
+ ],
46
+ };
47
+ const releaseConfig = generateWranglerConfig({
48
+ workerName: name,
49
+ main: `./${RELEASE_ENTRY}`,
50
+ ...(params.route === undefined ? {} : { route: params.route }),
51
+ vars,
52
+ secretBindings: [SIGNING_IDENTITY_BINDING],
53
+ modules: workerModules(release),
54
+ ...(context.build.frontend?.source.kind === 'vite'
55
+ ? {
56
+ frontend: {
57
+ directory: './frontend',
58
+ },
59
+ }
60
+ : {}),
61
+ ...(router === undefined
62
+ ? {}
63
+ : { router: { binding: router.binding, service: router.service } }),
64
+ ...(params.wrangler === undefined ? {} : { wrangler: params.wrangler }),
65
+ selfBinding: false,
66
+ });
67
+ const runtime = admitWorkerRuntimeConfiguration(releaseConfig);
68
+ const deployment = workerDeployment({
69
+ release,
70
+ runtime,
71
+ bindings: {
72
+ vars: runtime.vars,
73
+ services: runtime.services,
74
+ requiredSecrets: [SIGNING_IDENTITY_BINDING],
75
+ },
76
+ ...(frontend.length === 0
77
+ ? {}
78
+ : {
79
+ assets: {
80
+ files: frontend.map((file) => {
81
+ const contentType = file.mediaType ?? inferMediaType(file.path);
82
+ return {
83
+ path: `/${file.path}`,
84
+ content: file.bytes(),
85
+ ...(contentType === undefined ? {} : { contentType }),
86
+ };
87
+ }),
88
+ notFound: 'none',
89
+ runWorkerFirst: true,
90
+ htmlHandling: 'none',
91
+ },
92
+ }),
93
+ });
94
+ const manifest = encodeWorkerArtifactManifest({
95
+ origin: context.build.schema.compiled.root.origin,
96
+ revision,
97
+ deployment,
98
+ release: {
99
+ entrypoint: release.entrypoint,
100
+ modules: release.modules.map(({ path, kind, content }) => ({
101
+ path,
102
+ kind,
103
+ digest: workerContentDigest(content),
153
104
  })),
154
- ...(direct ? [{ path: 'wrangler.jsonc', content: encoder.encode(releaseConfig) }] : []),
155
- ];
156
- return Object.freeze({
157
- entrypoint: RELEASE_ENTRY,
158
- files: Object.freeze(files),
159
- addressing: configuredIssuer === undefined
160
- ? Object.freeze({ kind: 'provider-assigned', claim: name })
161
- : Object.freeze({
162
- kind: 'configured',
163
- addressing: addressing(params.identityIssuer ?? configuredIssuer, {
164
- publicUrl: configuredIssuer,
105
+ },
106
+ runtime: {
107
+ compatibilityDate: runtime.compatibilityDate,
108
+ compatibilityFlags: runtime.compatibilityFlags,
109
+ },
110
+ bindings: {
111
+ vars: runtime.vars,
112
+ services: runtime.services,
113
+ requiredSecrets: [SIGNING_IDENTITY_BINDING],
114
+ },
115
+ ...(frontend.length === 0
116
+ ? {}
117
+ : {
118
+ assets: {
119
+ files: frontend.map((file) => {
120
+ const contentType = file.mediaType ?? inferMediaType(file.path);
121
+ return {
122
+ path: `/${file.path}`,
123
+ artifactPath: `frontend/${file.path}`,
124
+ digest: workerContentDigest(file.bytes()),
125
+ ...(contentType === undefined ? {} : { contentType }),
126
+ };
165
127
  }),
128
+ notFound: 'none',
129
+ runWorkerFirst: true,
130
+ htmlHandling: 'none',
131
+ },
132
+ }),
133
+ });
134
+ const files = [
135
+ ...release.modules.map(({ path, content }) => ({ path, content })),
136
+ { path: WORKER_MANIFEST_PATH, content: manifest },
137
+ ...frontend.map((file) => ({
138
+ path: `frontend/${file.path}`,
139
+ content: file.bytes(),
140
+ })),
141
+ ...(direct ? [{ path: 'wrangler.jsonc', content: encoder.encode(releaseConfig) }] : []),
142
+ ];
143
+ return Object.freeze({
144
+ entrypoint: RELEASE_ENTRY,
145
+ files: Object.freeze(files),
146
+ addressing: configuredIssuer === undefined
147
+ ? Object.freeze({ kind: 'provider-assigned', claim: name })
148
+ : Object.freeze({
149
+ kind: 'configured',
150
+ addressing: addressing(params.identityIssuer ?? configuredIssuer, {
151
+ publicUrl: configuredIssuer,
166
152
  }),
167
- });
168
- }
169
- finally {
170
- await rm(directory, { recursive: true, force: true });
171
- }
153
+ }),
154
+ });
172
155
  }
@@ -0,0 +1,21 @@
1
+ import type { Bundle, BundleContext, Bundler } from '@astrale-os/sdk/deployment/adapter';
2
+ import type { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
3
+ import type { ReleaseModule } from './worker/execution.prepare.js';
4
+ /**
5
+ * Environment-free bytes of one generated Worker: its entry, the modules it loads (the execution
6
+ * Service, the deferred workflow engine and their WASM) and its frontend.
7
+ */
8
+ export interface WorkerBytes {
9
+ readonly entry: Uint8Array;
10
+ readonly modules: readonly ReleaseModule[];
11
+ readonly frontend: readonly frontendBuild.FrontendFile[];
12
+ }
13
+ /**
14
+ * Bundles every generated Cloudflare Worker, for adapter-cloudflare and adapter-astrale alike. It
15
+ * reads no Environment: settings, address, issuer and key are bound at deploy.
16
+ */
17
+ export declare const cloudflareWorkerBundler: Bundler;
18
+ /** Bundle the Worker modules and build the frontend they serve, in one scratch directory. */
19
+ export declare function bundleWorker(context: BundleContext): Promise<WorkerBytes>;
20
+ /** Read back the Worker bytes of a Bundle this bundler produced for the same Build. */
21
+ export declare function bundledWorker(bundle: Bundle, build: Bundle['build']): WorkerBytes;
@@ -0,0 +1,96 @@
1
+ import { mkdir, mkdtemp, rm } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { CLOUDFLARE_WORKER_BUNDLER, workerAssetFile } from '../release/build.js';
4
+ import { frontendFiles } from './frontend/files.js';
5
+ import { frontendRevision } from './frontend/revision.js';
6
+ import { prepareExecution, RELEASE_ENTRY, RELEASE_SERVICE } from './worker/execution.prepare.js';
7
+ /** Worker modules live beside the entry; WASM is the only non-ESM kind preparation emits. */
8
+ const MODULE_DIRECTORY = 'bundle/';
9
+ /**
10
+ * Bundles every generated Cloudflare Worker, for adapter-cloudflare and adapter-astrale alike. It
11
+ * reads no Environment: settings, address, issuer and key are bound at deploy.
12
+ */
13
+ export const cloudflareWorkerBundler = Object.freeze({
14
+ id: CLOUDFLARE_WORKER_BUNDLER.id,
15
+ version: CLOUDFLARE_WORKER_BUNDLER.version,
16
+ async bundle(context) {
17
+ const worker = await bundleWorker(context);
18
+ return Object.freeze({
19
+ entrypoint: RELEASE_ENTRY,
20
+ files: Object.freeze([
21
+ { path: RELEASE_ENTRY, content: worker.entry },
22
+ ...worker.modules.map(({ path, content }) => ({ path, content })),
23
+ ...worker.frontend.map((file) => ({
24
+ path: workerAssetFile(`/${file.path}`),
25
+ content: file.bytes(),
26
+ })),
27
+ ]),
28
+ });
29
+ },
30
+ });
31
+ /** Bundle the Worker modules and build the frontend they serve, in one scratch directory. */
32
+ export async function bundleWorker(context) {
33
+ const stateDirectory = join(context.projectDir, '.astrale');
34
+ await mkdir(stateDirectory, { recursive: true });
35
+ const directory = await mkdtemp(join(stateDirectory, 'prepare-'));
36
+ try {
37
+ // `frontend` is the served file list; `addressed` rides alongside it into the Worker, where it
38
+ // decides which of those files a browser may retain.
39
+ const { files: frontend, addressed } = await frontendFiles({
40
+ frontend: context.build.frontend,
41
+ projectDir: context.projectDir,
42
+ output: join(directory, 'frontend'),
43
+ recordSource: context.recordSource,
44
+ signal: context.signal,
45
+ });
46
+ const modules = await prepareExecution({
47
+ context,
48
+ directory,
49
+ ...(frontend.length === 0 ? {} : { frontendRevision: frontendRevision(frontend) }),
50
+ ...(addressed.length === 0 ? {} : { frontendAddressed: addressed }),
51
+ });
52
+ return Object.freeze({ entry: modules.entry, modules: modules.modules, frontend });
53
+ }
54
+ finally {
55
+ await rm(directory, { recursive: true, force: true });
56
+ }
57
+ }
58
+ /** Read back the Worker bytes of a Bundle this bundler produced for the same Build. */
59
+ export function bundledWorker(bundle, build) {
60
+ const { id, version } = bundle.manifest.bundler;
61
+ if (bundle.build !== build ||
62
+ id !== CLOUDFLARE_WORKER_BUNDLER.id ||
63
+ version !== CLOUDFLARE_WORKER_BUNDLER.version ||
64
+ bundle.entrypoint !== RELEASE_ENTRY) {
65
+ throw new TypeError('Cloudflare preparation requires the Bundle of its Worker bundler.');
66
+ }
67
+ let entry;
68
+ const modules = [];
69
+ const frontend = [];
70
+ for (const file of bundle.files) {
71
+ if (file.path === RELEASE_ENTRY)
72
+ entry = file.bytes();
73
+ else if (file.path.startsWith(MODULE_DIRECTORY) && /\.(?:mjs|wasm)$/u.test(file.path)) {
74
+ modules.push(Object.freeze({
75
+ path: file.path,
76
+ kind: file.path.endsWith('.wasm') ? 'wasm' : 'esm',
77
+ content: file.bytes(),
78
+ }));
79
+ }
80
+ else if (file.path.startsWith('assets/')) {
81
+ const content = file.bytes();
82
+ frontend.push(Object.freeze({ path: file.path.slice('assets/'.length), bytes: () => content.slice() }));
83
+ }
84
+ else {
85
+ throw new TypeError(`Cloudflare Worker Bundle file ${file.path} is not a Worker file.`);
86
+ }
87
+ }
88
+ if (entry === undefined || !modules.some((module) => module.path === RELEASE_SERVICE)) {
89
+ throw new TypeError('Cloudflare Worker Bundle lacks its execution modules.');
90
+ }
91
+ return Object.freeze({
92
+ entry,
93
+ modules: Object.freeze(modules),
94
+ frontend: Object.freeze(frontend),
95
+ });
96
+ }
@@ -1,9 +1,9 @@
1
- import type { Release } from '@astrale-os/sdk/deployment/release';
1
+ import type { legacy } from '@astrale-os/sdk/deployment/release';
2
2
  import type { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
3
3
  import type { WorkerArtifact } from '../worker/artifact.js';
4
4
  type ReadinessFetch = NonNullable<NonNullable<Parameters<typeof verifyReadiness>[1]>['fetch']>;
5
5
  /** Publication equality alone cannot prove a frontend-only deployment has propagated. */
6
- export declare function verifyWorkerFrontend(release: Release, worker: Pick<WorkerArtifact, 'assets'>, options?: {
6
+ export declare function verifyWorkerFrontend(release: legacy.Release, worker: Pick<WorkerArtifact, 'assets'>, options?: {
7
7
  readonly fetch?: ReadinessFetch;
8
8
  readonly signal?: AbortSignal;
9
9
  }): Promise<void>;
@@ -1,7 +1,9 @@
1
1
  export { generateBuildMaterialModule } from './material.js';
2
2
  export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
3
+ export { cloudflareWorkerBundler } from './bundler.js';
3
4
  export { loadWorkerArtifact } from './worker/index.js';
4
5
  export type { WorkerArtifact } from './worker/index.js';
6
+ export { uploadBuildDigest, uploadBuildEvidence, workerUpload } from './worker/upload.js';
5
7
  export { isAdapterVar, resolveRouter, workerName } from './configuration.js';
6
8
  export { frontendFiles } from './frontend/files.js';
7
9
  export { verifyWorkerFrontend } from './frontend/readiness.js';
@@ -1,6 +1,8 @@
1
1
  export { generateBuildMaterialModule } from './material.js';
2
2
  export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
3
+ export { cloudflareWorkerBundler } from './bundler.js';
3
4
  export { loadWorkerArtifact } from './worker/index.js';
5
+ export { uploadBuildDigest, uploadBuildEvidence, workerUpload } from './worker/upload.js';
4
6
  export { isAdapterVar, resolveRouter, workerName } from './configuration.js';
5
7
  export { frontendFiles } from './frontend/files.js';
6
8
  export { verifyWorkerFrontend } from './frontend/readiness.js';
@@ -1,4 +1,4 @@
1
- import type { PrepareContext } from '@astrale-os/sdk/deployment/adapter';
1
+ import type { BundleContext } from '@astrale-os/sdk/deployment/adapter';
2
2
  export declare const RELEASE_ENTRY = "bundle/index.mjs";
3
3
  export declare const RELEASE_SERVICE = "bundle/service.mjs";
4
4
  export declare const RELEASE_RESTATE = "bundle/restate.mjs";
@@ -9,7 +9,7 @@ export interface ReleaseModule {
9
9
  readonly content: Uint8Array;
10
10
  }
11
11
  export declare function prepareExecution(input: {
12
- readonly context: PrepareContext;
12
+ readonly context: BundleContext;
13
13
  readonly directory: string;
14
14
  readonly frontendRevision?: string;
15
15
  readonly frontendAddressed?: readonly string[];
@@ -17,4 +17,4 @@ export declare function prepareExecution(input: {
17
17
  readonly entry: Uint8Array;
18
18
  readonly modules: readonly ReleaseModule[];
19
19
  }>;
20
- export declare function declaresDurable(build: PrepareContext['build']): boolean;
20
+ export declare function declaresDurable(build: BundleContext['build']): boolean;
@@ -0,0 +1,18 @@
1
+ import type { PreparedArtifact } from '@astrale-os/sdk/deployment/adapter';
2
+ import type { Build } from '@astrale-os/sdk/deployment/build';
3
+ import type { BuildDigest } from '@astrale-os/sdk/deployment/build/manifest';
4
+ import type { WorkerArtifact } from './artifact.js';
5
+ import { type WorkerUpload } from '../../release/build.js';
6
+ /** The portable upload of one prepared Worker: exactly the modules and assets Services receives. */
7
+ export declare function workerUpload(artifact: PreparedArtifact, worker: WorkerArtifact): WorkerUpload;
8
+ /** Build digest of the portable upload of one prepared Worker. */
9
+ export declare function uploadBuildDigest(artifact: PreparedArtifact, worker: WorkerArtifact): BuildDigest;
10
+ /** Record that preparation placed the CLI's Bundle for this Build. */
11
+ export declare function recordPlacedBundle(build: Build): void;
12
+ /**
13
+ * The `build` field of one prepared Worker's provider evidence: the build digest of its portable
14
+ * upload, present only when its preparation placed the CLI's Bundle.
15
+ */
16
+ export declare function uploadBuildEvidence(artifact: PreparedArtifact, worker: WorkerArtifact): {
17
+ readonly build?: BuildDigest;
18
+ };
@@ -0,0 +1,40 @@
1
+ import { buildDigest } from '@astrale-os/sdk/deployment/build/manifest';
2
+ import { workerBuildManifest } from '../../release/build.js';
3
+ /** The portable upload of one prepared Worker: exactly the modules and assets Services receives. */
4
+ export function workerUpload(artifact, worker) {
5
+ const root = artifact.build.schema.compiled.root;
6
+ if (worker.origin !== root.origin || worker.revision !== root.revision) {
7
+ throw new TypeError('Worker artifact differs from its prepared Build.');
8
+ }
9
+ return Object.freeze({
10
+ origin: root.origin,
11
+ revision: root.revision,
12
+ bundle: artifact.build.schema.bundle.ref.digest,
13
+ entry: worker.release.entrypoint,
14
+ modules: worker.release.modules.map((module) => ({ name: module.path, bytes: module.bytes() })),
15
+ ...(worker.assets === undefined
16
+ ? {}
17
+ : { assets: worker.assets.files.map((file) => ({ path: file.path, bytes: file.bytes() })) }),
18
+ });
19
+ }
20
+ /** Build digest of the portable upload of one prepared Worker. */
21
+ export function uploadBuildDigest(artifact, worker) {
22
+ return buildDigest(workerBuildManifest(workerUpload(artifact, worker)));
23
+ }
24
+ /**
25
+ * Builds whose preparation placed the Bundle `astrale-domain deploy` passed (`PrepareContext.bundle`).
26
+ * Only a CLI that passes a Bundle reads `ProviderDeploymentEvidence.build`: an older one refuses an
27
+ * evidence field it does not know, after the provider has applied the upload.
28
+ */
29
+ const placedBuilds = new WeakSet();
30
+ /** Record that preparation placed the CLI's Bundle for this Build. */
31
+ export function recordPlacedBundle(build) {
32
+ placedBuilds.add(build);
33
+ }
34
+ /**
35
+ * The `build` field of one prepared Worker's provider evidence: the build digest of its portable
36
+ * upload, present only when its preparation placed the CLI's Bundle.
37
+ */
38
+ export function uploadBuildEvidence(artifact, worker) {
39
+ return placedBuilds.has(artifact.build) ? { build: uploadBuildDigest(artifact, worker) } : {};
40
+ }
@@ -1,5 +1,5 @@
1
1
  import type { DeployContext, DeployResult } from '@astrale-os/sdk/deployment/adapter';
2
- import type { Release } from '@astrale-os/sdk/deployment/release';
2
+ import { legacy } from '@astrale-os/sdk/deployment/release';
3
3
  import type { materialize } from '../build/artifact.js';
4
4
  import type { WorkerArtifact } from '../build/worker/artifact.js';
5
5
  import type { signingIdentity } from '../identity/source.js';
@@ -10,7 +10,7 @@ export interface CloudflareDeployDependencies {
10
10
  readonly materialize: typeof materialize;
11
11
  readonly runWranglerRollout: typeof runWranglerRollout;
12
12
  readonly runWranglerDeploymentStatus: typeof runWranglerDeploymentStatus;
13
- readonly waitForReadiness: (release: Release, signal: AbortSignal, version: string, worker: WorkerArtifact, workerName?: string) => Promise<void>;
13
+ readonly waitForReadiness: (release: legacy.Release, signal: AbortSignal, version: string, worker: WorkerArtifact, workerName?: string) => Promise<void>;
14
14
  readonly signingIdentity: typeof signingIdentity;
15
15
  }
16
16
  /** Deploy one stable Worker target and verify its exact serving implementation. */
@@ -1,9 +1,10 @@
1
- import { addressing, assemble } from '@astrale-os/sdk/deployment/release';
1
+ import { addressing, legacy } from '@astrale-os/sdk/deployment/release';
2
2
  import { mkdir, mkdtemp, rm } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { isAdapterVar, workerName } from '../build/configuration.js';
5
5
  import { admitDirectPlacement } from '../build/direct.js';
6
6
  import { loadWorkerArtifact } from '../build/worker/index.js';
7
+ import { uploadBuildEvidence } from '../build/worker/upload.js';
7
8
  import { signingIdentitySecrets } from '../identity/secret.js';
8
9
  import { WranglerDeployError } from '../wrangler/deploy.js';
9
10
  import { WranglerRolloutError } from '../wrangler/rollout.js';
@@ -24,8 +25,10 @@ export async function deployCloudflare(parameters, context, dependencies) {
24
25
  return failed('prerequisite', progress({}), cause);
25
26
  }
26
27
  let worker;
28
+ let build;
27
29
  try {
28
30
  worker = loadWorkerArtifact(context.artifact);
31
+ build = uploadBuildEvidence(context.artifact, worker);
29
32
  const occupied = new Set([
30
33
  ...Object.keys(worker.bindings.vars),
31
34
  ...worker.bindings.services.map((binding) => binding.name),
@@ -60,7 +63,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
60
63
  workerName: name,
61
64
  verifyCandidate: async (candidate) => {
62
65
  const selected = observeAddressing(context.artifact, candidate.url, name);
63
- const release = assemble(context.artifact.build, selected);
66
+ const release = legacy.assemble(context.artifact.build, selected);
64
67
  await dependencies.waitForReadiness(release, context.signal, candidate.deploymentVersion, worker, name);
65
68
  },
66
69
  secrets,
@@ -91,6 +94,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
91
94
  provider: 'cloudflare',
92
95
  deployment: deployed.deploymentVersion,
93
96
  artifact: context.artifact.digest,
97
+ ...build,
94
98
  }),
95
99
  });
96
100
  let selected;
@@ -121,7 +125,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
121
125
  }
122
126
  let release;
123
127
  try {
124
- release = assemble(context.artifact.build, selected);
128
+ release = legacy.assemble(context.artifact.build, selected);
125
129
  }
126
130
  catch (cause) {
127
131
  return failed('release', progress({ provider: retained, configuration }), cause);
@@ -138,6 +142,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
138
142
  provider: 'cloudflare',
139
143
  deployment: deployed.deploymentVersion,
140
144
  artifact: context.artifact.digest,
145
+ ...build,
141
146
  issuer: selected.issuer,
142
147
  }),
143
148
  });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@astrale-os/adapter-cloudflare` — deploy an Astrale application through
2
+ * `@astrale-os/adapter-cloudflare` — deploy an Astrale Domain through
3
3
  * Cloudflare Workers on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@astrale-os/adapter-cloudflare` — deploy an Astrale application through
2
+ * `@astrale-os/adapter-cloudflare` — deploy an Astrale Domain through
3
3
  * Cloudflare Workers on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
@@ -0,0 +1,35 @@
1
+ import type { BuildManifestV1 } from '@astrale-os/sdk/deployment/build/manifest';
2
+ /**
3
+ * The bundler of every generated Cloudflare Worker, whichever adapter deploys it. Its version names
4
+ * the upload layout below, not a package release: a Services host recomputes the same build digest
5
+ * from the same bytes whatever SDK it runs.
6
+ */
7
+ export declare const CLOUDFLARE_WORKER_BUNDLER: Readonly<{
8
+ id: "cloudflare-worker";
9
+ version: "1";
10
+ }>;
11
+ /** Portable Worker upload: what Services receives, and what a dispatch namespace upload carries. */
12
+ export interface WorkerUpload {
13
+ readonly origin: string;
14
+ readonly revision: BuildManifestV1['schema']['revision'];
15
+ /** Digest of the Domain Schema bundle. */
16
+ readonly bundle: BuildManifestV1['schema']['bundle'];
17
+ readonly entry: string;
18
+ readonly modules: readonly {
19
+ readonly name: string;
20
+ readonly bytes: Uint8Array;
21
+ }[];
22
+ /** Static assets by their absolute Worker asset path (`/index.html`). */
23
+ readonly assets?: readonly {
24
+ readonly path: string;
25
+ readonly bytes: Uint8Array;
26
+ }[];
27
+ }
28
+ /**
29
+ * The one build manifest of a Worker upload, computed on the exact upload the deploy sends; the
30
+ * SDK, Services and namespace mode all identify Worker code through it. Modules keep their uploaded
31
+ * name; a static asset `/<path>` is the file `assets/<path>`.
32
+ */
33
+ export declare function workerBuildManifest(upload: WorkerUpload): BuildManifestV1;
34
+ /** The build-manifest file of one Worker asset path. */
35
+ export declare function workerAssetFile(path: string): string;
@@ -0,0 +1,44 @@
1
+ import { buildManifest } from '@astrale-os/sdk/deployment/build/manifest';
2
+ /**
3
+ * The bundler of every generated Cloudflare Worker, whichever adapter deploys it. Its version names
4
+ * the upload layout below, not a package release: a Services host recomputes the same build digest
5
+ * from the same bytes whatever SDK it runs.
6
+ */
7
+ export const CLOUDFLARE_WORKER_BUNDLER = Object.freeze({
8
+ id: 'cloudflare-worker',
9
+ version: '1',
10
+ });
11
+ /**
12
+ * The one build manifest of a Worker upload, computed on the exact upload the deploy sends; the
13
+ * SDK, Services and namespace mode all identify Worker code through it. Modules keep their uploaded
14
+ * name; a static asset `/<path>` is the file `assets/<path>`.
15
+ */
16
+ export function workerBuildManifest(upload) {
17
+ if (upload === null || typeof upload !== 'object' || !Array.isArray(upload.modules)) {
18
+ throw new TypeError('Worker upload is invalid.');
19
+ }
20
+ if (upload.assets !== undefined && !Array.isArray(upload.assets)) {
21
+ throw new TypeError('Worker upload assets are invalid.');
22
+ }
23
+ return buildManifest({
24
+ origin: upload.origin,
25
+ revision: upload.revision,
26
+ bundle: upload.bundle,
27
+ bundler: CLOUDFLARE_WORKER_BUNDLER,
28
+ entrypoint: upload.entry,
29
+ files: [
30
+ ...upload.modules.map((module) => ({ path: module.name, content: module.bytes })),
31
+ ...(upload.assets ?? []).map((asset) => ({
32
+ path: workerAssetFile(asset.path),
33
+ content: asset.bytes,
34
+ })),
35
+ ],
36
+ });
37
+ }
38
+ /** The build-manifest file of one Worker asset path. */
39
+ export function workerAssetFile(path) {
40
+ if (typeof path !== 'string' || !path.startsWith('/')) {
41
+ throw new TypeError('Worker asset path must be absolute.');
42
+ }
43
+ return `assets${path}`;
44
+ }
@@ -1,2 +1,3 @@
1
+ export { CLOUDFLARE_WORKER_BUNDLER, workerBuildManifest, type WorkerUpload } from './build.js';
1
2
  export { acceptWorkerDeployment, workerDeployment, workerDeploymentMaterial, type WorkerDeploymentId, type WorkerDeploymentInput, } from './deployment.js';
2
3
  export { stagedWorkerName } from './staged-worker-name.js';
@@ -1,2 +1,3 @@
1
+ export { CLOUDFLARE_WORKER_BUNDLER, workerBuildManifest } from './build.js';
1
2
  export { acceptWorkerDeployment, workerDeployment, workerDeploymentMaterial, } from './deployment.js';
2
3
  export { stagedWorkerName } from './staged-worker-name.js';
@@ -1,6 +1,6 @@
1
1
  import { issuer } from '@astrale-os/sdk/auth';
2
2
  import { restoreBuild, restoreGeneratedBuild } from '@astrale-os/sdk/deployment/build/material';
3
- import { addressing, assemble } from '@astrale-os/sdk/deployment/release/runtime';
3
+ import { addressing, legacy } from '@astrale-os/sdk/deployment/release/runtime';
4
4
  import { createIdentity, serve } from '@astrale-os/sdk/execution';
5
5
  import { dispatchCloudflareRequest, prepareCloudflareRequest } from './fetch.js';
6
6
  import { cloudflareFrontDoor, withProviderVersion } from './front.js';
@@ -49,7 +49,7 @@ function createCloudflareWorkerEntry(restore) {
49
49
  if (frontDoor.current() !== environment.publicUrl) {
50
50
  throw new TypeError('Cloudflare Worker request origin differs from its current Release.');
51
51
  }
52
- return assemble(build, addressing(settings.identityIssuer ?? environment.publicUrl, {
52
+ return legacy.assemble(build, addressing(settings.identityIssuer ?? environment.publicUrl, {
53
53
  publicUrl: environment.publicUrl,
54
54
  }));
55
55
  },
@@ -1,4 +1,4 @@
1
1
  import type { BuildMaterial } from '@astrale-os/sdk/deployment/build/material';
2
2
  import type { Fetcher } from './front.js';
3
- /** Serve public frontend files without realizing the Application's execution Runtime. */
3
+ /** Serve public frontend files without realizing the Domain's execution Runtime. */
4
4
  export declare function frontendAsset(frontend: BuildMaterial['frontend'], assets: Fetcher | undefined, url: URL, request: Request, revision?: string, addressed?: readonly string[]): Promise<Response | undefined>;
@@ -1,4 +1,4 @@
1
- /** Serve public frontend files without realizing the Application's execution Runtime. */
1
+ /** Serve public frontend files without realizing the Domain's execution Runtime. */
2
2
  export async function frontendAsset(frontend, assets, url, request, revision, addressed) {
3
3
  if (frontend === undefined || frontend.source.kind === 'external')
4
4
  return undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrale-os/adapter-cloudflare",
3
- "version": "0.5.0-beta.165",
4
- "description": "Deploy an Astrale application through Cloudflare Workers",
3
+ "version": "0.5.0-beta.166",
4
+ "description": "Deploy an Astrale Domain through Cloudflare Workers",
5
5
  "keywords": [
6
6
  "adapter",
7
7
  "astrale",
@@ -61,7 +61,7 @@
61
61
  "registry": "https://registry.npmjs.org/"
62
62
  },
63
63
  "dependencies": {
64
- "@astrale-os/sdk": "0.6.0-beta.10",
64
+ "@astrale-os/sdk": "0.6.0-beta.11",
65
65
  "@noble/hashes": "2.3.0",
66
66
  "@restatedev/restate-sdk-cloudflare-workers": "1.17.0",
67
67
  "hono": "^4.6.20",
@@ -9,10 +9,10 @@ import { cloudflare } from '@astrale-os/adapter-cloudflare'
9
9
  import { defineProject } from '@astrale-os/sdk/project'
10
10
  import { dataset, tests } from '@astrale-os/sdk/testing'
11
11
 
12
- import { application } from './application.js'
12
+ import { domain } from './domain.js'
13
13
 
14
14
  export default defineProject({
15
- application,
15
+ domain,
16
16
 
17
17
  environments: {
18
18
  development: {