@astrale-os/adapter-cloudflare 0.5.0-beta.3 → 0.5.0-beta.30
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 +11 -0
- package/dist/adapter.d.ts +17 -0
- package/dist/adapter.js +174 -0
- package/dist/build/artifact.d.ts +7 -0
- package/dist/build/artifact.js +150 -0
- package/dist/build/bundle/admit.d.ts +1 -0
- package/dist/build/bundle/admit.js +9 -0
- package/dist/build/closure.d.ts +2 -0
- package/dist/build/closure.js +22 -0
- package/dist/build/codegen/execution.d.ts +3 -0
- package/dist/build/codegen/execution.js +2 -0
- package/dist/build/codegen/gateway.d.ts +1 -0
- package/dist/build/codegen/gateway.js +1 -0
- package/dist/build/codegen/merge.d.ts +0 -1
- package/dist/build/codegen/merge.js +0 -1
- package/dist/build/codegen/wrangler.d.ts +15 -1
- package/dist/build/codegen/wrangler.js +35 -16
- package/dist/build/configuration/addressing.d.ts +2 -0
- package/dist/build/configuration/addressing.js +11 -0
- package/dist/build/configuration/router.d.ts +7 -0
- package/dist/build/configuration/router.js +11 -0
- package/dist/build/configuration/worker-name.d.ts +2 -0
- package/dist/build/configuration/worker-name.js +18 -0
- package/dist/build/configuration.d.ts +3 -0
- package/dist/build/configuration.js +3 -0
- package/dist/build/direct.d.ts +4 -0
- package/dist/build/direct.js +52 -0
- package/dist/build/frontend/routes.d.ts +2 -0
- package/dist/build/frontend/routes.js +11 -0
- package/dist/build/frontend-v1.d.ts +16 -0
- package/dist/build/frontend-v1.js +133 -0
- package/dist/build/gateway/prepare.d.ts +8 -0
- package/dist/build/gateway/prepare.js +32 -0
- package/dist/build/gateway/source.d.ts +1 -0
- package/dist/build/gateway/source.js +7 -0
- package/dist/build/index.d.ts +10 -16
- package/dist/build/index.js +9 -16
- package/dist/build/materialize.d.ts +4 -0
- package/dist/build/materialize.js +10 -0
- package/dist/build/metafile.d.ts +5 -0
- package/dist/build/metafile.js +14 -0
- package/dist/build/parse-output.d.ts +0 -1
- package/dist/build/parse-output.js +0 -1
- package/dist/build/qualification.d.ts +5 -0
- package/dist/build/qualification.js +7 -0
- package/dist/build/roots.d.ts +2 -0
- package/dist/build/roots.js +34 -0
- package/dist/build/worker/artifact.d.ts +38 -0
- package/dist/build/worker/artifact.js +1 -0
- package/dist/build/worker/artifact.load.d.ts +3 -0
- package/dist/build/worker/artifact.load.js +266 -0
- package/dist/build/worker/artifact.manifest.d.ts +46 -0
- package/dist/build/worker/artifact.manifest.js +84 -0
- package/dist/build/worker/artifact.paths.d.ts +4 -0
- package/dist/build/worker/artifact.paths.js +25 -0
- package/dist/build/worker/codegen/entry.d.ts +2 -0
- package/dist/build/worker/codegen/entry.js +28 -0
- package/dist/build/worker/codegen/service.d.ts +13 -0
- package/dist/build/worker/codegen/service.js +44 -0
- package/dist/build/worker/execution.prepare.d.ts +18 -0
- package/dist/build/worker/execution.prepare.js +102 -0
- package/dist/build/worker/index.d.ts +2 -0
- package/dist/build/worker/index.js +1 -0
- package/dist/build/worker/runtime.d.ts +10 -0
- package/dist/build/worker/runtime.js +35 -0
- package/dist/build/wrangler/bundle.d.ts +10 -0
- package/dist/build/wrangler/bundle.js +29 -0
- package/dist/build/wrangler/deploy.d.ts +23 -0
- package/dist/build/wrangler/deploy.js +35 -0
- package/dist/build/wrangler/development.d.ts +15 -0
- package/dist/build/wrangler/development.js +135 -0
- package/dist/build/wrangler/namespace.d.ts +10 -0
- package/dist/build/wrangler/namespace.js +26 -0
- package/dist/build/wrangler/process.d.ts +15 -0
- package/dist/build/wrangler/process.js +103 -0
- package/dist/build/wrangler/release.d.ts +11 -0
- package/dist/build/wrangler/release.js +27 -0
- package/dist/build/wrangler/secrets.d.ts +6 -0
- package/dist/build/wrangler/secrets.js +58 -0
- package/dist/build/wrangler/status.d.ts +14 -0
- package/dist/build/wrangler/status.js +56 -0
- package/dist/build/wrangler/type-generation.d.ts +6 -0
- package/dist/build/wrangler/type-generation.js +19 -0
- package/dist/build/wrangler-cli.d.ts +8 -86
- package/dist/build/wrangler-cli.js +8 -275
- package/dist/cloudflare.d.ts +1 -34
- package/dist/cloudflare.js +1 -205
- package/dist/deploy/deploy.d.ts +17 -0
- package/dist/deploy/deploy.js +234 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/identity/binding.d.ts +2 -0
- package/dist/identity/binding.js +2 -0
- package/dist/identity/material.d.ts +7 -0
- package/dist/identity/material.js +42 -0
- package/dist/identity/secret.d.ts +3 -0
- package/dist/identity/secret.js +7 -0
- package/dist/identity/source.d.ts +26 -0
- package/dist/identity/source.js +98 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -4
- package/dist/params.d.ts +18 -5
- package/dist/params.js +0 -1
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/namespace.d.ts +2 -0
- package/dist/release/namespace.js +6 -0
- package/dist/release/target.d.ts +2 -0
- package/dist/release/target.js +9 -0
- package/dist/worker/entry.d.ts +38 -0
- package/dist/worker/entry.js +161 -0
- package/dist/worker/fetch.d.ts +11 -0
- package/dist/worker/fetch.js +31 -0
- package/dist/worker/gateway.d.ts +15 -0
- package/dist/worker/gateway.js +26 -0
- package/dist/worker/identity.d.ts +7 -0
- package/dist/worker/identity.js +23 -0
- package/dist/worker/index.d.ts +3 -59
- package/dist/worker/index.js +3 -209
- package/package.json +21 -18
- package/template/astrale.config.ts +19 -11
- package/dist/build/codegen/identity.d.ts +0 -10
- package/dist/build/codegen/identity.d.ts.map +0 -1
- package/dist/build/codegen/identity.js +0 -32
- package/dist/build/codegen/identity.js.map +0 -1
- package/dist/build/codegen/merge.d.ts.map +0 -1
- package/dist/build/codegen/merge.js.map +0 -1
- package/dist/build/codegen/worker.d.ts +0 -12
- package/dist/build/codegen/worker.d.ts.map +0 -1
- package/dist/build/codegen/worker.js +0 -40
- package/dist/build/codegen/worker.js.map +0 -1
- package/dist/build/codegen/wrangler.d.ts.map +0 -1
- package/dist/build/codegen/wrangler.js.map +0 -1
- package/dist/build/frontend.d.ts +0 -26
- package/dist/build/frontend.d.ts.map +0 -1
- package/dist/build/frontend.js +0 -263
- package/dist/build/frontend.js.map +0 -1
- package/dist/build/index.d.ts.map +0 -1
- package/dist/build/index.js.map +0 -1
- package/dist/build/parse-output.d.ts.map +0 -1
- package/dist/build/parse-output.js.map +0 -1
- package/dist/build/prepare.d.ts +0 -18
- package/dist/build/prepare.d.ts.map +0 -1
- package/dist/build/prepare.js +0 -117
- package/dist/build/prepare.js.map +0 -1
- package/dist/build/worker.d.ts +0 -15
- package/dist/build/worker.d.ts.map +0 -1
- package/dist/build/worker.js +0 -21
- package/dist/build/worker.js.map +0 -1
- package/dist/build/wrangler-cli.d.ts.map +0 -1
- package/dist/build/wrangler-cli.js.map +0 -1
- package/dist/cloudflare.d.ts.map +0 -1
- package/dist/cloudflare.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/params.js.map +0 -1
- package/dist/worker/index.d.ts.map +0 -1
- package/dist/worker/index.js.map +0 -1
- package/src/build/codegen/identity.ts +0 -40
- package/src/build/codegen/merge.ts +0 -92
- package/src/build/codegen/worker.ts +0 -53
- package/src/build/codegen/wrangler.ts +0 -127
- package/src/build/frontend.ts +0 -324
- package/src/build/index.ts +0 -29
- package/src/build/parse-output.ts +0 -60
- package/src/build/prepare.ts +0 -161
- package/src/build/worker.ts +0 -37
- package/src/build/wrangler-cli.ts +0 -362
- package/src/cloudflare.ts +0 -251
- package/src/index.ts +0 -21
- package/src/params.ts +0 -92
- package/src/worker/index.ts +0 -312
- package/src/worker/tsconfig.json +0 -10
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { issuer } from '@astrale-os/sdk/auth';
|
|
2
|
+
import { restoreBuild } from '@astrale-os/sdk/deployment/build/material';
|
|
3
|
+
import { addressing, assemble } from '@astrale-os/sdk/deployment/release/runtime';
|
|
4
|
+
import { createIdentity, serve } from '@astrale-os/sdk/execution';
|
|
5
|
+
import { dispatchCloudflareRequest, prepareCloudflareRequest } from './fetch.js';
|
|
6
|
+
/** Compose one exact revision Service; the provider gateway owns revision selection. */
|
|
7
|
+
export function cloudflareWorkerEntry() {
|
|
8
|
+
return function (config) {
|
|
9
|
+
let currentIssuer;
|
|
10
|
+
let realized;
|
|
11
|
+
const realize = () => {
|
|
12
|
+
if (realized !== undefined)
|
|
13
|
+
return realized;
|
|
14
|
+
realized = Promise.resolve()
|
|
15
|
+
.then(config.load)
|
|
16
|
+
.then((source) => {
|
|
17
|
+
const build = restoreBuild(source.material, source.runtime);
|
|
18
|
+
const application = serve({
|
|
19
|
+
runtime: source.runtime,
|
|
20
|
+
load(environment) {
|
|
21
|
+
if (currentIssuer !== environment.issuer) {
|
|
22
|
+
throw new TypeError('Cloudflare Worker request origin differs from its current Release.');
|
|
23
|
+
}
|
|
24
|
+
return assemble(build, addressing(environment.issuer));
|
|
25
|
+
},
|
|
26
|
+
initialize: (environment) => config.resolveEnvironment === undefined
|
|
27
|
+
? environment.bindings
|
|
28
|
+
: config.resolveEnvironment(environment.bindings),
|
|
29
|
+
identity(environment) {
|
|
30
|
+
return createIdentity({
|
|
31
|
+
issuer: issuer.accept(environment.issuer),
|
|
32
|
+
subject: build.schema.compiled.root.origin,
|
|
33
|
+
privateKey: typeof config.privateKey === 'function'
|
|
34
|
+
? config.privateKey(environment.bindings)
|
|
35
|
+
: config.privateKey,
|
|
36
|
+
fetch: routedFetch(application, environment, config),
|
|
37
|
+
...(new URL(environment.issuer).protocol === 'http:'
|
|
38
|
+
? { allowInsecureHttp: true }
|
|
39
|
+
: {}),
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
server: {
|
|
43
|
+
health: { probe: { check: () => true } },
|
|
44
|
+
limits: {
|
|
45
|
+
maximumRequestBytes: 1024 * 1024,
|
|
46
|
+
maximumPublicationBytes: 1024 * 1024,
|
|
47
|
+
maximumBundleBytes: 8 * 1024 * 1024,
|
|
48
|
+
maximumDeliveryBytes: 8 * 1024 * 1024,
|
|
49
|
+
invocationTimeoutMs: config.invocationTimeoutMs ?? 30_000,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
return Object.freeze({ build, application });
|
|
54
|
+
});
|
|
55
|
+
return realized;
|
|
56
|
+
};
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
async fetch(request, bindings, context) {
|
|
59
|
+
const url = new URL(request.url);
|
|
60
|
+
const issuer = canonicalServingUrl(bindings.WORKER_URL ??
|
|
61
|
+
bindings.IDENTITY_ISS ??
|
|
62
|
+
config.resolveUrl?.(bindings, requestOrigin(url, request)) ??
|
|
63
|
+
requestOrigin(url, request));
|
|
64
|
+
if (config.providerAssignedWorker !== undefined &&
|
|
65
|
+
bindings.WORKER_URL === undefined &&
|
|
66
|
+
bindings.IDENTITY_ISS === undefined &&
|
|
67
|
+
!matchesWorkersDevClaim(issuer, config.providerAssignedWorker)) {
|
|
68
|
+
return new Response('Request origin differs from provider-assigned Addressing.', {
|
|
69
|
+
status: 421,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (currentIssuer !== undefined && currentIssuer !== issuer) {
|
|
73
|
+
return new Response('Request origin differs from the current Release.', { status: 421 });
|
|
74
|
+
}
|
|
75
|
+
currentIssuer = issuer;
|
|
76
|
+
const { application, build } = await realize();
|
|
77
|
+
const frontend = await frontendAsset(build.frontend, bindings, url, request);
|
|
78
|
+
if (frontend !== undefined)
|
|
79
|
+
return frontend;
|
|
80
|
+
return application.fetch(request, Object.freeze({ bindings, issuer }), context);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function routedFetch(application, environment, config) {
|
|
86
|
+
return async (input, init) => {
|
|
87
|
+
const { request, rejectRedirect } = prepareCloudflareRequest(input, init);
|
|
88
|
+
const destination = new URL(request.url);
|
|
89
|
+
if (destination.origin === environment.issuer) {
|
|
90
|
+
const self = config.selfBinding?.(environment.bindings);
|
|
91
|
+
return dispatchCloudflareRequest(request, rejectRedirect, (selected) => self === null || self === undefined
|
|
92
|
+
? application.fetch(selected, environment)
|
|
93
|
+
: self.fetch(selected));
|
|
94
|
+
}
|
|
95
|
+
const route = config.routeSubrequest?.(destination, environment.bindings);
|
|
96
|
+
return dispatchCloudflareRequest(request, rejectRedirect, (selected) => route === null || route === undefined ? fetch(selected) : route.fetch(selected));
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function frontendAsset(frontend, environment, url, request) {
|
|
100
|
+
if (frontend === undefined || frontend.source.kind === 'external')
|
|
101
|
+
return undefined;
|
|
102
|
+
if (reserved(url.pathname) || (request.method !== 'GET' && request.method !== 'HEAD')) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const development = environment.FRONTEND_DEV_URL?.replace(/\/+$/u, '');
|
|
106
|
+
if (development !== undefined && development.length > 0) {
|
|
107
|
+
return fetch(new Request(new URL(`${url.pathname}${url.search}`, `${development}/`), request));
|
|
108
|
+
}
|
|
109
|
+
const route = frontend.routes
|
|
110
|
+
.filter((candidate) => candidate.path === url.pathname ||
|
|
111
|
+
(frontend.source.kind === 'vite' &&
|
|
112
|
+
frontend.source.spa &&
|
|
113
|
+
(candidate.path === '/' || url.pathname.startsWith(`${candidate.path}/`))))
|
|
114
|
+
.sort((left, right) => right.path.length - left.path.length)[0];
|
|
115
|
+
if (route === undefined && !url.pathname.startsWith('/assets/'))
|
|
116
|
+
return undefined;
|
|
117
|
+
const assets = environment.ASSETS;
|
|
118
|
+
if (assets === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
const response = await assets.fetch(request);
|
|
121
|
+
if (response.status !== 404 || route === undefined)
|
|
122
|
+
return response;
|
|
123
|
+
await response.body?.cancel();
|
|
124
|
+
return assets.fetch(new Request(new URL(`/${route.document}${url.search}`, url.origin), request));
|
|
125
|
+
}
|
|
126
|
+
function reserved(path) {
|
|
127
|
+
return ['/releases', '/install', '/health', '/meta', '/.well-known'].some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
|
|
128
|
+
}
|
|
129
|
+
function requestOrigin(url, request) {
|
|
130
|
+
if (url.protocol !== 'http:')
|
|
131
|
+
return url.origin;
|
|
132
|
+
const forwarded = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim().toLowerCase();
|
|
133
|
+
return forwarded === 'https' ? `https://${url.host}` : url.origin;
|
|
134
|
+
}
|
|
135
|
+
function canonicalServingUrl(input) {
|
|
136
|
+
let url;
|
|
137
|
+
try {
|
|
138
|
+
url = new URL(input);
|
|
139
|
+
}
|
|
140
|
+
catch (cause) {
|
|
141
|
+
throw new TypeError('Cloudflare Worker serving URL is invalid.', { cause });
|
|
142
|
+
}
|
|
143
|
+
if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
|
|
144
|
+
url.username !== '' ||
|
|
145
|
+
url.password !== '' ||
|
|
146
|
+
url.pathname !== '/' ||
|
|
147
|
+
url.search !== '' ||
|
|
148
|
+
url.hash !== '') {
|
|
149
|
+
throw new TypeError('Cloudflare Worker serving URL must be one HTTP(S) origin.');
|
|
150
|
+
}
|
|
151
|
+
return url.origin;
|
|
152
|
+
}
|
|
153
|
+
function matchesWorkersDevClaim(issuer, expectedWorker) {
|
|
154
|
+
const url = new URL(issuer);
|
|
155
|
+
const labels = url.hostname.split('.');
|
|
156
|
+
return (url.protocol === 'https:' &&
|
|
157
|
+
labels.length >= 4 &&
|
|
158
|
+
labels[0] === expectedWorker &&
|
|
159
|
+
labels.at(-2) === 'workers' &&
|
|
160
|
+
labels.at(-1) === 'dev');
|
|
161
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type RequestDispatcher = (request: Request) => Response | Promise<Response>;
|
|
2
|
+
export interface CloudflareRequest {
|
|
3
|
+
readonly request: Request;
|
|
4
|
+
readonly rejectRedirect: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** Construct a Cloudflare-compatible transport request without weakening redirect:error. */
|
|
7
|
+
export declare function prepareCloudflareRequest(input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): CloudflareRequest;
|
|
8
|
+
/** Preserve Fetch redirect:error semantics on Cloudflare, whose transport rejects that mode. */
|
|
9
|
+
export declare function dispatchCloudflareRequest(request: Request, rejectRedirect: boolean, dispatch: RequestDispatcher): Promise<Response>;
|
|
10
|
+
/** Restore the requested URL omitted by Cloudflare service-binding Responses. */
|
|
11
|
+
export declare function adaptCloudflareResponse(request: Request, response: Response): Response;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
2
|
+
/** Construct a Cloudflare-compatible transport request without weakening redirect:error. */
|
|
3
|
+
export function prepareCloudflareRequest(input, init) {
|
|
4
|
+
const rejectRedirect = init?.redirect === 'error' ||
|
|
5
|
+
(init?.redirect === undefined && input instanceof Request && input.redirect === 'error');
|
|
6
|
+
const request = new Request(input, rejectRedirect ? { ...init, redirect: 'manual' } : init);
|
|
7
|
+
return Object.freeze({ request, rejectRedirect });
|
|
8
|
+
}
|
|
9
|
+
/** Preserve Fetch redirect:error semantics on Cloudflare, whose transport rejects that mode. */
|
|
10
|
+
export async function dispatchCloudflareRequest(request, rejectRedirect, dispatch) {
|
|
11
|
+
const response = adaptCloudflareResponse(request, await dispatch(request));
|
|
12
|
+
if (!rejectRedirect)
|
|
13
|
+
return response;
|
|
14
|
+
if (!response.redirected && !REDIRECT_STATUSES.has(response.status))
|
|
15
|
+
return response;
|
|
16
|
+
await response.body?.cancel();
|
|
17
|
+
throw new TypeError('Redirects are not allowed for this request.');
|
|
18
|
+
}
|
|
19
|
+
/** Restore the requested URL omitted by Cloudflare service-binding Responses. */
|
|
20
|
+
export function adaptCloudflareResponse(request, response) {
|
|
21
|
+
if (response.url !== '')
|
|
22
|
+
return response;
|
|
23
|
+
return new Proxy(response, {
|
|
24
|
+
get(target, property) {
|
|
25
|
+
if (property === 'url')
|
|
26
|
+
return request.url;
|
|
27
|
+
const value = Reflect.get(target, property, target);
|
|
28
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Revision } from '@astrale-os/sdk/schema';
|
|
2
|
+
export interface DispatchNamespace {
|
|
3
|
+
get(name: string): {
|
|
4
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export interface CloudflareGatewayBindings {
|
|
8
|
+
readonly RELEASES: DispatchNamespace;
|
|
9
|
+
}
|
|
10
|
+
/** Stable public Worker that selects one exact immutable Schema-revision Service. */
|
|
11
|
+
export declare function cloudflareGatewayEntry(config: {
|
|
12
|
+
readonly current: Revision;
|
|
13
|
+
}): {
|
|
14
|
+
fetch(request: Request, bindings: CloudflareGatewayBindings): Promise<Response>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { releaseUnavailable, requestedRevision } from '@astrale-os/sdk/deployment/release/runtime';
|
|
2
|
+
import { releaseWorkerName } from '../release/target.js';
|
|
3
|
+
/** Stable public Worker that selects one exact immutable Schema-revision Service. */
|
|
4
|
+
export function cloudflareGatewayEntry(config) {
|
|
5
|
+
const current = releaseWorkerName(config.current);
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
async fetch(request, bindings) {
|
|
8
|
+
const revision = requestedRevision(new URL(request.url).pathname);
|
|
9
|
+
const target = revision === undefined ? current : releaseWorkerName(revision);
|
|
10
|
+
let worker;
|
|
11
|
+
try {
|
|
12
|
+
worker = bindings.RELEASES.get(target);
|
|
13
|
+
return await worker.fetch(request);
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
if (isMissingWorker(cause))
|
|
17
|
+
return releaseUnavailable();
|
|
18
|
+
throw cause;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** Admit only Cloudflare's documented missing-script failure at the provider boundary. */
|
|
24
|
+
function isMissingWorker(cause) {
|
|
25
|
+
return cause instanceof Error && /^Worker not found(?:: [^\r\n]+)?$/iu.test(cause.message);
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JsonWebKey } from '@astrale-os/sdk/auth';
|
|
2
|
+
/** Decode the secret representation at the Worker binding boundary. */
|
|
3
|
+
export declare function decodeSigningIdentity(input: string): JsonWebKey;
|
|
4
|
+
/** Remove adapter-owned signing material before Runtime initialization. */
|
|
5
|
+
export declare function runtimeBindings<Bindings extends {
|
|
6
|
+
readonly ASTRALE_SIGNING_IDENTITY: string;
|
|
7
|
+
}>(bindings: Bindings): Omit<Bindings, 'ASTRALE_SIGNING_IDENTITY'>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Decode the secret representation at the Worker binding boundary. */
|
|
2
|
+
export function decodeSigningIdentity(input) {
|
|
3
|
+
if (typeof input !== 'string' || input.length === 0)
|
|
4
|
+
invalid();
|
|
5
|
+
let decoded;
|
|
6
|
+
try {
|
|
7
|
+
decoded = JSON.parse(input);
|
|
8
|
+
}
|
|
9
|
+
catch (cause) {
|
|
10
|
+
throw new TypeError('Cloudflare signing identity secret is invalid JSON.', { cause });
|
|
11
|
+
}
|
|
12
|
+
if (decoded === null || typeof decoded !== 'object' || Array.isArray(decoded))
|
|
13
|
+
invalid();
|
|
14
|
+
return decoded;
|
|
15
|
+
}
|
|
16
|
+
/** Remove adapter-owned signing material before Runtime initialization. */
|
|
17
|
+
export function runtimeBindings(bindings) {
|
|
18
|
+
const { ASTRALE_SIGNING_IDENTITY: _identity, ...runtime } = bindings;
|
|
19
|
+
return runtime;
|
|
20
|
+
}
|
|
21
|
+
function invalid() {
|
|
22
|
+
throw new TypeError('Cloudflare signing identity secret is invalid.');
|
|
23
|
+
}
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,59 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import type { ExecutionContext } from 'hono';
|
|
5
|
-
/** SDK-owned Publication manifest passed through without adapter interpretation. */
|
|
6
|
-
export type CloudflareManifest = NonNullable<RemoteDeploymentInput['manifest']>;
|
|
7
|
-
export interface Fetcher {
|
|
8
|
-
fetch(request: Request): Response | Promise<Response>;
|
|
9
|
-
}
|
|
10
|
-
/** Adapter-owned bindings generated into every Cloudflare deployment. */
|
|
11
|
-
export interface CloudflareAdapterBindings {
|
|
12
|
-
/** Native Cloudflare deployment metadata (direct Wrangler deployments). */
|
|
13
|
-
readonly ASTRALE_VERSION_METADATA?: {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
readonly tag: string;
|
|
16
|
-
readonly timestamp: string;
|
|
17
|
-
};
|
|
18
|
-
/** Content-derived generation supplied by providers without version metadata. */
|
|
19
|
-
readonly ASTRALE_DEPLOYMENT_VERSION?: string;
|
|
20
|
-
readonly WORKER_URL?: string;
|
|
21
|
-
readonly ASSETS?: Fetcher;
|
|
22
|
-
readonly SELF?: Fetcher;
|
|
23
|
-
readonly FRONTEND_DEV_URL?: string;
|
|
24
|
-
readonly DISPATCHER?: {
|
|
25
|
-
get(name: string): Fetcher;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
interface CloudflareWorkerSharedConfig<Bindings extends CloudflareAdapterBindings, Environment> {
|
|
29
|
-
readonly privateKey: RemoteIdentityInput['privateKey'] | ((bindings: Bindings) => RemoteIdentityInput['privateKey']);
|
|
30
|
-
/** Compose provider bindings into the exact Domain environment once per deployment generation. */
|
|
31
|
-
readonly resolveEnvironment?: (bindings: Bindings) => Environment | Promise<Environment>;
|
|
32
|
-
readonly manifest?: CloudflareManifest;
|
|
33
|
-
readonly resolveUrl?: (bindings: Bindings, requestOrigin: string) => string;
|
|
34
|
-
readonly selfBinding?: (bindings: Bindings) => Fetcher | null | undefined;
|
|
35
|
-
readonly routeSubrequest?: (url: URL, bindings: Bindings) => Fetcher | null | undefined;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Resolve a Domain lazily when its module graph is too expensive for provider startup admission.
|
|
39
|
-
* The adapter resolves at most once concurrently and retries after a rejected resolution.
|
|
40
|
-
*/
|
|
41
|
-
export type CloudflareWorkerConfig<S extends schema.DomainSchema, Bindings extends CloudflareAdapterBindings, Environment> = CloudflareWorkerSharedConfig<Bindings, Environment> & ({
|
|
42
|
-
readonly definition: DomainDefinition<S>;
|
|
43
|
-
readonly resolveDefinition?: never;
|
|
44
|
-
} | {
|
|
45
|
-
readonly definition?: never;
|
|
46
|
-
readonly resolveDefinition: () => DomainDefinition<S> | Promise<DomainDefinition<S>>;
|
|
47
|
-
});
|
|
48
|
-
export interface CloudflareWorker<Environment> {
|
|
49
|
-
fetch(request: Request, environment: Environment, executionContext?: ExecutionContext): Response | Promise<Response>;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Compose one Cloudflare module Worker without request-global mutation.
|
|
53
|
-
* Environment bindings are captured once per immutable serving URL/version generation.
|
|
54
|
-
*/
|
|
55
|
-
export declare function cloudflareWorkerEntry<Bindings extends CloudflareAdapterBindings, Environment = Bindings>(): <const S extends schema.DomainSchema>(config: CloudflareWorkerConfig<S, Bindings, Environment>) => CloudflareWorker<Bindings>;
|
|
56
|
-
/** Close an evicted Application before its environment bindings leave the cache. */
|
|
57
|
-
export declare function closeEvictedApplication(application: Pick<Application, 'close'>, now?: () => number): Promise<void>;
|
|
58
|
-
export {};
|
|
59
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export * from './entry.js';
|
|
2
|
+
export { decodeSigningIdentity, runtimeBindings } from './identity.js';
|
|
3
|
+
export * from './gateway.js';
|
package/dist/worker/index.js
CHANGED
|
@@ -1,209 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const MAXIMUM_CACHED_APPLICATIONS = 4;
|
|
5
|
-
const APPLICATION_CLOSE_TIMEOUT_MS = 5_000;
|
|
6
|
-
/**
|
|
7
|
-
* Compose one Cloudflare module Worker without request-global mutation.
|
|
8
|
-
* Environment bindings are captured once per immutable serving URL/version generation.
|
|
9
|
-
*/
|
|
10
|
-
export function cloudflareWorkerEntry() {
|
|
11
|
-
return function (config) {
|
|
12
|
-
const applications = new Map();
|
|
13
|
-
let resolvedDefinition = config.definition;
|
|
14
|
-
let pendingDefinition;
|
|
15
|
-
const definitionFor = async () => {
|
|
16
|
-
if (resolvedDefinition !== undefined)
|
|
17
|
-
return resolvedDefinition;
|
|
18
|
-
if (pendingDefinition !== undefined)
|
|
19
|
-
return pendingDefinition;
|
|
20
|
-
const resolve = config.resolveDefinition;
|
|
21
|
-
if (resolve === undefined)
|
|
22
|
-
throw new TypeError('Cloudflare Worker Domain definition is unavailable.');
|
|
23
|
-
const current = Promise.resolve().then(resolve);
|
|
24
|
-
pendingDefinition = current;
|
|
25
|
-
try {
|
|
26
|
-
const definition = await current;
|
|
27
|
-
resolvedDefinition = definition;
|
|
28
|
-
return definition;
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
if (pendingDefinition === current)
|
|
32
|
-
pendingDefinition = undefined;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const applicationFor = async (url, bindings, definition) => {
|
|
36
|
-
const version = deploymentVersion(bindings);
|
|
37
|
-
const key = `${url}\u0000${version}`;
|
|
38
|
-
const cached = applications.get(key);
|
|
39
|
-
if (cached !== undefined)
|
|
40
|
-
return cached;
|
|
41
|
-
const environment = config.resolveEnvironment === undefined
|
|
42
|
-
? bindings
|
|
43
|
-
: await config.resolveEnvironment(bindings);
|
|
44
|
-
const routedFetch = async (input, init) => {
|
|
45
|
-
const request = new Request(input, init);
|
|
46
|
-
const destination = new URL(request.url);
|
|
47
|
-
if (destination.origin === new URL(url).origin) {
|
|
48
|
-
const self = config.selfBinding?.(bindings);
|
|
49
|
-
if (self !== null && self !== undefined)
|
|
50
|
-
return self.fetch(request);
|
|
51
|
-
return serving.app.fetch(request, environment);
|
|
52
|
-
}
|
|
53
|
-
const route = config.routeSubrequest?.(destination, bindings);
|
|
54
|
-
return route === null || route === undefined ? fetch(request) : route.fetch(request);
|
|
55
|
-
};
|
|
56
|
-
const deploymentQuery = `deployment=${encodeURIComponent(version)}`;
|
|
57
|
-
const issuerId = issuer.accept(url);
|
|
58
|
-
const host = new HonoApplication();
|
|
59
|
-
host.get('/meta', (context) => context.json({
|
|
60
|
-
origin: definition.schema.origin,
|
|
61
|
-
issuer: issuerId,
|
|
62
|
-
schemaRevision: definition.domain.$.revision,
|
|
63
|
-
deploymentVersion: version,
|
|
64
|
-
}));
|
|
65
|
-
const serving = createRemoteApplication({
|
|
66
|
-
definition,
|
|
67
|
-
environment,
|
|
68
|
-
identity: {
|
|
69
|
-
issuer: issuerId,
|
|
70
|
-
subject: definition.schema.origin,
|
|
71
|
-
privateKey: typeof config.privateKey === 'function'
|
|
72
|
-
? config.privateKey(bindings)
|
|
73
|
-
: config.privateKey,
|
|
74
|
-
fetch: routedFetch,
|
|
75
|
-
...(new URL(url).protocol === 'http:' ? { allowInsecureHttp: true } : {}),
|
|
76
|
-
},
|
|
77
|
-
deployment: {
|
|
78
|
-
bundleHref: `${url}/.well-known/astrale/domain.bundle.json?${deploymentQuery}`,
|
|
79
|
-
endpoint: { http: `${url}/invoke` },
|
|
80
|
-
...(definition.frontend === undefined
|
|
81
|
-
? {}
|
|
82
|
-
: { frontendBaseHref: url, frontendCacheKey: version }),
|
|
83
|
-
...(config.manifest === undefined ? {} : { manifest: config.manifest }),
|
|
84
|
-
installation: {
|
|
85
|
-
href: `${url}/install`,
|
|
86
|
-
method: 'POST',
|
|
87
|
-
authentication: { kind: 'public' },
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
app: host,
|
|
91
|
-
server: {
|
|
92
|
-
path: '/invoke',
|
|
93
|
-
health: { probe: { check: () => true } },
|
|
94
|
-
limits: {
|
|
95
|
-
maximumRequestBytes: 1024 * 1024,
|
|
96
|
-
maximumPublicationBytes: 1024 * 1024,
|
|
97
|
-
maximumBundleBytes: 8 * 1024 * 1024,
|
|
98
|
-
maximumInstallationBytes: 8 * 1024 * 1024,
|
|
99
|
-
invocationTimeoutMs: 30_000,
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
const created = Object.freeze({
|
|
104
|
-
origin: new URL(url).origin,
|
|
105
|
-
application: serving,
|
|
106
|
-
environment,
|
|
107
|
-
});
|
|
108
|
-
if (applications.size >= MAXIMUM_CACHED_APPLICATIONS) {
|
|
109
|
-
const oldest = applications.keys().next().value;
|
|
110
|
-
if (oldest !== undefined) {
|
|
111
|
-
const evicted = applications.get(oldest);
|
|
112
|
-
applications.delete(oldest);
|
|
113
|
-
if (evicted !== undefined) {
|
|
114
|
-
try {
|
|
115
|
-
await closeEvictedApplication(evicted.application);
|
|
116
|
-
}
|
|
117
|
-
catch (cause) {
|
|
118
|
-
await serving
|
|
119
|
-
.close({ deadline: Date.now() + APPLICATION_CLOSE_TIMEOUT_MS })
|
|
120
|
-
.catch(() => undefined);
|
|
121
|
-
throw cause;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
applications.set(key, created);
|
|
127
|
-
return created;
|
|
128
|
-
};
|
|
129
|
-
return Object.freeze({
|
|
130
|
-
async fetch(request, bindings, executionContext) {
|
|
131
|
-
const requestUrl = new URL(request.url);
|
|
132
|
-
const definition = await definitionFor();
|
|
133
|
-
if (definition.frontend !== undefined) {
|
|
134
|
-
const asset = await frontendAsset(definition, bindings, requestUrl, request);
|
|
135
|
-
if (asset !== undefined)
|
|
136
|
-
return asset;
|
|
137
|
-
}
|
|
138
|
-
const publicUrl = canonicalServingUrl(config.resolveUrl?.(bindings, requestOrigin(requestUrl, request)) ??
|
|
139
|
-
bindings.WORKER_URL ??
|
|
140
|
-
requestOrigin(requestUrl, request));
|
|
141
|
-
const serving = await applicationFor(publicUrl, bindings, definition);
|
|
142
|
-
return serving.application.app.fetch(request, serving.environment, executionContext);
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/** Close an evicted Application before its environment bindings leave the cache. */
|
|
148
|
-
export function closeEvictedApplication(application, now = Date.now) {
|
|
149
|
-
return application.close({ deadline: now() + APPLICATION_CLOSE_TIMEOUT_MS });
|
|
150
|
-
}
|
|
151
|
-
async function frontendAsset(definition, environment, url, request) {
|
|
152
|
-
const frontend = definition.frontend;
|
|
153
|
-
if (frontend === undefined || frontend.source.kind === 'external')
|
|
154
|
-
return undefined;
|
|
155
|
-
const routes = frontend.routes;
|
|
156
|
-
const route = Object.values(routes).find((candidate) => candidate.path === url.pathname);
|
|
157
|
-
const staticAsset = url.pathname.startsWith('/assets/');
|
|
158
|
-
if (route === undefined && !staticAsset)
|
|
159
|
-
return undefined;
|
|
160
|
-
if (request.method !== 'GET' && request.method !== 'HEAD')
|
|
161
|
-
return undefined;
|
|
162
|
-
const binding = environment.ASSETS;
|
|
163
|
-
if (binding === undefined)
|
|
164
|
-
return undefined;
|
|
165
|
-
const dev = environment.FRONTEND_DEV_URL?.replace(/\/+$/u, '');
|
|
166
|
-
if (dev !== undefined && dev.length > 0) {
|
|
167
|
-
return fetch(new Request(`${dev}${url.pathname}${url.search}`, request));
|
|
168
|
-
}
|
|
169
|
-
const path = route === undefined ? `${url.pathname}${url.search}` : `/${route.document}${url.search}`;
|
|
170
|
-
return binding.fetch(new Request(new URL(path, url.origin), request));
|
|
171
|
-
}
|
|
172
|
-
function requestOrigin(url, request) {
|
|
173
|
-
if (url.protocol !== 'http:')
|
|
174
|
-
return url.origin;
|
|
175
|
-
const forwarded = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim().toLowerCase();
|
|
176
|
-
return forwarded === 'https' ? `https://${url.host}` : url.origin;
|
|
177
|
-
}
|
|
178
|
-
function canonicalServingUrl(input) {
|
|
179
|
-
let url;
|
|
180
|
-
try {
|
|
181
|
-
url = new URL(input);
|
|
182
|
-
}
|
|
183
|
-
catch (cause) {
|
|
184
|
-
throw new TypeError('Cloudflare Worker serving URL is invalid.', { cause });
|
|
185
|
-
}
|
|
186
|
-
if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
|
|
187
|
-
url.username !== '' ||
|
|
188
|
-
url.password !== '' ||
|
|
189
|
-
url.pathname !== '/' ||
|
|
190
|
-
url.search !== '' ||
|
|
191
|
-
url.hash !== '') {
|
|
192
|
-
throw new TypeError('Cloudflare Worker serving URL must be one HTTP(S) origin.');
|
|
193
|
-
}
|
|
194
|
-
return url.origin;
|
|
195
|
-
}
|
|
196
|
-
function deploymentVersion(environment) {
|
|
197
|
-
const input = environment.ASTRALE_VERSION_METADATA;
|
|
198
|
-
if (input !== null &&
|
|
199
|
-
typeof input === 'object' &&
|
|
200
|
-
typeof input.id === 'string' &&
|
|
201
|
-
input.id.length > 0) {
|
|
202
|
-
return input.id;
|
|
203
|
-
}
|
|
204
|
-
const fallback = environment.ASTRALE_DEPLOYMENT_VERSION;
|
|
205
|
-
if (typeof fallback === 'string' && fallback.length > 0)
|
|
206
|
-
return fallback;
|
|
207
|
-
throw new TypeError('Cloudflare Worker deployment version is unavailable.');
|
|
208
|
-
}
|
|
209
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export * from './entry.js';
|
|
2
|
+
export { decodeSigningIdentity, runtimeBindings } from './identity.js';
|
|
3
|
+
export * from './gateway.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/adapter-cloudflare",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
4
|
-
"description": "Deploy an Astrale
|
|
3
|
+
"version": "0.5.0-beta.30",
|
|
4
|
+
"description": "Deploy an Astrale application through Cloudflare Workers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
7
7
|
"astrale",
|
|
@@ -11,16 +11,21 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "Apache-2.0",
|
|
13
13
|
"author": "Astrale",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/astrale-os/sdk.git"
|
|
17
|
+
},
|
|
14
18
|
"files": [
|
|
15
19
|
"dist",
|
|
16
|
-
"
|
|
20
|
+
"!dist/**/*.map",
|
|
17
21
|
"template",
|
|
18
|
-
"!template
|
|
19
|
-
"!template
|
|
22
|
+
"!template/**/node_modules",
|
|
23
|
+
"!template/**/node_modules/**",
|
|
24
|
+
"!template/**/pnpm-lock.yaml"
|
|
20
25
|
],
|
|
21
26
|
"type": "module",
|
|
22
|
-
"main": "./
|
|
23
|
-
"types": "./
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
24
29
|
"exports": {
|
|
25
30
|
".": {
|
|
26
31
|
"types": "./dist/index.d.ts",
|
|
@@ -37,31 +42,29 @@
|
|
|
37
42
|
"./package.json": "./package.json"
|
|
38
43
|
},
|
|
39
44
|
"dependencies": {
|
|
40
|
-
"@astrale-os/kernel-core": "0.9.0-beta.1",
|
|
41
|
-
"@astrale-os/kernel-dsl": "0.2.0-beta.1",
|
|
42
45
|
"hono": "^4.6.20",
|
|
43
46
|
"jose": "^6.1.3",
|
|
44
|
-
"
|
|
47
|
+
"wrangler": "4.123.0",
|
|
48
|
+
"@astrale-os/sdk": "^0.5.0-beta.27"
|
|
45
49
|
},
|
|
46
50
|
"devDependencies": {
|
|
47
51
|
"@astrale-os/ox": ">=0.1.0 <1.0.0",
|
|
48
52
|
"@types/node": "^22.0.0",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"wrangler": "4.121.0"
|
|
53
|
+
"oxfmt": "0.63.0",
|
|
54
|
+
"oxlint": "1.78.0",
|
|
55
|
+
"typescript": "7.0.2",
|
|
56
|
+
"vitest": "^4.1.10",
|
|
57
|
+
"zod": "^4.4.3"
|
|
55
58
|
},
|
|
56
59
|
"engines": {
|
|
57
60
|
"node": ">=22"
|
|
58
61
|
},
|
|
59
62
|
"scripts": {
|
|
60
|
-
"typecheck": "
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
61
64
|
"test": "vitest run",
|
|
62
65
|
"lint": "oxlint --disable-nested-config .",
|
|
63
66
|
"format": "pnpm exec oxfmt --write .",
|
|
64
67
|
"format:check": "pnpm exec oxfmt --check .",
|
|
65
|
-
"build": "rm -rf dist &&
|
|
68
|
+
"build": "rm -rf dist && tsc && node ../scripts/verify-no-source-maps.mjs dist"
|
|
66
69
|
}
|
|
67
70
|
}
|