@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
|
@@ -5,7 +5,10 @@ const TYPES = Object.freeze({
|
|
|
5
5
|
text: 'Text',
|
|
6
6
|
data: 'Data',
|
|
7
7
|
});
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Project one admitted Worker module inventory into Wrangler's relative module discovery. Rules
|
|
10
|
+
* follow module kinds, so the projection does not depend on the inventory's order.
|
|
11
|
+
*/
|
|
9
12
|
export function workerModules(worker) {
|
|
10
13
|
const directory = posix.dirname(worker.entrypoint);
|
|
11
14
|
const groups = new Map();
|
|
@@ -21,6 +24,9 @@ export function workerModules(worker) {
|
|
|
21
24
|
globs.push(relative);
|
|
22
25
|
groups.set(type, globs);
|
|
23
26
|
}
|
|
24
|
-
const rules =
|
|
27
|
+
const rules = Object.values(TYPES).flatMap((type) => {
|
|
28
|
+
const globs = groups.get(type);
|
|
29
|
+
return globs === undefined ? [] : [Object.freeze({ type, globs: Object.freeze(globs) })];
|
|
30
|
+
});
|
|
25
31
|
return Object.freeze({ baseDir: `./${directory}`, rules: Object.freeze(rules) });
|
|
26
32
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare function workerModuleSpecifier(): string;
|
|
2
2
|
/** The isolate-startup module, which must stay free of the execution Service's dependencies. */
|
|
3
3
|
export declare function frontModuleSpecifier(): string;
|
|
4
|
+
/** The engine's Workers build, resolved from this adapter so a Domain needs no engine dependency. */
|
|
5
|
+
export declare function restateModuleSpecifier(): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
const workerModule = import.meta.resolve('@astrale-os/adapter-cloudflare/worker');
|
|
3
|
+
const restateModule = import.meta.resolve('@astrale-os/adapter-cloudflare/worker/restate');
|
|
3
4
|
const frontModule = import.meta.resolve('@astrale-os/adapter-cloudflare/worker/front');
|
|
4
5
|
export function workerModuleSpecifier() {
|
|
5
6
|
return fileURLToPath(workerModule);
|
|
@@ -8,3 +9,7 @@ export function workerModuleSpecifier() {
|
|
|
8
9
|
export function frontModuleSpecifier() {
|
|
9
10
|
return fileURLToPath(frontModule);
|
|
10
11
|
}
|
|
12
|
+
/** The engine's Workers build, resolved from this adapter so a Domain needs no engine dependency. */
|
|
13
|
+
export function restateModuleSpecifier() {
|
|
14
|
+
return fileURLToPath(restateModule);
|
|
15
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/deploy/deploy.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DeployContext, DeployResult } from '@astrale-os/sdk/deployment/adapter';
|
|
2
|
-
import
|
|
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. */
|
package/dist/deploy/deploy.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { addressing,
|
|
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
|
-
import { workerName } from '../build/configuration.js';
|
|
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,19 @@ 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);
|
|
32
|
+
const occupied = new Set([
|
|
33
|
+
...Object.keys(worker.bindings.vars),
|
|
34
|
+
...worker.bindings.services.map((binding) => binding.name),
|
|
35
|
+
]);
|
|
36
|
+
for (const name of Object.keys(secrets)) {
|
|
37
|
+
if (occupied.has(name) || isAdapterVar(name)) {
|
|
38
|
+
throw new TypeError(`Cloudflare Worker secret ${name} collides with a binding.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
29
41
|
for (const name of worker.bindings.requiredSecrets) {
|
|
30
42
|
if (!Object.hasOwn(secrets, name)) {
|
|
31
43
|
throw new TypeError(`Cloudflare Worker requires secret ${name}.`);
|
|
@@ -51,7 +63,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
|
|
|
51
63
|
workerName: name,
|
|
52
64
|
verifyCandidate: async (candidate) => {
|
|
53
65
|
const selected = observeAddressing(context.artifact, candidate.url, name);
|
|
54
|
-
const release = assemble(context.artifact.build, selected);
|
|
66
|
+
const release = legacy.assemble(context.artifact.build, selected);
|
|
55
67
|
await dependencies.waitForReadiness(release, context.signal, candidate.deploymentVersion, worker, name);
|
|
56
68
|
},
|
|
57
69
|
secrets,
|
|
@@ -82,6 +94,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
|
|
|
82
94
|
provider: 'cloudflare',
|
|
83
95
|
deployment: deployed.deploymentVersion,
|
|
84
96
|
artifact: context.artifact.digest,
|
|
97
|
+
...build,
|
|
85
98
|
}),
|
|
86
99
|
});
|
|
87
100
|
let selected;
|
|
@@ -112,7 +125,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
|
|
|
112
125
|
}
|
|
113
126
|
let release;
|
|
114
127
|
try {
|
|
115
|
-
release = assemble(context.artifact.build, selected);
|
|
128
|
+
release = legacy.assemble(context.artifact.build, selected);
|
|
116
129
|
}
|
|
117
130
|
catch (cause) {
|
|
118
131
|
return failed('release', progress({ provider: retained, configuration }), cause);
|
|
@@ -129,6 +142,7 @@ export async function deployCloudflare(parameters, context, dependencies) {
|
|
|
129
142
|
provider: 'cloudflare',
|
|
130
143
|
deployment: deployed.deploymentVersion,
|
|
131
144
|
artifact: context.artifact.digest,
|
|
145
|
+
...build,
|
|
132
146
|
issuer: selected.issuer,
|
|
133
147
|
}),
|
|
134
148
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@astrale-os/adapter-cloudflare` — deploy an Astrale
|
|
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'
|
|
@@ -19,4 +19,4 @@
|
|
|
19
19
|
* tooling owns optional Kernel installation after provider readiness.
|
|
20
20
|
*/
|
|
21
21
|
export { cloudflare } from './cloudflare.js';
|
|
22
|
-
export type { CloudflareParams } from './params.js';
|
|
22
|
+
export type { CloudflareParams, DurableWorkerOptions } from './params.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@astrale-os/adapter-cloudflare` — deploy an Astrale
|
|
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/params.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExchangeOptions } from '@astrale-os/sdk/execution';
|
|
1
|
+
import type { DurableServing, ExchangeOptions } from '@astrale-os/sdk/execution';
|
|
2
2
|
/**
|
|
3
3
|
* `CloudflareParams` — the provider-typed config for the Cloudflare adapter.
|
|
4
4
|
*
|
|
@@ -50,6 +50,13 @@ export interface CloudflareParams {
|
|
|
50
50
|
* The default maximum is 30 minutes; production and CI environments may select a shorter bound.
|
|
51
51
|
*/
|
|
52
52
|
readonly tokenExchange?: ExchangeOptions | false;
|
|
53
|
+
/**
|
|
54
|
+
* What a Worker serving durable Workflows declares to the workflow engine, as `serve({ durable })`
|
|
55
|
+
* does: run retention, retry before pausing and the deadline of each Step. Canonical callbacks
|
|
56
|
+
* discover engine keys from their Kernel. The deprecated `ASTRALE_RESTATE_IDENTITY_KEYS` var
|
|
57
|
+
* admits retained legacy callbacks only.
|
|
58
|
+
*/
|
|
59
|
+
readonly durable?: DurableWorkerOptions;
|
|
53
60
|
/**
|
|
54
61
|
* Escape hatch: raw wrangler config deep-merged over the generated base. Use
|
|
55
62
|
* it to declare extra bindings the adapter has no typed field for — KV, R2,
|
|
@@ -67,6 +74,13 @@ export interface CloudflareParams {
|
|
|
67
74
|
* `routes` are rejected — use `workerName` and `route` instead. Avoid
|
|
68
75
|
* the reserved binding names `SELF` and `ASSETS`. Durable Objects need extra
|
|
69
76
|
* worker-entry codegen and aren't supported through this field yet.
|
|
77
|
+
*
|
|
78
|
+
* The overlay reaches the deployment configuration, never the bundle: every
|
|
79
|
+
* Environment of one source bundles the same bytes. Keys the bundler reads
|
|
80
|
+
* (`alias`, `build`, `define`, `jsx_factory`, `jsx_fragment`, `keep_names`,
|
|
81
|
+
* `minify`, `no_bundle`, `node_compat`, `preserve_file_names`, `rules`,
|
|
82
|
+
* `tsconfig`, `upload_source_maps`) and compatibility flags it reads (Node.js,
|
|
83
|
+
* `navigator`, fetch iterable typing, Python) are rejected.
|
|
70
84
|
*/
|
|
71
85
|
readonly wrangler?: Record<string, unknown>;
|
|
72
86
|
/**
|
|
@@ -75,8 +89,9 @@ export interface CloudflareParams {
|
|
|
75
89
|
* same-zone Worker calls honor public routes too.
|
|
76
90
|
*
|
|
77
91
|
* Wires BOTH halves the SDK needs: the `<binding>` service binding in the
|
|
78
|
-
*
|
|
79
|
-
*
|
|
92
|
+
* deployment's wrangler config, and the host suffix and label rule bound in
|
|
93
|
+
* `ASTRALE_SETTINGS`, which the Worker applies to divert matching hosts
|
|
94
|
+
* through that binding.
|
|
80
95
|
*
|
|
81
96
|
* Opt in only when the selected router owns every hostname matched by the
|
|
82
97
|
* suffix/label predicate. A shared DNS suffix is not proof of router ownership:
|
|
@@ -96,3 +111,4 @@ export interface CloudflareParams {
|
|
|
96
111
|
readonly minLabels?: number;
|
|
97
112
|
};
|
|
98
113
|
}
|
|
114
|
+
export type DurableWorkerOptions = Pick<DurableServing, 'retentionMs' | 'retry' | 'stepTimeoutMs'>;
|
|
@@ -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
|
+
}
|
package/dist/release/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/release/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DurableEngine, DurableServing } from '@astrale-os/sdk/execution';
|
|
2
|
+
/** @deprecated Static request identity keys for retained legacy callbacks only. */
|
|
3
|
+
export declare const RESTATE_IDENTITY_KEYS_BINDING = "ASTRALE_RESTATE_IDENTITY_KEYS";
|
|
4
|
+
export interface DurableBindings {
|
|
5
|
+
readonly [RESTATE_IDENTITY_KEYS_BINDING]?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Canonical callbacks discover their engine's authority from the Kernel named in their path.
|
|
9
|
+
* The optional request preserves the three-argument legacy helper's static-key behavior.
|
|
10
|
+
*/
|
|
11
|
+
export declare function restateServing(engine: DurableEngine, bindings: DurableBindings, options?: Pick<DurableServing, 'retentionMs' | 'retry' | 'stepTimeoutMs'>, request?: Request): DurableServing | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isCanonicalDurableRequest, isLegacyDurableRequest } from '@astrale-os/sdk/execution';
|
|
2
|
+
/** @deprecated Static request identity keys for retained legacy callbacks only. */
|
|
3
|
+
export const RESTATE_IDENTITY_KEYS_BINDING = 'ASTRALE_RESTATE_IDENTITY_KEYS';
|
|
4
|
+
/**
|
|
5
|
+
* Canonical callbacks discover their engine's authority from the Kernel named in their path.
|
|
6
|
+
* The optional request preserves the three-argument legacy helper's static-key behavior.
|
|
7
|
+
*/
|
|
8
|
+
export function restateServing(engine, bindings, options = {}, request) {
|
|
9
|
+
const canonical = request !== undefined && isCanonicalDurableRequest(request);
|
|
10
|
+
if (request !== undefined && !canonical && !isLegacyDurableRequest(request))
|
|
11
|
+
return undefined;
|
|
12
|
+
const value = bindings[RESTATE_IDENTITY_KEYS_BINDING];
|
|
13
|
+
if (value === undefined || value.trim() === '') {
|
|
14
|
+
return canonical ? Object.freeze({ ...options, engine }) : undefined;
|
|
15
|
+
}
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
...options,
|
|
18
|
+
engine,
|
|
19
|
+
identityKeys: Object.freeze(value
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((key) => key.trim())
|
|
22
|
+
.filter((key) => key.length > 0)),
|
|
23
|
+
});
|
|
24
|
+
}
|
package/dist/worker/entry.d.ts
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import type { JsonWebKey } from '@astrale-os/sdk/auth';
|
|
2
2
|
import type { BuildMaterial, GeneratedBuildMaterial } from '@astrale-os/sdk/deployment/build/material';
|
|
3
|
-
import type {
|
|
3
|
+
import type { DurableServingProvider, ExecutionContext } from '@astrale-os/sdk/execution';
|
|
4
4
|
import type { Runtime } from '@astrale-os/sdk/runtime';
|
|
5
|
+
import type { DurableWorkerOptions } from '../params.js';
|
|
5
6
|
import type { CloudflareAdapterBindings, CloudflareFrontDoorConfig, Fetcher } from './front.js';
|
|
7
|
+
import type { CloudflareWorkerSettings } from './settings.js';
|
|
6
8
|
export type { CloudflareAdapterBindings, Fetcher } from './front.js';
|
|
7
|
-
export interface CloudflareWorkerConfig<RuntimeValue extends Runtime, Bindings extends CloudflareAdapterBindings, Environment, Material = BuildMaterial> extends CloudflareFrontDoorConfig<Bindings> {
|
|
9
|
+
export interface CloudflareWorkerConfig<RuntimeValue extends Runtime, Bindings extends CloudflareAdapterBindings, Environment, Material = BuildMaterial> extends CloudflareFrontDoorConfig<Bindings>, CloudflareWorkerSettings<Bindings> {
|
|
8
10
|
/** Load the exact Runtime and Build once, after the isolate has accepted a request. */
|
|
9
11
|
readonly load: () => CloudflareWorkerSource<RuntimeValue, Material> | Promise<CloudflareWorkerSource<RuntimeValue, Material>>;
|
|
10
12
|
readonly privateKey: JsonWebKey | ((bindings: Bindings) => JsonWebKey);
|
|
11
13
|
readonly resolveEnvironment?: (bindings: Bindings) => Environment;
|
|
12
|
-
readonly identityIssuer?: string;
|
|
13
14
|
readonly selfBinding?: (bindings: Bindings) => Fetcher | null | undefined;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Read the execution settings from the deployment's bindings on the isolate's first execution
|
|
17
|
+
* request, as generated Workers do with `deploymentSettings`. A hand-composed Worker states
|
|
18
|
+
* them in this configuration instead; one Worker never takes both.
|
|
19
|
+
*/
|
|
20
|
+
readonly settings?: (bindings: Bindings) => CloudflareWorkerSettings<Bindings>;
|
|
21
|
+
/** Resolve the engine for this durable request with the deployment's captured options. */
|
|
22
|
+
readonly durable?: (bindings: Bindings, request: Request, options?: DurableWorkerOptions) => ReturnType<DurableServingProvider>;
|
|
18
23
|
}
|
|
19
24
|
export interface CloudflareWorkerSource<RuntimeValue extends Runtime, Material = BuildMaterial> {
|
|
20
25
|
readonly runtime: RuntimeValue;
|
package/dist/worker/entry.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { issuer } from '@astrale-os/sdk/auth';
|
|
2
2
|
import { restoreBuild, restoreGeneratedBuild } from '@astrale-os/sdk/deployment/build/material';
|
|
3
|
-
import { addressing,
|
|
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';
|
|
7
7
|
import { frontendAsset } from './frontend.js';
|
|
8
|
+
import { DEFAULT_INVOCATION_TIMEOUT_MS, DEFAULT_MAXIMUM_REQUEST_BYTES } from './settings.js';
|
|
8
9
|
/** Compose one Service implementation behind its provider-owned stable address. */
|
|
9
10
|
export function cloudflareWorkerEntry() {
|
|
10
11
|
return createCloudflareWorkerEntry(restoreBuild);
|
|
@@ -13,60 +14,82 @@ export function cloudflareWorkerEntry() {
|
|
|
13
14
|
export function generatedCloudflareWorkerEntry() {
|
|
14
15
|
return createCloudflareWorkerEntry(restoreGeneratedBuild);
|
|
15
16
|
}
|
|
17
|
+
/** The static settings a hand-composed Worker may state instead of reading its bindings. */
|
|
18
|
+
const SETTINGS = [
|
|
19
|
+
'identityIssuer',
|
|
20
|
+
'routeSubrequest',
|
|
21
|
+
'invocationTimeoutMs',
|
|
22
|
+
'maximumRequestBytes',
|
|
23
|
+
'tokenExchange',
|
|
24
|
+
'durableOptions',
|
|
25
|
+
];
|
|
16
26
|
function createCloudflareWorkerEntry(restore) {
|
|
17
27
|
return function (config) {
|
|
28
|
+
if (config.settings !== undefined && SETTINGS.some((key) => config[key] !== undefined)) {
|
|
29
|
+
throw new TypeError('Cloudflare Worker settings come from its deployment bindings or its configuration, not both.');
|
|
30
|
+
}
|
|
18
31
|
const frontDoor = cloudflareFrontDoor(config);
|
|
19
|
-
let
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
+
let restored;
|
|
33
|
+
let application;
|
|
34
|
+
const realize = () => (restored ??= Promise.resolve()
|
|
35
|
+
.then(config.load)
|
|
36
|
+
.then((source) => Object.freeze({
|
|
37
|
+
runtime: source.runtime,
|
|
38
|
+
build: restore(source.material, source.runtime),
|
|
39
|
+
})));
|
|
40
|
+
// Settings are a deployment's, so they are read from the first execution request's bindings.
|
|
41
|
+
const served = (source, bindings) => {
|
|
42
|
+
if (application !== undefined)
|
|
43
|
+
return application;
|
|
44
|
+
const { build } = source;
|
|
45
|
+
const settings = config.settings === undefined ? config : config.settings(bindings);
|
|
46
|
+
const created = serve({
|
|
47
|
+
runtime: source.runtime,
|
|
48
|
+
load(environment) {
|
|
49
|
+
if (frontDoor.current() !== environment.publicUrl) {
|
|
50
|
+
throw new TypeError('Cloudflare Worker request origin differs from its current Release.');
|
|
51
|
+
}
|
|
52
|
+
return legacy.assemble(build, addressing(settings.identityIssuer ?? environment.publicUrl, {
|
|
53
|
+
publicUrl: environment.publicUrl,
|
|
54
|
+
}));
|
|
55
|
+
},
|
|
56
|
+
initialize: (environment) => config.resolveEnvironment === undefined
|
|
57
|
+
? environment.bindings
|
|
58
|
+
: config.resolveEnvironment(environment.bindings),
|
|
59
|
+
identity(environment) {
|
|
60
|
+
return createIdentity({
|
|
61
|
+
issuer: issuer.accept(settings.identityIssuer ?? environment.publicUrl),
|
|
62
|
+
subject: build.schema.compiled.root.origin,
|
|
63
|
+
privateKey: typeof config.privateKey === 'function'
|
|
64
|
+
? config.privateKey(environment.bindings)
|
|
65
|
+
: config.privateKey,
|
|
66
|
+
fetch: routedFetch(created, environment, config.selfBinding, settings.routeSubrequest),
|
|
67
|
+
...(new URL(environment.publicUrl).protocol === 'http:'
|
|
68
|
+
? { allowInsecureHttp: true }
|
|
69
|
+
: {}),
|
|
70
|
+
...(settings.tokenExchange === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { tokenExchange: settings.tokenExchange }),
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
...(config.durable === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: {
|
|
78
|
+
durable: (environment, request) => config.durable(environment.bindings, request, settings.durableOptions),
|
|
79
|
+
}),
|
|
80
|
+
server: {
|
|
81
|
+
health: { probe: { check: () => true } },
|
|
82
|
+
limits: {
|
|
83
|
+
maximumRequestBytes: settings.maximumRequestBytes ?? DEFAULT_MAXIMUM_REQUEST_BYTES,
|
|
84
|
+
maximumPublicationBytes: 1024 * 1024,
|
|
85
|
+
maximumBundleBytes: 8 * 1024 * 1024,
|
|
86
|
+
maximumDeliveryBytes: 8 * 1024 * 1024,
|
|
87
|
+
invocationTimeoutMs: settings.invocationTimeoutMs ?? DEFAULT_INVOCATION_TIMEOUT_MS,
|
|
65
88
|
},
|
|
66
|
-
}
|
|
67
|
-
return Object.freeze({ build, application });
|
|
89
|
+
},
|
|
68
90
|
});
|
|
69
|
-
|
|
91
|
+
application = created;
|
|
92
|
+
return created;
|
|
70
93
|
};
|
|
71
94
|
return Object.freeze({
|
|
72
95
|
async fetch(request, bindings, context) {
|
|
@@ -74,13 +97,13 @@ function createCloudflareWorkerEntry(restore) {
|
|
|
74
97
|
if (admission.kind !== 'execution')
|
|
75
98
|
return admission.response;
|
|
76
99
|
const { url, publicUrl } = admission;
|
|
77
|
-
const
|
|
100
|
+
const source = await realize();
|
|
78
101
|
if (config.frontend === undefined) {
|
|
79
|
-
const frontend = await frontendAsset(build.frontend, bindings.ASSETS, url, request, config.frontendRevision, config.frontendAddressed);
|
|
102
|
+
const frontend = await frontendAsset(source.build.frontend, bindings.ASSETS, url, request, config.frontendRevision, config.frontendAddressed);
|
|
80
103
|
if (frontend !== undefined)
|
|
81
104
|
return withProviderVersion(frontend, bindings);
|
|
82
105
|
}
|
|
83
|
-
const response = await
|
|
106
|
+
const response = await served(source, bindings).fetch(request, Object.freeze({ bindings, publicUrl }), context);
|
|
84
107
|
return withProviderVersion(response, bindings);
|
|
85
108
|
},
|
|
86
109
|
async prepare() {
|
|
@@ -89,17 +112,17 @@ function createCloudflareWorkerEntry(restore) {
|
|
|
89
112
|
});
|
|
90
113
|
};
|
|
91
114
|
}
|
|
92
|
-
function routedFetch(application, environment,
|
|
115
|
+
function routedFetch(application, environment, selfBinding, routeSubrequest) {
|
|
93
116
|
return async (input, init) => {
|
|
94
117
|
const { request, rejectRedirect } = prepareCloudflareRequest(input, init);
|
|
95
118
|
const destination = new URL(request.url);
|
|
96
119
|
if (destination.origin === environment.publicUrl) {
|
|
97
|
-
const self =
|
|
120
|
+
const self = selfBinding?.(environment.bindings);
|
|
98
121
|
return dispatchCloudflareRequest(request, rejectRedirect, (selected) => self === null || self === undefined
|
|
99
122
|
? application.fetch(selected, environment)
|
|
100
123
|
: self.fetch(selected));
|
|
101
124
|
}
|
|
102
|
-
const route =
|
|
125
|
+
const route = routeSubrequest?.(destination, environment.bindings);
|
|
103
126
|
return dispatchCloudflareRequest(request, rejectRedirect, (selected) => route === null || route === undefined ? fetch(selected) : route.fetch(selected));
|
|
104
127
|
};
|
|
105
128
|
}
|