@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
|
@@ -37,8 +37,8 @@ export interface WranglerCodegenOptions {
|
|
|
37
37
|
selfBinding: boolean;
|
|
38
38
|
/**
|
|
39
39
|
* Add a service binding to a router Worker for instance-host subrequest
|
|
40
|
-
* routing (see `CloudflareParams.router`). Pairs with the
|
|
41
|
-
*
|
|
40
|
+
* routing (see `CloudflareParams.router`). Pairs with the router rule bound
|
|
41
|
+
* in `ASTRALE_SETTINGS`.
|
|
42
42
|
*/
|
|
43
43
|
router?: {
|
|
44
44
|
binding: string;
|
|
@@ -49,7 +49,8 @@ export interface WranglerCodegenOptions {
|
|
|
49
49
|
* hatch for extra bindings (KV, R2, D1, queues, service bindings, extra
|
|
50
50
|
* `compatibility_flags`, cron `triggers`, …). Arrays concat; new keys are
|
|
51
51
|
* added as-is. The adapter-owned keys `name`, `main`, `assets`, `routes` are
|
|
52
|
-
* rejected (throws) — use `workerName` / `route` instead
|
|
52
|
+
* rejected (throws) — use `workerName` / `route` instead — and so are the keys
|
|
53
|
+
* and compatibility flags Wrangler's bundler reads.
|
|
53
54
|
*/
|
|
54
55
|
wrangler?: Record<string, unknown>;
|
|
55
56
|
}
|
|
@@ -25,6 +25,7 @@ export function generateWranglerConfig(opts) {
|
|
|
25
25
|
workers_dev: opts.route === undefined,
|
|
26
26
|
version_metadata: { binding: 'ASTRALE_VERSION_METADATA' },
|
|
27
27
|
observability: { enabled: true },
|
|
28
|
+
minify: true,
|
|
28
29
|
};
|
|
29
30
|
if (opts.route) {
|
|
30
31
|
config.routes = [{ pattern: opts.route, custom_domain: true }];
|
|
@@ -78,9 +79,30 @@ const ADAPTER_OWNED_KEYS = [
|
|
|
78
79
|
'find_additional_modules',
|
|
79
80
|
'base_dir',
|
|
80
81
|
];
|
|
82
|
+
/**
|
|
83
|
+
* Keys Wrangler's bundler reads. Bundling sees the adapter base configuration only, so that every
|
|
84
|
+
* Environment of one source bundles the same bytes; an Environment's overlay never sets these.
|
|
85
|
+
*/
|
|
86
|
+
const BUNDLING_KEYS = [
|
|
87
|
+
'alias',
|
|
88
|
+
'build',
|
|
89
|
+
'define',
|
|
90
|
+
'jsx_factory',
|
|
91
|
+
'jsx_fragment',
|
|
92
|
+
'keep_names',
|
|
93
|
+
'minify',
|
|
94
|
+
'no_bundle',
|
|
95
|
+
'node_compat',
|
|
96
|
+
'preserve_file_names',
|
|
97
|
+
'rules',
|
|
98
|
+
'tsconfig',
|
|
99
|
+
'upload_source_maps',
|
|
100
|
+
];
|
|
101
|
+
/** Compatibility flags Wrangler's bundler reads: Node.js polyfills, `navigator`, fetch typing, Python. */
|
|
102
|
+
const BUNDLING_FLAG = /nodejs|navigator|fetch_iterable_type_support|python_workers/u;
|
|
81
103
|
/**
|
|
82
104
|
* Deep-merge the dev's raw wrangler overlay over the generated config, after
|
|
83
|
-
* refusing any attempt to override an adapter-owned key. Throwing (rather than
|
|
105
|
+
* refusing any attempt to override an adapter-owned key or a bundler input. Throwing (rather than
|
|
84
106
|
* silently dropping) keeps a stray `main` from breaking the Worker unnoticed.
|
|
85
107
|
*/
|
|
86
108
|
function mergeUserConfig(base, user) {
|
|
@@ -90,5 +112,17 @@ function mergeUserConfig(base, user) {
|
|
|
90
112
|
'Use `workerName` for the worker name and `route` for routing; ' +
|
|
91
113
|
'`main` and `assets` are generated by the adapter.');
|
|
92
114
|
}
|
|
115
|
+
const baseFlags = Array.isArray(base.compatibility_flags) ? base.compatibility_flags : [];
|
|
116
|
+
const bundling = [
|
|
117
|
+
...BUNDLING_KEYS.filter((k) => k in user),
|
|
118
|
+
...(Array.isArray(user.compatibility_flags) ? user.compatibility_flags : [])
|
|
119
|
+
.filter((flag) => typeof flag === 'string' && BUNDLING_FLAG.test(flag))
|
|
120
|
+
.filter((flag) => !baseFlags.includes(flag))
|
|
121
|
+
.map((flag) => `compatibility_flags ${String(flag)}`),
|
|
122
|
+
];
|
|
123
|
+
if (bundling.length > 0) {
|
|
124
|
+
throw new Error(`wrangler: ${bundling.join(', ')} shape(s) the bundle, which takes no Environment. ` +
|
|
125
|
+
'Bundling uses the adapter base configuration only.');
|
|
126
|
+
}
|
|
93
127
|
return deepMergeConfig(base, user);
|
|
94
128
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { MAXIMUM_INVOCATION_TIMEOUT_MS } from '../../worker/settings.js';
|
|
2
2
|
export function workerInvocationTimeout(value) {
|
|
3
3
|
if (value === undefined)
|
|
4
4
|
return undefined;
|
|
5
|
-
if (!Number.isSafeInteger(value) || value <= 0 || value >
|
|
5
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > MAXIMUM_INVOCATION_TIMEOUT_MS) {
|
|
6
6
|
throw new TypeError('Cloudflare invocationTimeoutMs must be a positive integer within the platform timer range.');
|
|
7
7
|
}
|
|
8
8
|
return value;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CloudflareParams } from '../../../params.js';
|
|
2
|
+
/**
|
|
3
|
+
* The bindings legacy direct mode adds to a stable Worker's deployment: the stable issuer it serves
|
|
4
|
+
* behind its route (`IDENTITY_ISS`), or the workers.dev Worker name a provider-assigned deployment
|
|
5
|
+
* must claim (`ASTRALE_PROVIDER_ASSIGNED_WORKER`). The Worker reads them through
|
|
6
|
+
* `worker/legacy/direct.ts`.
|
|
7
|
+
*
|
|
8
|
+
* @deprecated Legacy direct mode only. Replacement: an immutable deployment, whose host binds
|
|
9
|
+
* `WORKER_URL` to the deployment URL, which is also its issuer (Cloudflare namespace mode).
|
|
10
|
+
* Deleted with direct mode, once the Shell, Services, Issues, domains and ui configurations deploy
|
|
11
|
+
* in namespace mode and no installation pins a direct Worker.
|
|
12
|
+
*/
|
|
13
|
+
export declare function directDeploymentVars(parameters: Pick<CloudflareParams, 'identityIssuer'>, deployment: {
|
|
14
|
+
readonly providerAssignedWorker?: string;
|
|
15
|
+
}): Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bindings legacy direct mode adds to a stable Worker's deployment: the stable issuer it serves
|
|
3
|
+
* behind its route (`IDENTITY_ISS`), or the workers.dev Worker name a provider-assigned deployment
|
|
4
|
+
* must claim (`ASTRALE_PROVIDER_ASSIGNED_WORKER`). The Worker reads them through
|
|
5
|
+
* `worker/legacy/direct.ts`.
|
|
6
|
+
*
|
|
7
|
+
* @deprecated Legacy direct mode only. Replacement: an immutable deployment, whose host binds
|
|
8
|
+
* `WORKER_URL` to the deployment URL, which is also its issuer (Cloudflare namespace mode).
|
|
9
|
+
* Deleted with direct mode, once the Shell, Services, Issues, domains and ui configurations deploy
|
|
10
|
+
* in namespace mode and no installation pins a direct Worker.
|
|
11
|
+
*/
|
|
12
|
+
export function directDeploymentVars(parameters, deployment) {
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
...(parameters.identityIssuer === undefined ? {} : { IDENTITY_ISS: parameters.identityIssuer }),
|
|
15
|
+
...(deployment.providerAssignedWorker === undefined
|
|
16
|
+
? {}
|
|
17
|
+
: { ASTRALE_PROVIDER_ASSIGNED_WORKER: deployment.providerAssignedWorker }),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CloudflareParams } from '../../params.js';
|
|
2
|
+
import type { WorkerSettings } from '../../worker/settings.js';
|
|
3
|
+
/** Whether a binding name is one the adapter owns, so no author var or secret may take it. */
|
|
4
|
+
export declare function isAdapterVar(name: string): boolean;
|
|
5
|
+
/** Resolve one Environment's execution settings, defaults included, as its deployment binds them. */
|
|
6
|
+
export declare function workerSettings(parameters: CloudflareParams): WorkerSettings;
|
|
7
|
+
/**
|
|
8
|
+
* The plain bindings one stable Worker deployment carries: the author's vars, its execution
|
|
9
|
+
* settings and serving origin, and the legacy direct-mode bindings. Never `ASTRALE_BUILD_DIGEST`:
|
|
10
|
+
* only an immutable deployment may answer for a build digest.
|
|
11
|
+
*/
|
|
12
|
+
export declare function deploymentVars(parameters: CloudflareParams, deployment: {
|
|
13
|
+
readonly configuredIssuer?: string;
|
|
14
|
+
readonly providerAssignedWorker?: string;
|
|
15
|
+
}): Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { maximumExchangeTtl } from '@astrale-os/sdk/execution';
|
|
2
|
+
import { captureDurableOptions, DEFAULT_INVOCATION_TIMEOUT_MS, DEFAULT_MAXIMUM_REQUEST_BYTES, encodeWorkerSettings, SETTINGS_BINDING, } from '../../worker/settings.js';
|
|
3
|
+
import { workerInvocationTimeout } from './invocation-timeout.js';
|
|
4
|
+
import { directDeploymentVars } from './legacy/direct.js';
|
|
5
|
+
import { workerRequestLimit } from './request-limit.js';
|
|
6
|
+
import { resolveRouter } from './router.js';
|
|
7
|
+
/**
|
|
8
|
+
* Plain bindings the adapter owns on a deployment, bound or not; no author var or secret takes
|
|
9
|
+
* these names. `IDENTITY_ISS` and `ASTRALE_PROVIDER_ASSIGNED_WORKER` belong to legacy direct mode.
|
|
10
|
+
*/
|
|
11
|
+
const ADAPTER_VARS = new Set([
|
|
12
|
+
'WORKER_URL',
|
|
13
|
+
'ASTRALE_BUILD_DIGEST',
|
|
14
|
+
SETTINGS_BINDING,
|
|
15
|
+
'IDENTITY_ISS',
|
|
16
|
+
'ASTRALE_PROVIDER_ASSIGNED_WORKER',
|
|
17
|
+
]);
|
|
18
|
+
/** Whether a binding name is one the adapter owns, so no author var or secret may take it. */
|
|
19
|
+
export function isAdapterVar(name) {
|
|
20
|
+
return ADAPTER_VARS.has(name);
|
|
21
|
+
}
|
|
22
|
+
/** Resolve one Environment's execution settings, defaults included, as its deployment binds them. */
|
|
23
|
+
export function workerSettings(parameters) {
|
|
24
|
+
const router = resolveRouter(parameters.router);
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
...(parameters.durable === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { durableOptions: captureDurableOptions(parameters.durable) }),
|
|
29
|
+
maximumRequestBytes: workerRequestLimit(parameters.maximumRequestBytes) ?? DEFAULT_MAXIMUM_REQUEST_BYTES,
|
|
30
|
+
invocationTimeoutMs: workerInvocationTimeout(parameters.invocationTimeoutMs) ?? DEFAULT_INVOCATION_TIMEOUT_MS,
|
|
31
|
+
tokenExchange: parameters.tokenExchange === false
|
|
32
|
+
? false
|
|
33
|
+
: Object.freeze({ maximumLifetimeSeconds: maximumExchangeTtl(parameters.tokenExchange) }),
|
|
34
|
+
router: router === undefined
|
|
35
|
+
? false
|
|
36
|
+
: Object.freeze({
|
|
37
|
+
binding: router.binding,
|
|
38
|
+
hostSuffix: router.hostSuffix,
|
|
39
|
+
minLabels: router.minLabels,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The plain bindings one stable Worker deployment carries: the author's vars, its execution
|
|
45
|
+
* settings and serving origin, and the legacy direct-mode bindings. Never `ASTRALE_BUILD_DIGEST`:
|
|
46
|
+
* only an immutable deployment may answer for a build digest.
|
|
47
|
+
*/
|
|
48
|
+
export function deploymentVars(parameters, deployment) {
|
|
49
|
+
const authored = [
|
|
50
|
+
...Object.keys(parameters.vars ?? {}),
|
|
51
|
+
...Object.keys(wranglerVars(parameters.wrangler)),
|
|
52
|
+
];
|
|
53
|
+
const owned = authored.filter(isAdapterVar);
|
|
54
|
+
if (owned.length > 0) {
|
|
55
|
+
throw new TypeError(`Cloudflare var(s) ${owned.join(', ')} are adapter-owned bindings.`);
|
|
56
|
+
}
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
...parameters.vars,
|
|
59
|
+
[SETTINGS_BINDING]: encodeWorkerSettings(workerSettings(parameters)),
|
|
60
|
+
...(deployment.configuredIssuer === undefined
|
|
61
|
+
? {}
|
|
62
|
+
: { WORKER_URL: deployment.configuredIssuer }),
|
|
63
|
+
...directDeploymentVars(parameters, deployment),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function wranglerVars(wrangler) {
|
|
67
|
+
const vars = wrangler?.vars;
|
|
68
|
+
return vars !== null && typeof vars === 'object' && !Array.isArray(vars)
|
|
69
|
+
? vars
|
|
70
|
+
: {};
|
|
71
|
+
}
|
|
@@ -2,4 +2,5 @@ export { deploymentAddressing } from './configuration/addressing.js';
|
|
|
2
2
|
export { workerInvocationTimeout } from './configuration/invocation-timeout.js';
|
|
3
3
|
export { workerRequestLimit } from './configuration/request-limit.js';
|
|
4
4
|
export { resolveRouter } from './configuration/router.js';
|
|
5
|
+
export { deploymentVars, isAdapterVar, workerSettings } from './configuration/settings.js';
|
|
5
6
|
export { workerName } from './configuration/worker-name.js';
|
|
@@ -2,4 +2,5 @@ export { deploymentAddressing } from './configuration/addressing.js';
|
|
|
2
2
|
export { workerInvocationTimeout } from './configuration/invocation-timeout.js';
|
|
3
3
|
export { workerRequestLimit } from './configuration/request-limit.js';
|
|
4
4
|
export { resolveRouter } from './configuration/router.js';
|
|
5
|
+
export { deploymentVars, isAdapterVar, workerSettings } from './configuration/settings.js';
|
|
5
6
|
export { workerName } from './configuration/worker-name.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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>;
|
package/dist/build/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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';
|
|
5
|
-
export {
|
|
6
|
+
export { uploadBuildDigest, uploadBuildEvidence, workerUpload } from './worker/upload.js';
|
|
7
|
+
export { isAdapterVar, resolveRouter, workerName } from './configuration.js';
|
|
6
8
|
export { frontendFiles } from './frontend/files.js';
|
|
7
9
|
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
8
10
|
export { qualifyWorkerBundle } from './qualification.js';
|
package/dist/build/index.js
CHANGED
|
@@ -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
|
-
export {
|
|
5
|
+
export { uploadBuildDigest, uploadBuildEvidence, workerUpload } from './worker/upload.js';
|
|
6
|
+
export { isAdapterVar, resolveRouter, workerName } from './configuration.js';
|
|
5
7
|
export { frontendFiles } from './frontend/files.js';
|
|
6
8
|
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
7
9
|
export { qualifyWorkerBundle } from './qualification.js';
|
|
@@ -5,7 +5,6 @@ export interface ExecutionEntryOptions {
|
|
|
5
5
|
readonly frontend?: BuildMaterial['frontend'];
|
|
6
6
|
readonly frontendRevision?: string;
|
|
7
7
|
readonly frontendAddressed?: readonly string[];
|
|
8
|
-
readonly providerAssignedWorker?: string;
|
|
9
8
|
}
|
|
10
9
|
/** Generate the isolate-startup entry that realizes the heavy Service on first request. */
|
|
11
10
|
export declare function generateExecutionEntry(serviceSpecifier: string, options?: ExecutionEntryOptions): string;
|
|
@@ -12,9 +12,6 @@ export function generateExecutionEntry(serviceSpecifier, options = {}) {
|
|
|
12
12
|
options.frontendAddressed === undefined
|
|
13
13
|
? ''
|
|
14
14
|
: ` frontendAddressed: ${JSON.stringify(options.frontendAddressed)},\n`,
|
|
15
|
-
options.providerAssignedWorker === undefined
|
|
16
|
-
? ''
|
|
17
|
-
: ` providerAssignedWorker: ${JSON.stringify(options.providerAssignedWorker)},\n`,
|
|
18
15
|
].join('');
|
|
19
16
|
return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
|
|
20
17
|
import type { BuildMaterial } from '@astrale-os/sdk/deployment/build'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Build } from '@astrale-os/sdk/deployment/build';
|
|
2
|
-
|
|
2
|
+
/** Build inputs only: the deployment binds every environment value (`deploymentSettings`). */
|
|
3
3
|
export interface ExecutionCodegenOptions {
|
|
4
4
|
readonly origin: string;
|
|
5
5
|
readonly runtimeSpecifier: string;
|
|
@@ -7,15 +7,8 @@ export interface ExecutionCodegenOptions {
|
|
|
7
7
|
readonly build: Build;
|
|
8
8
|
readonly frontendRevision?: string;
|
|
9
9
|
readonly frontendAddressed?: readonly string[];
|
|
10
|
-
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
readonly providerAssignedWorker?: string;
|
|
14
|
-
readonly tokenExchange?: ExchangeOptions | false;
|
|
15
|
-
readonly router?: {
|
|
16
|
-
readonly binding: string;
|
|
17
|
-
readonly hostSuffix: string;
|
|
18
|
-
readonly minLabels: number;
|
|
19
|
-
};
|
|
10
|
+
/** Static Schema capability; deployment options remain in bindings. */
|
|
11
|
+
readonly durable?: boolean;
|
|
12
|
+
readonly restateSpecifier?: string;
|
|
20
13
|
}
|
|
21
14
|
export declare function generateExecutionService(options: ExecutionCodegenOptions): string;
|
|
@@ -9,39 +9,31 @@ export function generateExecutionService(options) {
|
|
|
9
9
|
const material = sharedLiteral(generated);
|
|
10
10
|
verifySharedLiteral(material, generated);
|
|
11
11
|
const materialDeclarations = material.declarations.map((line) => ` ${line}\n`).join('');
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`;
|
|
20
|
-
const routeSubrequest = router === undefined
|
|
21
|
-
? ''
|
|
22
|
-
: `
|
|
23
|
-
routeSubrequest: (url, env) => {
|
|
24
|
-
// Services own their public ingress, including retirement and private-route isolation.
|
|
25
|
-
if (url.hostname.split('.')[1] === 'svc') return null
|
|
26
|
-
if (env.${router.binding} && isInstanceHost(url.hostname)) return env.${router.binding}
|
|
27
|
-
return null
|
|
28
|
-
},`;
|
|
12
|
+
const durableImports = options.durable === true
|
|
13
|
+
? `import { RESTATE_IDENTITY_KEYS_BINDING, restateServing } from ${JSON.stringify(workerSpecifier)}
|
|
14
|
+
import { isCanonicalDurableRequest, isLegacyDurableRequest } from '@astrale-os/sdk/execution'
|
|
15
|
+
// A variable specifier preserves the engine as a separate deployed module.
|
|
16
|
+
const RESTATE_SPECIFIER = ${JSON.stringify(options.restateSpecifier ?? './restate.mjs')}
|
|
17
|
+
`
|
|
18
|
+
: '';
|
|
29
19
|
const frontend = generated.material.frontend === undefined
|
|
30
20
|
? ''
|
|
31
21
|
: ` frontend: ${JSON.stringify(generated.material.frontend)} as const satisfies NonNullable<BuildMaterial['frontend']>,\n`;
|
|
32
22
|
return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
|
|
33
23
|
// Domain origin: ${options.origin}
|
|
34
24
|
import type { BuildMaterial, GeneratedBuildMaterial } from '@astrale-os/sdk/deployment/build'
|
|
35
|
-
import type { CloudflareAdapterBindings } from ${JSON.stringify(workerSpecifier)}
|
|
25
|
+
import type { CloudflareAdapterBindings${options.durable === true ? ', DurableBindings' : ''} } from ${JSON.stringify(workerSpecifier)}
|
|
36
26
|
|
|
37
27
|
import { generatedCloudflareWorkerEntry } from ${JSON.stringify(workerSpecifier)}
|
|
38
|
-
import { decodeSigningIdentity, runtimeBindings } from ${JSON.stringify(workerSpecifier)}
|
|
28
|
+
import { decodeSigningIdentity, deploymentSettings, runtimeBindings } from ${JSON.stringify(workerSpecifier)}
|
|
29
|
+
import { withDirectIssuer } from ${JSON.stringify(workerSpecifier)}
|
|
39
30
|
|
|
40
|
-
|
|
31
|
+
${durableImports}
|
|
32
|
+
type WorkerEnv = CloudflareEnv & CloudflareAdapterBindings${options.durable === true ? ' & DurableBindings' : ''} & {
|
|
41
33
|
readonly ASTRALE_SIGNING_IDENTITY: string
|
|
42
34
|
}
|
|
43
35
|
type RuntimeEnv = Omit<WorkerEnv, 'ASTRALE_SIGNING_IDENTITY'>
|
|
44
|
-
|
|
36
|
+
|
|
45
37
|
export default generatedCloudflareWorkerEntry<WorkerEnv, RuntimeEnv>()({
|
|
46
38
|
${frontend}${options.frontendRevision === undefined ? '' : ` frontendRevision: ${JSON.stringify(options.frontendRevision)},\n`}${options.frontendAddressed === undefined ? '' : ` frontendAddressed: ${JSON.stringify(options.frontendAddressed)},\n`} load: async () => {
|
|
47
39
|
${materialDeclarations} return {
|
|
@@ -51,13 +43,16 @@ ${materialDeclarations} return {
|
|
|
51
43
|
},
|
|
52
44
|
privateKey: (env) => decodeSigningIdentity(env.ASTRALE_SIGNING_IDENTITY),
|
|
53
45
|
resolveEnvironment: runtimeBindings,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
// Legacy direct mode: a bound IDENTITY_ISS signs instead of the serving origin.
|
|
47
|
+
settings: withDirectIssuer(deploymentSettings),
|
|
48
|
+
selfBinding: (env) => env.SELF,${options.durable === true
|
|
49
|
+
? `
|
|
50
|
+
durable: async (env, request, options) => {
|
|
51
|
+
if (!isCanonicalDurableRequest(request) && !(isLegacyDurableRequest(request) && env[RESTATE_IDENTITY_KEYS_BINDING]?.trim())) return undefined
|
|
52
|
+
const restate = await import(RESTATE_SPECIFIER)
|
|
53
|
+
return restateServing(restate, env, options, request)
|
|
54
|
+
},`
|
|
55
|
+
: ''}
|
|
61
56
|
})
|
|
62
57
|
`;
|
|
63
58
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { CloudflareParams } from '../../params.js';
|
|
3
|
-
import type { resolveRouter } from '../configuration.js';
|
|
1
|
+
import type { BundleContext } from '@astrale-os/sdk/deployment/adapter';
|
|
4
2
|
export declare const RELEASE_ENTRY = "bundle/index.mjs";
|
|
5
3
|
export declare const RELEASE_SERVICE = "bundle/service.mjs";
|
|
4
|
+
export declare const RELEASE_RESTATE = "bundle/restate.mjs";
|
|
5
|
+
/** A deployed module retained outside the startup bundle. */
|
|
6
|
+
export interface ReleaseModule {
|
|
7
|
+
readonly kind: 'esm' | 'wasm';
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly content: Uint8Array;
|
|
10
|
+
}
|
|
6
11
|
export declare function prepareExecution(input: {
|
|
7
|
-
readonly
|
|
8
|
-
readonly context: PrepareContext;
|
|
12
|
+
readonly context: BundleContext;
|
|
9
13
|
readonly directory: string;
|
|
10
|
-
readonly name: string;
|
|
11
|
-
readonly direct: boolean;
|
|
12
14
|
readonly frontendRevision?: string;
|
|
13
15
|
readonly frontendAddressed?: readonly string[];
|
|
14
|
-
readonly configuredIssuer?: string;
|
|
15
|
-
readonly router: ReturnType<typeof resolveRouter>;
|
|
16
16
|
}): Promise<{
|
|
17
17
|
readonly entry: Uint8Array;
|
|
18
|
-
readonly
|
|
18
|
+
readonly modules: readonly ReleaseModule[];
|
|
19
19
|
}>;
|
|
20
|
-
export declare function
|
|
20
|
+
export declare function declaresDurable(build: BundleContext['build']): boolean;
|
|
@@ -1,82 +1,83 @@
|
|
|
1
1
|
import { generatedMaterial } from '@astrale-os/sdk/deployment/build';
|
|
2
|
-
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { SIGNING_IDENTITY_BINDING } from '../../identity/binding.js';
|
|
5
5
|
import { runWranglerBundle } from '../../wrangler/bundle.js';
|
|
6
|
-
import { admitWorkerBundle } from '../bundle/admit.js';
|
|
6
|
+
import { admitDurableEngineBundle, admitWorkerBundle } from '../bundle/admit.js';
|
|
7
7
|
import { generateExecutionEntry, generateExecutionService } from '../codegen/execution.js';
|
|
8
8
|
import { generateWranglerConfig } from '../codegen/wrangler.js';
|
|
9
|
-
import { workerInvocationTimeout, workerRequestLimit } from '../configuration.js';
|
|
10
9
|
import { qualifyWorkerBundle } from '../qualification.js';
|
|
11
|
-
import {
|
|
10
|
+
import { workerModules } from './modules.js';
|
|
11
|
+
import { frontModuleSpecifier, restateModuleSpecifier, workerModuleSpecifier } from './specifier.js';
|
|
12
12
|
export const RELEASE_ENTRY = 'bundle/index.mjs';
|
|
13
13
|
export const RELEASE_SERVICE = 'bundle/service.mjs';
|
|
14
|
+
export const RELEASE_RESTATE = 'bundle/restate.mjs';
|
|
15
|
+
/**
|
|
16
|
+
* Bundling sees the adapter base configuration only: no Environment, route, var, router or
|
|
17
|
+
* `wrangler` overlay reaches the bytes, so every Environment of one source bundles identically.
|
|
18
|
+
*/
|
|
19
|
+
const BUNDLE_WORKER_NAME = 'astrale-domain';
|
|
14
20
|
export async function prepareExecution(input) {
|
|
15
21
|
const runtimePath = resolve(input.context.projectDir, input.context.runtime.reference.path);
|
|
16
22
|
const runtimeSpecifier = moduleSpecifier(relative(input.directory, runtimePath));
|
|
17
23
|
const workerSpecifier = workerModuleSpecifier();
|
|
18
|
-
const maximumRequestBytes = workerRequestLimit(input.params.maximumRequestBytes);
|
|
19
|
-
const invocationTimeoutMs = workerInvocationTimeout(input.params.invocationTimeoutMs);
|
|
20
24
|
const serviceSource = generateExecutionService({
|
|
21
25
|
origin: input.context.build.schema.compiled.root.origin,
|
|
22
|
-
...(input.params.identityIssuer === undefined
|
|
23
|
-
? {}
|
|
24
|
-
: { identityIssuer: input.params.identityIssuer }),
|
|
25
26
|
runtimeSpecifier,
|
|
26
27
|
workerSpecifier,
|
|
27
28
|
build: input.context.build,
|
|
29
|
+
...(declaresDurable(input.context.build)
|
|
30
|
+
? { durable: true, restateSpecifier: './restate.mjs' }
|
|
31
|
+
: {}),
|
|
28
32
|
...(input.frontendRevision === undefined ? {} : { frontendRevision: input.frontendRevision }),
|
|
29
33
|
...(input.frontendAddressed === undefined
|
|
30
34
|
? {}
|
|
31
35
|
: { frontendAddressed: input.frontendAddressed }),
|
|
32
|
-
...(maximumRequestBytes === undefined ? {} : { maximumRequestBytes }),
|
|
33
|
-
...(invocationTimeoutMs === undefined ? {} : { invocationTimeoutMs }),
|
|
34
|
-
...(input.params.tokenExchange === undefined
|
|
35
|
-
? {}
|
|
36
|
-
: { tokenExchange: input.params.tokenExchange }),
|
|
37
|
-
...(input.configuredIssuer === undefined && input.direct
|
|
38
|
-
? { providerAssignedWorker: input.name }
|
|
39
|
-
: {}),
|
|
40
|
-
...(input.router === undefined
|
|
41
|
-
? {}
|
|
42
|
-
: {
|
|
43
|
-
router: {
|
|
44
|
-
binding: input.router.binding,
|
|
45
|
-
hostSuffix: input.router.hostSuffix,
|
|
46
|
-
minLabels: input.router.minLabels,
|
|
47
|
-
},
|
|
48
|
-
}),
|
|
49
|
-
});
|
|
50
|
-
const serviceSourcePath = join(input.directory, 'service.gen.ts');
|
|
51
|
-
await writeFile(serviceSourcePath, serviceSource);
|
|
52
|
-
const serviceConfigPath = join(input.directory, 'wrangler.service.prepare.jsonc');
|
|
53
|
-
await writeFile(serviceConfigPath, generateWranglerConfig({
|
|
54
|
-
workerName: input.name,
|
|
55
|
-
main: './service.gen.ts',
|
|
56
|
-
vars: runtimeVars(input.params, input.configuredIssuer),
|
|
57
|
-
secretBindings: [SIGNING_IDENTITY_BINDING],
|
|
58
|
-
...(input.router === undefined
|
|
59
|
-
? {}
|
|
60
|
-
: { router: { binding: input.router.binding, service: input.router.service } }),
|
|
61
|
-
...(input.params.wrangler === undefined ? {} : { wrangler: input.params.wrangler }),
|
|
62
|
-
selfBinding: false,
|
|
63
|
-
}));
|
|
64
|
-
const serviceOutput = join(input.directory, 'dist-service');
|
|
65
|
-
await rm(serviceOutput, { recursive: true, force: true });
|
|
66
|
-
const emittedService = await runWranglerBundle({
|
|
67
|
-
projectDir: input.context.projectDir,
|
|
68
|
-
configPath: serviceConfigPath,
|
|
69
|
-
outDir: serviceOutput,
|
|
70
|
-
signal: input.context.signal,
|
|
71
36
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
37
|
+
const modules = [];
|
|
38
|
+
const bundle = async (name, path, source, admit) => {
|
|
39
|
+
const sourcePath = join(input.directory, `${name}.gen.ts`);
|
|
40
|
+
await writeFile(sourcePath, source);
|
|
41
|
+
const configPath = join(input.directory, `wrangler.${name}.prepare.jsonc`);
|
|
42
|
+
await writeFile(configPath, generateWranglerConfig({
|
|
43
|
+
workerName: BUNDLE_WORKER_NAME,
|
|
44
|
+
main: `./${name}.gen.ts`,
|
|
45
|
+
secretBindings: [SIGNING_IDENTITY_BINDING],
|
|
46
|
+
selfBinding: false,
|
|
47
|
+
}));
|
|
48
|
+
const output = join(input.directory, `dist-${name}`);
|
|
49
|
+
await rm(output, { recursive: true, force: true });
|
|
50
|
+
const emitted = await runWranglerBundle({
|
|
51
|
+
projectDir: input.context.projectDir,
|
|
52
|
+
configPath,
|
|
53
|
+
outDir: output,
|
|
54
|
+
signal: input.context.signal,
|
|
55
|
+
});
|
|
56
|
+
await qualifyWorkerBundle({
|
|
57
|
+
metafilePath: emitted.metafilePath,
|
|
58
|
+
inputRoot: dirname(configPath),
|
|
59
|
+
});
|
|
60
|
+
await admit(emitted.bundlePath);
|
|
61
|
+
const content = new Uint8Array(await readFile(emitted.bundlePath));
|
|
62
|
+
await mkdir(dirname(join(input.directory, path)), { recursive: true });
|
|
63
|
+
await writeFile(join(input.directory, path), content);
|
|
64
|
+
modules.push(Object.freeze({ path, kind: 'esm', content }));
|
|
65
|
+
// Imported WASM modules are emitted beside their owner under content-hashed names.
|
|
66
|
+
for (const file of (await readdir(output)).filter((name) => name.endsWith('.wasm')).sort()) {
|
|
67
|
+
const wasmPath = `${dirname(path)}/${file}`;
|
|
68
|
+
const content = new Uint8Array(await readFile(join(output, file)));
|
|
69
|
+
await writeFile(join(input.directory, wasmPath), content);
|
|
70
|
+
if (!modules.some((module) => module.path === wasmPath)) {
|
|
71
|
+
modules.push(Object.freeze({ path: wasmPath, kind: 'wasm', content }));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
await bundle('service', RELEASE_SERVICE, serviceSource, admitWorkerBundle);
|
|
76
|
+
if (declaresDurable(input.context.build)) {
|
|
77
|
+
await bundle('restate', RELEASE_RESTATE,
|
|
78
|
+
// Wrangler requires a default export to classify an independently bundled ESM entry.
|
|
79
|
+
`export { createEndpointHandler, workflow, TerminalError, CancelledError } from ${JSON.stringify(restateModuleSpecifier())}\nexport default {}\n`, admitDurableEngineBundle);
|
|
80
|
+
}
|
|
80
81
|
const sourcePath = join(input.directory, 'worker.gen.ts');
|
|
81
82
|
const frontend = generatedMaterial(input.context.build).material.frontend;
|
|
82
83
|
await writeFile(sourcePath, generateExecutionEntry('./service.mjs', {
|
|
@@ -87,21 +88,12 @@ export async function prepareExecution(input) {
|
|
|
87
88
|
...(input.frontendAddressed === undefined
|
|
88
89
|
? {}
|
|
89
90
|
: { frontendAddressed: input.frontendAddressed }),
|
|
90
|
-
...(input.configuredIssuer === undefined && input.direct
|
|
91
|
-
? { providerAssignedWorker: input.name }
|
|
92
|
-
: {}),
|
|
93
91
|
}));
|
|
94
92
|
const configPath = join(input.directory, 'wrangler.prepare.jsonc');
|
|
95
93
|
await writeFile(configPath, generateWranglerConfig({
|
|
96
|
-
workerName:
|
|
97
|
-
...(input.params.route === undefined ? {} : { route: input.params.route }),
|
|
98
|
-
vars: runtimeVars(input.params, input.configuredIssuer),
|
|
94
|
+
workerName: BUNDLE_WORKER_NAME,
|
|
99
95
|
secretBindings: [SIGNING_IDENTITY_BINDING],
|
|
100
|
-
modules: {
|
|
101
|
-
...(input.router === undefined
|
|
102
|
-
? {}
|
|
103
|
-
: { router: { binding: input.router.binding, service: input.router.service } }),
|
|
104
|
-
...(input.params.wrangler === undefined ? {} : { wrangler: input.params.wrangler }),
|
|
96
|
+
modules: workerModules({ entrypoint: 'worker.gen.ts', modules }),
|
|
105
97
|
selfBinding: false,
|
|
106
98
|
}));
|
|
107
99
|
const output = join(input.directory, 'dist-prepare');
|
|
@@ -119,16 +111,19 @@ export async function prepareExecution(input) {
|
|
|
119
111
|
await admitWorkerBundle(emitted.bundlePath);
|
|
120
112
|
return Object.freeze({
|
|
121
113
|
entry: new Uint8Array(await readFile(emitted.bundlePath)),
|
|
122
|
-
|
|
114
|
+
modules: Object.freeze(modules),
|
|
123
115
|
});
|
|
124
116
|
}
|
|
125
|
-
export function runtimeVars(params, configuredIssuer) {
|
|
126
|
-
return {
|
|
127
|
-
...params.vars,
|
|
128
|
-
...(configuredIssuer === undefined ? {} : { WORKER_URL: configuredIssuer }),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
117
|
function moduleSpecifier(input) {
|
|
132
118
|
const normalized = input.split('\\').join('/');
|
|
133
119
|
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
134
120
|
}
|
|
121
|
+
export function declaresDurable(build) {
|
|
122
|
+
const compiled = build.schema.compiled;
|
|
123
|
+
const root = compiled.closure.find(({ origin }) => origin === compiled.root.origin);
|
|
124
|
+
if (root === undefined)
|
|
125
|
+
return false;
|
|
126
|
+
return (root.definitions.functions.some((definition) => definition.execution?.durable === true) ||
|
|
127
|
+
root.definitions.classes.some((definition) => 'methods' in definition &&
|
|
128
|
+
definition.methods.some((method) => method.execution?.durable === true)));
|
|
129
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { WorkerArtifact } from './artifact.js';
|
|
2
2
|
type WranglerModuleType = 'ESModule' | 'CompiledWasm' | 'Text' | 'Data';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Project one admitted Worker module inventory into Wrangler's relative module discovery. Rules
|
|
5
|
+
* follow module kinds, so the projection does not depend on the inventory's order.
|
|
6
|
+
*/
|
|
7
|
+
export declare function workerModules(worker: {
|
|
8
|
+
readonly entrypoint: string;
|
|
9
|
+
readonly modules: readonly Pick<WorkerArtifact['release']['modules'][number], 'path' | 'kind'>[];
|
|
10
|
+
}): {
|
|
5
11
|
readonly baseDir: string;
|
|
6
12
|
readonly rules: readonly {
|
|
7
13
|
readonly type: WranglerModuleType;
|