@astrale-os/adapter-cloudflare 0.5.0-beta.163 → 0.5.0-beta.165
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 +35 -0
- package/dist/build/artifact.js +25 -23
- 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/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/index.d.ts +1 -1
- package/dist/build/index.js +1 -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 +9 -9
- 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/deploy/deploy.js +10 -1
- package/dist/index.d.ts +1 -1
- package/dist/params.d.ts +19 -3
- 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 +78 -55
- package/dist/worker/front.d.ts +23 -1
- package/dist/worker/front.js +3 -14
- package/dist/worker/frontend.js +1 -1
- 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 +8 -3
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ 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;
|
|
@@ -76,6 +80,34 @@ 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
|
+
|
|
79
111
|
## Material for a custom Worker entrypoint
|
|
80
112
|
|
|
81
113
|
Workers with provider-specific routes or environment initialization can reuse the adapter's
|
|
@@ -91,6 +123,9 @@ await writeFile('src/build-material.gen.ts', generateBuildMaterialModule(build))
|
|
|
91
123
|
|
|
92
124
|
The generated TypeScript module exports `loadBuildMaterial()`. Call it from the
|
|
93
125
|
`load` callback of `generatedCloudflareWorkerEntry` alongside the application's Runtime.
|
|
126
|
+
Such an entrypoint states its execution settings (`invocationTimeoutMs`, `maximumRequestBytes`,
|
|
127
|
+
`tokenExchange`, `routeSubrequest`, `identityIssuer`) in its configuration, or reads them from its
|
|
128
|
+
deployment bindings with `settings: deploymentSettings`; one Worker never takes both.
|
|
94
129
|
It constructs the same verified, deduplicated material as the adapter's generated Service;
|
|
95
130
|
allocation is deferred until the factory is called and the SDK deep-freezes it before use.
|
|
96
131
|
The generated file needs only a type import from the SDK. Keep its compiler and runtime SDK
|
package/dist/build/artifact.js
CHANGED
|
@@ -4,11 +4,12 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { SIGNING_IDENTITY_BINDING } from '../identity/binding.js';
|
|
5
5
|
import { workerDeployment } from '../release/index.js';
|
|
6
6
|
import { generateWranglerConfig } from './codegen/wrangler.js';
|
|
7
|
-
import { deploymentAddressing, resolveRouter, workerName } from './configuration.js';
|
|
7
|
+
import { deploymentAddressing, deploymentVars, resolveRouter, workerName } from './configuration.js';
|
|
8
8
|
import { frontendFiles } from './frontend/files.js';
|
|
9
9
|
import { frontendRevision } from './frontend/revision.js';
|
|
10
10
|
import { encodeWorkerArtifactManifest, inferMediaType, workerContentDigest, WORKER_MANIFEST_PATH, } from './worker/artifact.manifest.js';
|
|
11
|
-
import { prepareExecution, RELEASE_ENTRY
|
|
11
|
+
import { prepareExecution, RELEASE_ENTRY } from './worker/execution.prepare.js';
|
|
12
|
+
import { workerModules } from './worker/modules.js';
|
|
12
13
|
import { admitWorkerRuntimeConfiguration } from './worker/runtime.js';
|
|
13
14
|
export { materialize } from './materialize.js';
|
|
14
15
|
const encoder = new TextEncoder();
|
|
@@ -29,6 +30,11 @@ async function prepare(params, context, direct) {
|
|
|
29
30
|
const name = workerName(params, context.build.schema.compiled.root.origin, context.environment);
|
|
30
31
|
const revision = context.build.schema.compiled.root.revision;
|
|
31
32
|
const configuredIssuer = deploymentAddressing(params);
|
|
33
|
+
// The Environment enters only as the deployment's bindings, never the bundled bytes.
|
|
34
|
+
const vars = deploymentVars(params, {
|
|
35
|
+
...(configuredIssuer === undefined ? {} : { configuredIssuer }),
|
|
36
|
+
...(direct && configuredIssuer === undefined ? { providerAssignedWorker: name } : {}),
|
|
37
|
+
});
|
|
32
38
|
// `frontend` stays the served file list every branch below already reads; `addressed` rides
|
|
33
39
|
// alongside it into the Worker, where it decides which of those files a browser may retain.
|
|
34
40
|
const { files: frontend, addressed } = await frontendFiles({
|
|
@@ -39,23 +45,25 @@ async function prepare(params, context, direct) {
|
|
|
39
45
|
signal: context.signal,
|
|
40
46
|
});
|
|
41
47
|
const modules = await prepareExecution({
|
|
42
|
-
params,
|
|
43
48
|
context,
|
|
44
49
|
directory,
|
|
45
|
-
name,
|
|
46
|
-
direct,
|
|
47
|
-
configuredIssuer,
|
|
48
|
-
router,
|
|
49
50
|
...(frontend.length === 0 ? {} : { frontendRevision: frontendRevision(frontend) }),
|
|
50
51
|
...(addressed.length === 0 ? {} : { frontendAddressed: addressed }),
|
|
51
52
|
});
|
|
53
|
+
const release = {
|
|
54
|
+
entrypoint: RELEASE_ENTRY,
|
|
55
|
+
modules: [
|
|
56
|
+
{ path: RELEASE_ENTRY, kind: 'esm', content: modules.entry },
|
|
57
|
+
...modules.modules,
|
|
58
|
+
],
|
|
59
|
+
};
|
|
52
60
|
const releaseConfig = generateWranglerConfig({
|
|
53
61
|
workerName: name,
|
|
54
62
|
main: `./${RELEASE_ENTRY}`,
|
|
55
63
|
...(params.route === undefined ? {} : { route: params.route }),
|
|
56
|
-
vars
|
|
64
|
+
vars,
|
|
57
65
|
secretBindings: [SIGNING_IDENTITY_BINDING],
|
|
58
|
-
modules:
|
|
66
|
+
modules: workerModules(release),
|
|
59
67
|
...(context.build.frontend?.source.kind === 'vite'
|
|
60
68
|
? {
|
|
61
69
|
frontend: {
|
|
@@ -71,13 +79,7 @@ async function prepare(params, context, direct) {
|
|
|
71
79
|
});
|
|
72
80
|
const runtime = admitWorkerRuntimeConfiguration(releaseConfig);
|
|
73
81
|
const deployment = workerDeployment({
|
|
74
|
-
release
|
|
75
|
-
entrypoint: RELEASE_ENTRY,
|
|
76
|
-
modules: [
|
|
77
|
-
{ path: RELEASE_ENTRY, kind: 'esm', content: modules.entry },
|
|
78
|
-
{ path: RELEASE_SERVICE, kind: 'esm', content: modules.service },
|
|
79
|
-
],
|
|
80
|
-
},
|
|
82
|
+
release,
|
|
81
83
|
runtime,
|
|
82
84
|
bindings: {
|
|
83
85
|
vars: runtime.vars,
|
|
@@ -107,11 +109,12 @@ async function prepare(params, context, direct) {
|
|
|
107
109
|
revision,
|
|
108
110
|
deployment,
|
|
109
111
|
release: {
|
|
110
|
-
entrypoint:
|
|
111
|
-
modules:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
entrypoint: release.entrypoint,
|
|
113
|
+
modules: release.modules.map(({ path, kind, content }) => ({
|
|
114
|
+
path,
|
|
115
|
+
kind,
|
|
116
|
+
digest: workerContentDigest(content),
|
|
117
|
+
})),
|
|
115
118
|
},
|
|
116
119
|
runtime: {
|
|
117
120
|
compatibilityDate: runtime.compatibilityDate,
|
|
@@ -142,8 +145,7 @@ async function prepare(params, context, direct) {
|
|
|
142
145
|
}),
|
|
143
146
|
});
|
|
144
147
|
const files = [
|
|
145
|
-
{ path
|
|
146
|
-
{ path: RELEASE_SERVICE, content: modules.service },
|
|
148
|
+
...release.modules.map(({ path, content }) => ({ path, content })),
|
|
147
149
|
{ path: WORKER_MANIFEST_PATH, content: manifest },
|
|
148
150
|
...frontend.map((file) => ({
|
|
149
151
|
path: `frontend/${file.path}`,
|
|
@@ -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
|
+
`)}`;
|
|
@@ -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';
|
package/dist/build/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { generateBuildMaterialModule } from './material.js';
|
|
|
2
2
|
export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
3
3
|
export { loadWorkerArtifact } from './worker/index.js';
|
|
4
4
|
export type { WorkerArtifact } from './worker/index.js';
|
|
5
|
-
export { resolveRouter, workerName } from './configuration.js';
|
|
5
|
+
export { isAdapterVar, resolveRouter, workerName } from './configuration.js';
|
|
6
6
|
export { frontendFiles } from './frontend/files.js';
|
|
7
7
|
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
8
8
|
export { qualifyWorkerBundle } from './qualification.js';
|
package/dist/build/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { generateBuildMaterialModule } from './material.js';
|
|
2
2
|
export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
3
3
|
export { loadWorkerArtifact } from './worker/index.js';
|
|
4
|
-
export { resolveRouter, workerName } from './configuration.js';
|
|
4
|
+
export { isAdapterVar, resolveRouter, workerName } from './configuration.js';
|
|
5
5
|
export { frontendFiles } from './frontend/files.js';
|
|
6
6
|
export { verifyWorkerFrontend } from './frontend/readiness.js';
|
|
7
7
|
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
|
}
|