@astrale-os/adapter-cloudflare 0.5.0-beta.164 → 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 +64 -5
- package/dist/adapter.d.ts +5 -4
- package/dist/adapter.js +2 -0
- package/dist/build/artifact.js +134 -149
- package/dist/build/bundle/admit.d.ts +2 -0
- package/dist/build/bundle/admit.js +37 -0
- package/dist/build/bundle/compiled-wasm-loader.d.ts +8 -0
- package/dist/build/bundle/compiled-wasm-loader.js +21 -0
- package/dist/build/bundler.d.ts +21 -0
- package/dist/build/bundler.js +96 -0
- package/dist/build/codegen/wrangler.d.ts +4 -3
- package/dist/build/codegen/wrangler.js +35 -1
- package/dist/build/configuration/invocation-timeout.js +2 -2
- package/dist/build/configuration/legacy/direct.d.ts +15 -0
- package/dist/build/configuration/legacy/direct.js +19 -0
- package/dist/build/configuration/request-limit.js +1 -1
- package/dist/build/configuration/settings.d.ts +15 -0
- package/dist/build/configuration/settings.js +71 -0
- package/dist/build/configuration.d.ts +1 -0
- package/dist/build/configuration.js +1 -0
- package/dist/build/frontend/readiness.d.ts +2 -2
- package/dist/build/index.d.ts +3 -1
- package/dist/build/index.js +3 -1
- package/dist/build/worker/codegen/entry.d.ts +0 -1
- package/dist/build/worker/codegen/entry.js +0 -3
- package/dist/build/worker/codegen/service.d.ts +4 -11
- package/dist/build/worker/codegen/service.js +23 -28
- package/dist/build/worker/execution.prepare.d.ts +11 -11
- package/dist/build/worker/execution.prepare.js +69 -74
- package/dist/build/worker/modules.d.ts +8 -2
- package/dist/build/worker/modules.js +8 -2
- package/dist/build/worker/specifier.d.ts +2 -0
- package/dist/build/worker/specifier.js +5 -0
- package/dist/build/worker/upload.d.ts +18 -0
- package/dist/build/worker/upload.js +40 -0
- package/dist/deploy/deploy.d.ts +2 -2
- package/dist/deploy/deploy.js +18 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/params.d.ts +19 -3
- package/dist/release/build.d.ts +35 -0
- package/dist/release/build.js +44 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/worker/durable.d.ts +11 -0
- package/dist/worker/durable.js +24 -0
- package/dist/worker/entry.d.ts +12 -7
- package/dist/worker/entry.js +79 -56
- package/dist/worker/front.d.ts +23 -1
- package/dist/worker/front.js +3 -14
- package/dist/worker/frontend.d.ts +1 -1
- package/dist/worker/frontend.js +2 -2
- package/dist/worker/index.d.ts +3 -0
- package/dist/worker/index.js +3 -0
- package/dist/worker/legacy/direct.d.ts +37 -0
- package/dist/worker/legacy/direct.js +58 -0
- package/dist/worker/restate.d.ts +6 -0
- package/dist/worker/restate.js +6 -0
- package/dist/worker/settings.d.ts +48 -0
- package/dist/worker/settings.js +161 -0
- package/dist/wrangler/bundle.js +0 -1
- package/package.json +9 -4
- package/template/astrale.config.ts +2 -2
package/README.md
CHANGED
|
@@ -16,10 +16,14 @@ as `deploy`; it starts no local Worker, Vite server, or tunnel. Omitting `route`
|
|
|
16
16
|
stable `*.workers.dev` issuer. Add `installation: { instance: '...' }` to the Project Environment
|
|
17
17
|
when generic tooling should reconcile the ready Release onto an Astrale Kernel.
|
|
18
18
|
|
|
19
|
+
Generated Worker modules are minified by default, including separately loaded runtime modules.
|
|
20
|
+
The Environment's `wrangler` overlay cannot change minification: every Environment of one source
|
|
21
|
+
must emit the same JavaScript bytes. `cloudflare({ wrangler: { minify: false } })` is rejected.
|
|
22
|
+
|
|
19
23
|
One watcher serializes its own updates. For an existing Worker, it uploads a candidate, reconciles
|
|
20
24
|
configured triggers, and stages the candidate at 0% while the previous version retains 100% of normal
|
|
21
25
|
HTTP traffic. Exact-version probes verify Publication, Bundle, and serving identity before promotion;
|
|
22
|
-
serving the Publication also initializes the
|
|
26
|
+
serving the Publication also initializes the Domain Runtime. After promotion, normal requests
|
|
23
27
|
must prove that exact version again. A failed candidate remains at 0% and can be replaced on retry.
|
|
24
28
|
Bundled View routes must also serve the prepared browser-file generation before readiness passes.
|
|
25
29
|
This covers frontend-only changes without changing the Schema or reinstalling the Domain. HTML is
|
|
@@ -40,7 +44,7 @@ protects the HTTP implementation, not an atomic rollback of external resources o
|
|
|
40
44
|
When an Environment also declares `installation`, the SDK excludes another local checkout that
|
|
41
45
|
targets the same resolved `Kernel issuer + Domain origin` (including bookmark aliases), reconciles
|
|
42
46
|
exact installed Release evidence, and opens
|
|
43
|
-
the default View when the
|
|
47
|
+
the default View when the Domain has a frontend. These are Project-tooling behaviors rather
|
|
44
48
|
than Cloudflare adapter responsibilities.
|
|
45
49
|
|
|
46
50
|
## Frontend files
|
|
@@ -76,6 +80,58 @@ candidate. The public Worker name and `/invoke` endpoint remain stable across co
|
|
|
76
80
|
implementation and Schema updates. Provider staging identity never enters Release, Publication,
|
|
77
81
|
Kernel routing, or graph identity.
|
|
78
82
|
|
|
83
|
+
## Deployment bindings
|
|
84
|
+
|
|
85
|
+
The bundled Worker carries no Environment value, so every Environment of one source bundles the
|
|
86
|
+
same bytes. Each deployment binds its own values as plain Worker bindings:
|
|
87
|
+
|
|
88
|
+
| Binding | Value |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `WORKER_URL` | serving origin of the deployment |
|
|
91
|
+
| `ASTRALE_SETTINGS` | canonical JSON of `{ invocationTimeoutMs, maximumRequestBytes, router, tokenExchange }`, defaults resolved, with optional `durableOptions` |
|
|
92
|
+
| `ASTRALE_BUILD_DIGEST` | build digest, bound only by hosts that deploy immutable Workers; never by a stable Worker |
|
|
93
|
+
| `IDENTITY_ISS`, `ASTRALE_PROVIDER_ASSIGNED_WORKER` | **legacy direct mode only** (deprecated): a stable issuer behind another route, and the workers.dev claim |
|
|
94
|
+
|
|
95
|
+
These names are adapter-owned: preparation refuses an author var, or a `wrangler.vars` entry, that
|
|
96
|
+
takes one, and deployment refuses a secret that does. A host that deploys the Worker itself encodes
|
|
97
|
+
the settings with `encodeWorkerSettings` from `@astrale-os/adapter-cloudflare/worker`. A generated
|
|
98
|
+
Worker refuses execution without `ASTRALE_SETTINGS`.
|
|
99
|
+
|
|
100
|
+
The two direct-mode bindings are deprecated. Their replacement is an immutable deployment, whose
|
|
101
|
+
host binds `WORKER_URL` to the deployment URL, which is also its issuer (namespace mode). They are
|
|
102
|
+
deleted with direct mode, once the Shell, Services, Issues, domains and ui configurations deploy in
|
|
103
|
+
namespace mode and no installation pins a direct Worker.
|
|
104
|
+
|
|
105
|
+
The `wrangler` overlay reaches the deployment configuration, never the bundle. Preparation refuses
|
|
106
|
+
an overlay key the bundler reads (`alias`, `build`, `define`, `jsx_factory`, `jsx_fragment`,
|
|
107
|
+
`keep_names`, `minify`, `no_bundle`, `node_compat`, `preserve_file_names`, `rules`, `tsconfig`,
|
|
108
|
+
`upload_source_maps`) and a compatibility flag it reads (Node.js, `navigator`, fetch iterable
|
|
109
|
+
typing, Python) beyond the adapter's own `nodejs_compat`.
|
|
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
|
+
|
|
79
135
|
## Material for a custom Worker entrypoint
|
|
80
136
|
|
|
81
137
|
Workers with provider-specific routes or environment initialization can reuse the adapter's
|
|
@@ -85,16 +141,19 @@ material emission without adopting the entire generated entrypoint:
|
|
|
85
141
|
import { generateBuildMaterialModule } from '@astrale-os/adapter-cloudflare/build'
|
|
86
142
|
import { writeFile } from 'node:fs/promises'
|
|
87
143
|
|
|
88
|
-
// build is an admitted Build returned by compile(
|
|
144
|
+
// build is an admitted Build returned by compile(domain).
|
|
89
145
|
await writeFile('src/build-material.gen.ts', generateBuildMaterialModule(build))
|
|
90
146
|
```
|
|
91
147
|
|
|
92
148
|
The generated TypeScript module exports `loadBuildMaterial()`. Call it from the
|
|
93
|
-
`load` callback of `generatedCloudflareWorkerEntry` alongside the
|
|
149
|
+
`load` callback of `generatedCloudflareWorkerEntry` alongside the Domain's Runtime.
|
|
150
|
+
Such an entrypoint states its execution settings (`invocationTimeoutMs`, `maximumRequestBytes`,
|
|
151
|
+
`tokenExchange`, `routeSubrequest`, `identityIssuer`) in its configuration, or reads them from its
|
|
152
|
+
deployment bindings with `settings: deploymentSettings`; one Worker never takes both.
|
|
94
153
|
It constructs the same verified, deduplicated material as the adapter's generated Service;
|
|
95
154
|
allocation is deferred until the factory is called and the SDK deep-freezes it before use.
|
|
96
155
|
The generated file needs only a type import from the SDK. Keep its compiler and runtime SDK
|
|
97
|
-
versions aligned, and regenerate it when either the
|
|
156
|
+
versions aligned, and regenerate it when either the Domain or that SDK version changes.
|
|
98
157
|
|
|
99
158
|
Such an entrypoint can keep the same fast frontend path by answering from
|
|
100
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 {
|
|
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,
|
package/dist/build/artifact.js
CHANGED
|
@@ -1,15 +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
|
-
import { deploymentAddressing, resolveRouter, workerName } from './configuration.js';
|
|
8
|
-
import { frontendFiles } from './frontend/files.js';
|
|
9
|
-
import { frontendRevision } from './frontend/revision.js';
|
|
6
|
+
import { deploymentAddressing, deploymentVars, resolveRouter, workerName } from './configuration.js';
|
|
10
7
|
import { encodeWorkerArtifactManifest, inferMediaType, workerContentDigest, WORKER_MANIFEST_PATH, } from './worker/artifact.manifest.js';
|
|
11
|
-
import {
|
|
8
|
+
import { RELEASE_ENTRY } from './worker/execution.prepare.js';
|
|
9
|
+
import { workerModules } from './worker/modules.js';
|
|
12
10
|
import { admitWorkerRuntimeConfiguration } from './worker/runtime.js';
|
|
11
|
+
import { recordPlacedBundle } from './worker/upload.js';
|
|
13
12
|
export { materialize } from './materialize.js';
|
|
14
13
|
const encoder = new TextEncoder();
|
|
15
14
|
/** Prepare one portable Worker whose public provider address remains stable across builds. */
|
|
@@ -21,150 +20,136 @@ export async function prepareWorkerArtifact(params, context) {
|
|
|
21
20
|
return prepare(params, context, false);
|
|
22
21
|
}
|
|
23
22
|
async function prepare(params, context, direct) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const manifest = encodeWorkerArtifactManifest({
|
|
106
|
-
origin: context.build.schema.compiled.root.origin,
|
|
107
|
-
revision,
|
|
108
|
-
deployment,
|
|
109
|
-
release: {
|
|
110
|
-
entrypoint: RELEASE_ENTRY,
|
|
111
|
-
modules: [
|
|
112
|
-
{ path: RELEASE_ENTRY, kind: 'esm', digest: workerContentDigest(modules.entry) },
|
|
113
|
-
{ path: RELEASE_SERVICE, kind: 'esm', digest: workerContentDigest(modules.service) },
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
runtime: {
|
|
117
|
-
compatibilityDate: runtime.compatibilityDate,
|
|
118
|
-
compatibilityFlags: runtime.compatibilityFlags,
|
|
119
|
-
},
|
|
120
|
-
bindings: {
|
|
121
|
-
vars: runtime.vars,
|
|
122
|
-
services: runtime.services,
|
|
123
|
-
requiredSecrets: [SIGNING_IDENTITY_BINDING],
|
|
124
|
-
},
|
|
125
|
-
...(frontend.length === 0
|
|
126
|
-
? {}
|
|
127
|
-
: {
|
|
128
|
-
assets: {
|
|
129
|
-
files: frontend.map((file) => {
|
|
130
|
-
const contentType = file.mediaType ?? inferMediaType(file.path);
|
|
131
|
-
return {
|
|
132
|
-
path: `/${file.path}`,
|
|
133
|
-
artifactPath: `frontend/${file.path}`,
|
|
134
|
-
digest: workerContentDigest(file.bytes()),
|
|
135
|
-
...(contentType === undefined ? {} : { contentType }),
|
|
136
|
-
};
|
|
137
|
-
}),
|
|
138
|
-
notFound: 'none',
|
|
139
|
-
runWorkerFirst: true,
|
|
140
|
-
htmlHandling: 'none',
|
|
141
|
-
},
|
|
142
|
-
}),
|
|
143
|
-
});
|
|
144
|
-
const files = [
|
|
145
|
-
{ path: RELEASE_ENTRY, content: modules.entry },
|
|
146
|
-
{ path: RELEASE_SERVICE, content: modules.service },
|
|
147
|
-
{ path: WORKER_MANIFEST_PATH, content: manifest },
|
|
148
|
-
...frontend.map((file) => ({
|
|
149
|
-
path: `frontend/${file.path}`,
|
|
150
|
-
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),
|
|
151
104
|
})),
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
};
|
|
163
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,
|
|
164
152
|
}),
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
finally {
|
|
168
|
-
await rm(directory, { recursive: true, force: true });
|
|
169
|
-
}
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
170
155
|
}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
|
+
import * as nodeModule from 'node:module';
|
|
1
2
|
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { compiledWasmLoader, compiledWasmSource } from './compiled-wasm-loader.js';
|
|
4
|
+
let compiledWasm = false;
|
|
5
|
+
/**
|
|
6
|
+
* Import a WebAssembly module as Workers do for `CompiledWasm`: its default export is the compiled
|
|
7
|
+
* `WebAssembly.Module`, instantiated by the importing code. The CLI admits bundles under Bun; Node
|
|
8
|
+
* serves programmatic builds.
|
|
9
|
+
*/
|
|
10
|
+
function admitCompiledWasm() {
|
|
11
|
+
if (compiledWasm)
|
|
12
|
+
return;
|
|
13
|
+
if (process.versions.bun !== undefined) {
|
|
14
|
+
;
|
|
15
|
+
Reflect.get(globalThis, 'Bun').plugin({
|
|
16
|
+
name: 'astrale-compiled-wasm',
|
|
17
|
+
setup(build) {
|
|
18
|
+
build.onLoad({ filter: /\.wasm$/ }, ({ path }) => ({
|
|
19
|
+
loader: 'js',
|
|
20
|
+
contents: compiledWasmSource(JSON.stringify(path)),
|
|
21
|
+
}));
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
compiledWasm = true;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
nodeModule.register(compiledWasmLoader);
|
|
28
|
+
compiledWasm = true;
|
|
29
|
+
}
|
|
2
30
|
export async function admitWorkerBundle(path) {
|
|
31
|
+
admitCompiledWasm();
|
|
3
32
|
const loaded = (await import(`${pathToFileURL(path).href}?admit=${Date.now()}`));
|
|
4
33
|
if (loaded.default === null ||
|
|
5
34
|
typeof loaded.default !== 'object' ||
|
|
@@ -7,3 +36,11 @@ export async function admitWorkerBundle(path) {
|
|
|
7
36
|
throw new TypeError('Generated Worker bundle does not export a Fetch handler.');
|
|
8
37
|
}
|
|
9
38
|
}
|
|
39
|
+
/** Admit the separately emitted engine's actual platform exports, including its compiled WASM. */
|
|
40
|
+
export async function admitDurableEngineBundle(path) {
|
|
41
|
+
admitCompiledWasm();
|
|
42
|
+
const engine = (await import(`${pathToFileURL(path).href}?admit=${Date.now()}`));
|
|
43
|
+
if (['createEndpointHandler', 'workflow', 'TerminalError', 'CancelledError'].some((key) => typeof engine[key] !== 'function')) {
|
|
44
|
+
throw new TypeError('Generated durable engine bundle does not export the platform Fetch entrypoint.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Workers expose CompiledWasm imports as modules, rather than instantiated exports. */
|
|
2
|
+
export declare function compiledWasmSource(file: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Async hooks run in a separate Node thread. A self-contained ESM hook admits both emitted JS and
|
|
5
|
+
* source builds under tsx without requiring that thread to resolve or transpile our source files.
|
|
6
|
+
* The emitted import source is shared with Bun.
|
|
7
|
+
*/
|
|
8
|
+
export declare const compiledWasmLoader: string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const SOURCE_PREFIX = "import { readFileSync } from 'node:fs'\nexport default new WebAssembly.Module(readFileSync(";
|
|
2
|
+
const SOURCE_SUFFIX = '))\n';
|
|
3
|
+
/** Workers expose CompiledWasm imports as modules, rather than instantiated exports. */
|
|
4
|
+
export function compiledWasmSource(file) {
|
|
5
|
+
return `${SOURCE_PREFIX}${file}${SOURCE_SUFFIX}`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Async hooks run in a separate Node thread. A self-contained ESM hook admits both emitted JS and
|
|
9
|
+
* source builds under tsx without requiring that thread to resolve or transpile our source files.
|
|
10
|
+
* The emitted import source is shared with Bun.
|
|
11
|
+
*/
|
|
12
|
+
export const compiledWasmLoader = `data:text/javascript,${encodeURIComponent(`
|
|
13
|
+
export function load(url, context, nextLoad) {
|
|
14
|
+
if (!url.startsWith('file:') || !url.endsWith('.wasm')) return nextLoad(url, context);
|
|
15
|
+
return {
|
|
16
|
+
format: 'module',
|
|
17
|
+
shortCircuit: true,
|
|
18
|
+
source: ${JSON.stringify(SOURCE_PREFIX)} + 'new URL(' + JSON.stringify(url) + ')' + ${JSON.stringify(SOURCE_SUFFIX)},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
`)}`;
|
|
@@ -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
|
+
}
|