@sanoma-ai/connector-cloudflare 0.6.0 → 0.6.2
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 +37 -19
- package/dist/deploy.d.ts +66 -0
- package/dist/deploy.js +143 -0
- package/dist/deploy.js.map +1 -0
- package/dist/driver.d.ts +7 -2
- package/dist/driver.js +22 -3
- package/dist/driver.js.map +1 -1
- package/dist/fake.d.ts +46 -20
- package/dist/fake.js +248 -41
- package/dist/fake.js.map +1 -1
- package/dist/ops.d.ts +58 -15
- package/dist/ops.js +224 -125
- package/dist/ops.js.map +1 -1
- package/dist/records.d.ts +2 -2
- package/dist/steps.d.ts +2 -1
- package/dist/steps.js +7 -2
- package/dist/steps.js.map +1 -1
- package/features/cloudflare.feature +6 -2
- package/package.json +4 -3
- package/worker/index.js +10 -6
- package/worker/instance.d.ts +25 -13
- package/worker/instance.js +128 -78
- package/worker/no-app.js +10 -0
- package/dist/sites.d.ts +0 -58
- package/dist/sites.js +0 -153
- package/dist/sites.js.map +0 -1
- package/dist/worker.d.ts +0 -19
- package/dist/worker.js +0 -29
- package/dist/worker.js.map +0 -1
- package/worker/sites.js +0 -17
package/worker/instance.js
CHANGED
|
@@ -1,30 +1,43 @@
|
|
|
1
|
-
// The company's
|
|
2
|
-
// `
|
|
3
|
-
//
|
|
1
|
+
// The company's Worker script without the platform: its fetch handler (`serve`) and its Durable
|
|
2
|
+
// Object (`Instance`). `index.js` binds them to `cloudflare:workers` and to the app bundle the
|
|
3
|
+
// deploy uploaded under `app/`; the connector's fake runs this file in Node over a stubbed `ctx`,
|
|
4
|
+
// so both kits check the code Cloudflare runs. No imports, so Node loads it as it is.
|
|
4
5
|
//
|
|
5
|
-
// The
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// caller waiting for it, and calls its `/wake`: a worker that stopped itself runs the queued run.
|
|
6
|
+
// The Worker script is the app: every request but the object's own routes and `/wake` goes to the
|
|
7
|
+
// app bundle's fetch, with the visitor's address from `cf-connecting-ip` and the object's
|
|
8
|
+
// `startNow` as its host. The object runs the company's worker in its container (`ctx.container`),
|
|
9
|
+
// never a visitor's request: it starts it at the Worker's `SANOMA_REVISION` and the image its upload
|
|
10
|
+
// names (`ctx.container.images.runtime`, as the `durable_object` scheduling policy has it), drains
|
|
11
|
+
// and replaces one running at another (a deploy's restart), holds the company's one wake as an
|
|
12
|
+
// alarm, and answers three routes: its health, its wake, and the app bundle the worker builds,
|
|
13
|
+
// pulled from the container's port with the instance token.
|
|
14
14
|
|
|
15
|
-
/** The object's
|
|
15
|
+
/** The object's route that holds the company's wake, with the instance token. */
|
|
16
16
|
export const WAKE_PATH = "/__sanoma/wake";
|
|
17
|
-
/** The object's route that answers
|
|
17
|
+
/** The object's route that answers what the Worker runs; with `?start=1` and the instance token, it starts the container at it. */
|
|
18
18
|
export const HEALTH_PATH = "/__sanoma/health";
|
|
19
|
-
/**
|
|
19
|
+
/** The worker port's route of the app bundle at `?revision=`, which the object passes on with the instance token (`APP_BUNDLE_PATH` in `@sanoma-ai/workflows/shared`). */
|
|
20
|
+
export const APP_BUNDLE_PATH = "/__sanoma/app-bundle";
|
|
21
|
+
/** The Host contract's wake: the Worker has the worker started now (a wakeup's `GET {url}/wake`). */
|
|
22
|
+
const HOST_WAKE_PATH = "/wake";
|
|
23
|
+
/** The header the app reads the visitor's address from (`CLIENT_IP_HEADER` in `@sanoma-ai/workflows`). */
|
|
24
|
+
export const CLIENT_IP_HEADER = "x-sanoma-client-ip";
|
|
25
|
+
/** How long the container keeps running after the object's last request: Cloudflare stops it then. */
|
|
20
26
|
export const INACTIVITY_MS = 10 * 60 * 1000;
|
|
21
|
-
/**
|
|
27
|
+
/** The binding that names the object, in the Worker's env. */
|
|
28
|
+
export const NAMESPACE = "SANOMA_INSTANCE";
|
|
29
|
+
/** The name the upload gives the runtime image (`containers[].images`), which the object starts. */
|
|
30
|
+
export const IMAGE = "runtime";
|
|
31
|
+
/** How long a start may take to answer `/healthz`: the image pulled, the source cloned and installed. */
|
|
22
32
|
const START_TIMEOUT_MS = 10 * 60 * 1000;
|
|
33
|
+
/** How long a drained worker has to stop on its own before its container is destroyed. */
|
|
34
|
+
const DRAIN_MS = 30 * 1000;
|
|
35
|
+
/** SIGTERM: the worker's own stop (it stops dequeuing, closes and exits). */
|
|
36
|
+
const SIGTERM = 15;
|
|
23
37
|
/** How long one try of a starting container's `/healthz` may take. */
|
|
24
38
|
const TRY_MS = 5_000;
|
|
25
|
-
/** How long between two tries of a starting container's `/healthz`. */
|
|
39
|
+
/** How long between two tries of a starting container's `/healthz`, doubling to `MAX_POLL_MS`. */
|
|
26
40
|
const POLL_MS = 250;
|
|
27
|
-
/** The longest wait between two tries. */
|
|
28
41
|
const MAX_POLL_MS = 1_000;
|
|
29
42
|
/** Where the wake is kept, in the object's storage. */
|
|
30
43
|
const WAKE_KEY = "wake";
|
|
@@ -35,10 +48,6 @@ const START_KEY = "start";
|
|
|
35
48
|
* held for later is set again: Cloudflare retries an alarm that throws up to six times, then drops it.
|
|
36
49
|
*/
|
|
37
50
|
const START_RETRIES = 5;
|
|
38
|
-
/** The binding that names the object, in the Worker's env. */
|
|
39
|
-
export const NAMESPACE = "SANOMA_INSTANCE";
|
|
40
|
-
/** The name the upload gives the runtime image (`containers[].images`), which the object starts. */
|
|
41
|
-
export const IMAGE = "runtime";
|
|
42
51
|
/** The container's size, one of the named types `start` takes: ½ vCPU, 4 GiB (`basic`, ¼ vCPU, is refused under this policy). */
|
|
43
52
|
const INSTANCE_TYPE = "standard-1";
|
|
44
53
|
|
|
@@ -53,9 +62,9 @@ function containerEnv(env) {
|
|
|
53
62
|
return Object.fromEntries(Object.entries(env).filter(([, value]) => typeof value === "string"));
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
/**
|
|
57
|
-
export async function
|
|
58
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(
|
|
65
|
+
/** A label of a value too long for one (a label holds 64 bytes; a pinned image is longer): a digest of it. */
|
|
66
|
+
export async function digestLabel(value) {
|
|
67
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
59
68
|
return [...new Uint8Array(digest).slice(0, 8)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -75,7 +84,7 @@ function parseRegistration(body) {
|
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
export class Instance {
|
|
78
|
-
/** The start in progress, which every
|
|
87
|
+
/** The start in progress, which every caller waits on. */
|
|
79
88
|
#starting;
|
|
80
89
|
/** True once a container this object started (or found) answered `/healthz`. */
|
|
81
90
|
#ready = false;
|
|
@@ -98,46 +107,59 @@ export class Instance {
|
|
|
98
107
|
return Number(this.env.PORT);
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
/** The object's routes; never a visitor's request, which the app answers on the Worker. */
|
|
101
111
|
async fetch(request) {
|
|
102
112
|
const url = new URL(request.url);
|
|
103
113
|
if (url.pathname === WAKE_PATH) return this.wake(request);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await this.ensureRunning();
|
|
109
|
-
} catch (error) {
|
|
110
|
-
return problem(503, `the company's container did not start: ${error?.message ?? error}`, this.#exit);
|
|
114
|
+
if (url.pathname === HEALTH_PATH && !url.searchParams.has("start")) return this.health(200);
|
|
115
|
+
if (url.pathname !== HEALTH_PATH && url.pathname !== APP_BUNDLE_PATH) {
|
|
116
|
+
return problem(404, `the object answers ${WAKE_PATH}, ${HEALTH_PATH} and ${APP_BUNDLE_PATH} alone`);
|
|
111
117
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
const refused = this.#refuse(request);
|
|
119
|
+
if (refused) return refused;
|
|
120
|
+
// A container that started and ended is the answer: asking again would only start it again.
|
|
121
|
+
// Told once: the next ask starts it again.
|
|
122
|
+
const exit = this.#starting ? undefined : this.#exit;
|
|
123
|
+
if (exit) {
|
|
124
|
+
this.#exit = undefined;
|
|
125
|
+
return problem(503, `the company's container did not start: ${exit}`, exit);
|
|
126
|
+
}
|
|
127
|
+
if (!this.#ready || !this.ctx.container.running) {
|
|
128
|
+
// Started from an alarm, which outlives this request (the caller may be the worker this
|
|
129
|
+
// start drains): the caller asks again until it is ready. One already under way is not asked again.
|
|
130
|
+
if (!this.#starting) await this.startNow();
|
|
131
|
+
return url.pathname === HEALTH_PATH ? this.health(202) : json(202, { starting: true });
|
|
132
|
+
}
|
|
133
|
+
if (url.pathname === HEALTH_PATH) return this.health(200);
|
|
134
|
+
// The worker builds the bundle at the revision asked for, in a checkout of its own.
|
|
125
135
|
try {
|
|
126
|
-
return await this.ctx.container.getTcpPort(this.port).fetch(
|
|
136
|
+
return await this.ctx.container.getTcpPort(this.port).fetch(`http://container${url.pathname}${url.search}`, {
|
|
137
|
+
headers: { authorization: `Bearer ${this.env.SANOMA_WAKEUP_TOKEN}` },
|
|
138
|
+
});
|
|
127
139
|
} catch (error) {
|
|
128
|
-
// Stopped since it
|
|
140
|
+
// Stopped since it answered (Cloudflare's own stop, a crash): the next ask starts it again.
|
|
129
141
|
this.#ready = false;
|
|
130
142
|
return problem(503, `the company's container did not answer: ${error?.message ?? error}`);
|
|
131
143
|
}
|
|
132
144
|
}
|
|
133
145
|
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
/** Why a request without the company's instance token is refused, if it is. */
|
|
147
|
+
#refuse(request) {
|
|
148
|
+
const token = this.env.SANOMA_WAKEUP_TOKEN;
|
|
149
|
+
if (!token) return problem(404, "no instance token here: SANOMA_WAKEUP_TOKEN is not among the host's secrets");
|
|
150
|
+
if (!sameToken(request.headers.get("authorization") ?? "", `Bearer ${token}`)) {
|
|
151
|
+
return problem(401, "the token is not this host's SANOMA_WAKEUP_TOKEN");
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** What the Worker runs, and whether its container does and answered at it. */
|
|
157
|
+
health(status) {
|
|
158
|
+
return json(status, {
|
|
137
159
|
revision: this.env.SANOMA_REVISION ?? "",
|
|
138
160
|
...(this.env.SANOMA_CONFIG_REVISION && { configurationRevision: this.env.SANOMA_CONFIG_REVISION }),
|
|
139
161
|
running: this.ctx.container.running,
|
|
140
|
-
ready: this.#ready,
|
|
162
|
+
ready: this.#ready && this.ctx.container.running,
|
|
141
163
|
...(this.#last !== undefined && !this.#ready && { last: this.#last }),
|
|
142
164
|
...(this.#exit !== undefined && { exit: this.#exit }),
|
|
143
165
|
});
|
|
@@ -159,10 +181,10 @@ export class Instance {
|
|
|
159
181
|
if (!image) throw new Error(`the Worker names no "${IMAGE}" image among its containers' images`);
|
|
160
182
|
const labels = {
|
|
161
183
|
revision: this.env.SANOMA_REVISION ?? "",
|
|
162
|
-
image: await
|
|
184
|
+
image: await digestLabel(image),
|
|
163
185
|
...(this.env.SANOMA_CONFIG_REVISION && { configuration: this.env.SANOMA_CONFIG_REVISION }),
|
|
164
|
-
// The sites' domains, which
|
|
165
|
-
...(this.env.SANOMA_DOMAINS && { domains: await
|
|
186
|
+
// The sites' domains, which the worker's environment, and the bundles it builds, name.
|
|
187
|
+
...(this.env.SANOMA_DOMAINS && { domains: await digestLabel(this.env.SANOMA_DOMAINS) }),
|
|
166
188
|
};
|
|
167
189
|
const until = Date.now() + START_TIMEOUT_MS;
|
|
168
190
|
let last;
|
|
@@ -183,7 +205,8 @@ export class Instance {
|
|
|
183
205
|
}
|
|
184
206
|
try {
|
|
185
207
|
if (!checked && container.running) {
|
|
186
|
-
// One running from before: kept only if it says it started at these
|
|
208
|
+
// One running from before: kept only if it says it started at these; else the deploy's
|
|
209
|
+
// restart, its worker drained first, so it stops dequeuing before its container goes.
|
|
187
210
|
const info = await container.inspect();
|
|
188
211
|
const same =
|
|
189
212
|
info &&
|
|
@@ -191,7 +214,7 @@ export class Instance {
|
|
|
191
214
|
(info.labels?.image ?? "") === labels.image &&
|
|
192
215
|
(info.labels?.configuration ?? "") === (labels.configuration ?? "") &&
|
|
193
216
|
(info.labels?.domains ?? "") === (labels.domains ?? "");
|
|
194
|
-
if (!same) await container
|
|
217
|
+
if (!same) await this.#replace(container);
|
|
195
218
|
}
|
|
196
219
|
checked = true;
|
|
197
220
|
if (!container.running) {
|
|
@@ -206,7 +229,7 @@ export class Instance {
|
|
|
206
229
|
// that exits, is what `monitor` tells, and the next try reports it. Only this start's, and
|
|
207
230
|
// only until it answered: an earlier container's monitor may settle after this one started,
|
|
208
231
|
// and one that answered and then stopped (Cloudflare's own stop, a crash) is started again
|
|
209
|
-
// by the next
|
|
232
|
+
// by the next ask, not reported as a start that failed.
|
|
210
233
|
this.#exit = undefined;
|
|
211
234
|
const started = (this.#started = {});
|
|
212
235
|
const ended = (how) => void (this.#started === started && !this.#ready && (this.#exit = how));
|
|
@@ -215,7 +238,7 @@ export class Instance {
|
|
|
215
238
|
(error) => ended(`the container failed: ${error?.message ?? error}`),
|
|
216
239
|
);
|
|
217
240
|
}
|
|
218
|
-
// A try of its own time: a hung one would hold every
|
|
241
|
+
// A try of its own time: a hung one would hold every caller past the deadline.
|
|
219
242
|
const res = await container
|
|
220
243
|
.getTcpPort(this.port)
|
|
221
244
|
.fetch("http://container/healthz", { signal: AbortSignal.timeout(TRY_MS) });
|
|
@@ -235,16 +258,23 @@ export class Instance {
|
|
|
235
258
|
wait = Math.min(wait * 2, MAX_POLL_MS);
|
|
236
259
|
}
|
|
237
260
|
const ended = this.#exit ? `; ${this.#exit}` : "";
|
|
238
|
-
throw new Error(
|
|
261
|
+
throw new Error(
|
|
262
|
+
`still starting after ${START_TIMEOUT_MS / 1000} s: no answer from /healthz on port ${this.port} (${last}${ended})`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Drains the running container's worker (SIGTERM: its own stop), then destroys what is left of it. */
|
|
267
|
+
async #replace(container) {
|
|
268
|
+
container.signal(SIGTERM);
|
|
269
|
+
const until = Date.now() + DRAIN_MS;
|
|
270
|
+
while (container.running && Date.now() < until) await sleep(POLL_MS);
|
|
271
|
+
if (container.running) await container.destroy("a new revision, image, configuration or domains");
|
|
239
272
|
}
|
|
240
273
|
|
|
241
274
|
/** `/__sanoma/wake`, with the company's `SANOMA_WAKEUP_TOKEN`: the wake this object holds. */
|
|
242
275
|
async wake(request) {
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
245
|
-
if (!sameToken(request.headers.get("authorization") ?? "", `Bearer ${token}`)) {
|
|
246
|
-
return problem(401, "the token is not this host's SANOMA_WAKEUP_TOKEN");
|
|
247
|
-
}
|
|
276
|
+
const refused = this.#refuse(request);
|
|
277
|
+
if (refused) return refused;
|
|
248
278
|
const storage = this.ctx.storage;
|
|
249
279
|
if (request.method === "GET") return json(200, (await storage.get(WAKE_KEY)) ?? { at: null });
|
|
250
280
|
// A start `startNow` asked for keeps its alarm, which sets the wake's once it rang.
|
|
@@ -266,8 +296,9 @@ export class Instance {
|
|
|
266
296
|
|
|
267
297
|
/**
|
|
268
298
|
* Starts the container soon and sends it `/wake`, without waiting for it: a run was queued for
|
|
269
|
-
* it (the
|
|
270
|
-
* which outlives the request that asked, makes the start; a wake held for later is set
|
|
299
|
+
* it (the app's call over RPC), a wake came, or a deploy asks for its bundle or its restart. An
|
|
300
|
+
* alarm now, which outlives the request that asked, makes the start; a wake held for later is set
|
|
301
|
+
* again once it is made.
|
|
271
302
|
*/
|
|
272
303
|
async startNow() {
|
|
273
304
|
const storage = this.ctx.storage;
|
|
@@ -277,8 +308,9 @@ export class Instance {
|
|
|
277
308
|
|
|
278
309
|
/**
|
|
279
310
|
* A start `startNow` asked for, then the wake if it is due: start the container and send it
|
|
280
|
-
* `/wake` (once for both), then note when the wake rang. A wake not due yet is set again. A throw
|
|
281
|
-
* alarm; a start that still fails after `START_RETRIES` is given up,
|
|
311
|
+
* `/wake` (once for both), then note when the wake rang. A wake not due yet is set again. A throw
|
|
312
|
+
* makes Cloudflare retry the alarm; a start that still fails after `START_RETRIES` is given up,
|
|
313
|
+
* the wake kept.
|
|
282
314
|
*/
|
|
283
315
|
async alarm(info) {
|
|
284
316
|
const storage = this.ctx.storage;
|
|
@@ -299,13 +331,13 @@ export class Instance {
|
|
|
299
331
|
if (at > Date.now()) return storage.setAlarm(at);
|
|
300
332
|
if (!woke) await this.#wakeContainer();
|
|
301
333
|
// When it rang, which a GET shows: unless another wake replaced it meanwhile.
|
|
302
|
-
const held = await
|
|
303
|
-
if (held?.at === wake.at) await
|
|
334
|
+
const held = await storage.get(WAKE_KEY);
|
|
335
|
+
if (held?.at === wake.at) await storage.put(WAKE_KEY, { ...held, rang: new Date().toISOString() });
|
|
304
336
|
}
|
|
305
337
|
|
|
306
338
|
/**
|
|
307
339
|
* Starts the container unless it runs, then sends it `/wake`: a container that is up may hold a
|
|
308
|
-
* worker that stopped itself, which only a wake
|
|
340
|
+
* worker that stopped itself, which only a wake starts again.
|
|
309
341
|
*/
|
|
310
342
|
async #wakeContainer() {
|
|
311
343
|
await this.ensureRunning();
|
|
@@ -324,10 +356,28 @@ async function alarmBy(storage, at) {
|
|
|
324
356
|
if (set === null || set > at) await storage.setAlarm(at);
|
|
325
357
|
}
|
|
326
358
|
|
|
327
|
-
/** The Worker's one object
|
|
328
|
-
|
|
359
|
+
/** The Worker's one object. */
|
|
360
|
+
const instanceOf = (env) => env[NAMESPACE].getByName("instance");
|
|
329
361
|
|
|
330
|
-
/**
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
362
|
+
/**
|
|
363
|
+
* The Worker script's fetch: the object's routes to the object, the Host contract's `/wake` as a
|
|
364
|
+
* start now, and everything else to the app bundle (`loadApp`, imported on the first request, so
|
|
365
|
+
* the object's isolate never evaluates it), its visitor's address set from Cloudflare's
|
|
366
|
+
* `cf-connecting-ip`, a client's own copy overwritten: the one trust decision the app relies on.
|
|
367
|
+
*/
|
|
368
|
+
export async function serve(request, env, ctx, loadApp) {
|
|
369
|
+
const url = new URL(request.url);
|
|
370
|
+
const object = instanceOf(env);
|
|
371
|
+
if (url.pathname === WAKE_PATH || url.pathname === HEALTH_PATH || url.pathname === APP_BUNDLE_PATH) {
|
|
372
|
+
return object.fetch(request);
|
|
373
|
+
}
|
|
374
|
+
if (url.pathname === HOST_WAKE_PATH && request.method === "GET") {
|
|
375
|
+
await object.startNow();
|
|
376
|
+
return new Response(null, { status: 204, headers: { "cache-control": "no-store" } });
|
|
377
|
+
}
|
|
378
|
+
const headers = new Headers(request.headers);
|
|
379
|
+
headers.set(CLIENT_IP_HEADER, request.headers.get("cf-connecting-ip") ?? "");
|
|
380
|
+
const { default: app } = await loadApp();
|
|
381
|
+
// The object's stub is the app's host: its `startNow` over RPC.
|
|
382
|
+
return app.fetch(new Request(request, { headers }), env, ctx, object);
|
|
383
|
+
}
|
package/worker/no-app.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The app bundle of a Worker made before its first bundle was built (a create's first uploads,
|
|
2
|
+
// as `app/index.js`): the worker builds the bundle once its container runs, and the create
|
|
3
|
+
// uploads the Worker with it. Nothing was served here before, so a visitor is told to come back.
|
|
4
|
+
export default {
|
|
5
|
+
fetch: () =>
|
|
6
|
+
new Response("The company's app is being set up: try again in a minute.\n", {
|
|
7
|
+
status: 503,
|
|
8
|
+
headers: { "content-type": "text/plain; charset=utf-8", "retry-after": "30" },
|
|
9
|
+
}),
|
|
10
|
+
};
|
package/dist/sites.d.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { type SitesBundle } from "@sanoma-ai/workflows/shared";
|
|
2
|
-
import type { CloudflareApi, Upload } from "./ops.ts";
|
|
3
|
-
/** The binding the bundle reads its assets through (`WorkerEnv.ASSETS`). */
|
|
4
|
-
export declare const ASSETS = "ASSETS";
|
|
5
|
-
/**
|
|
6
|
-
* The plain-text binding of a Worker whose sites upload is still to be made: the deploy's upload
|
|
7
|
-
* sets it, `serveSites`' takes it off. A read that finds it plans an update, which makes that upload.
|
|
8
|
-
*/
|
|
9
|
-
export declare const SITES_PENDING = "SANOMA_SITES_PENDING";
|
|
10
|
-
/**
|
|
11
|
-
* How the Worker serves its assets: the client build under `/assets/` straight from them (a path
|
|
12
|
-
* there they lack goes on to the Worker, as the app's own do), every other path through the
|
|
13
|
-
* Worker first, which answers a prerendered page from them itself; never an HTML rewrite or a
|
|
14
|
-
* fallback page of the assets' own.
|
|
15
|
-
*/
|
|
16
|
-
export declare const ASSETS_CONFIG: {
|
|
17
|
-
readonly html_handling: "none";
|
|
18
|
-
readonly not_found_handling: "none";
|
|
19
|
-
readonly run_worker_first: readonly ["/*", "!/assets/*"];
|
|
20
|
-
};
|
|
21
|
-
/** An asset's hash as a session's manifest names it: as the API reference's example has it, sha256 over its base64 and its extension, 32 hex digits. */
|
|
22
|
-
export declare const assetHash: (base64: string, path: string) => string;
|
|
23
|
-
interface Polling {
|
|
24
|
-
/** How long the container may take to build the bundle. */
|
|
25
|
-
startTimeoutMs: number;
|
|
26
|
-
/** How long between two asks. */
|
|
27
|
-
pollMs: number;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* The bundle the container at `url` built, asked with the instance token until it answers it
|
|
31
|
-
* (202 while it builds); undefined when it has no site to serve on the Worker (404), or the
|
|
32
|
-
* runtime has no such route: a 401 or a 404 without `SITES_BUNDLE_HEADER`, an image from before
|
|
33
|
-
* it, whose staff login refuses the path (or whose app has no page there). A failed build, or a
|
|
34
|
-
* token it refuses, is a refusal; any other answer (the object's 503 while the container starts
|
|
35
|
-
* again, an error of Cloudflare's, a body cut off) is asked again, and still one at the timeout
|
|
36
|
-
* is a retryable `DriverError`.
|
|
37
|
-
*/
|
|
38
|
-
export declare function sitesBundle(cf: CloudflareApi, script: string, url: string, token: string, { startTimeoutMs, pollMs }: Polling): Promise<SitesBundle | undefined>;
|
|
39
|
-
/** Puts `assets` in through an upload session for `script`'s next upload: the completion jwt that upload redeems. */
|
|
40
|
-
export declare function uploadAssets(cf: CloudflareApi, script: string, assets: SitesBundle["assets"]): Promise<string>;
|
|
41
|
-
/** What `serveSites` uploads again: the Worker as the deploy uploaded it. */
|
|
42
|
-
interface Deployed extends Polling {
|
|
43
|
-
script: string;
|
|
44
|
-
url: string;
|
|
45
|
-
/** The instance token (`SANOMA_WAKEUP_TOKEN`), which the container's bundle route takes. */
|
|
46
|
-
token: string;
|
|
47
|
-
metadata: Omit<Upload, "migrations">;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* The public sites on the Worker: the bundle the container built, its assets uploaded, and the
|
|
51
|
-
* Worker uploaded again with them and the sites router as its entry (its bindings, image and
|
|
52
|
-
* revision as they were, so its container keeps running). When the container has none to give,
|
|
53
|
-
* the Worker is uploaded again as the deploy uploaded it. Either way without `SITES_PENDING`,
|
|
54
|
-
* which the deploy's upload carries: a Worker whose sites upload failed keeps it, which a read
|
|
55
|
-
* sees, so the next apply makes this upload again.
|
|
56
|
-
*/
|
|
57
|
-
export declare function serveSites(cf: CloudflareApi, { script, url, token, metadata, ...polling }: Deployed): Promise<void>;
|
|
58
|
-
export {};
|
package/dist/sites.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { extname } from "node:path";
|
|
3
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
-
import { DriverError, errorMessage } from "@sanoma-ai/workflows";
|
|
5
|
-
import { SITES_BUNDLE_HEADER, SITES_BUNDLE_PATH } from "@sanoma-ai/workflows/shared";
|
|
6
|
-
import mime from "mime";
|
|
7
|
-
import { refused } from "./errors.js";
|
|
8
|
-
import { SITES_MODULE, sitesRouter } from "./worker.js";
|
|
9
|
-
// The public sites on the company's Worker (sanoma#428): once a deploy's container answers at its
|
|
10
|
-
// revision, the sites bundle it built (`buildWorker` in `@sanoma-ai/app/build`) is downloaded from
|
|
11
|
-
// it with the instance token, its assets go in through an assets upload session, and the Worker is
|
|
12
|
-
// uploaded again with the bundle's modules under `sites/`, the router `worker/sites.js` as its
|
|
13
|
-
// entry and the assets bound as `ASSETS`. A page of a public site without a login then answers
|
|
14
|
-
// from the Worker, never waiting for the container; everything else goes to the object as before.
|
|
15
|
-
/** The binding the bundle reads its assets through (`WorkerEnv.ASSETS`). */
|
|
16
|
-
export const ASSETS = "ASSETS";
|
|
17
|
-
/**
|
|
18
|
-
* The plain-text binding of a Worker whose sites upload is still to be made: the deploy's upload
|
|
19
|
-
* sets it, `serveSites`' takes it off. A read that finds it plans an update, which makes that upload.
|
|
20
|
-
*/
|
|
21
|
-
export const SITES_PENDING = "SANOMA_SITES_PENDING";
|
|
22
|
-
/**
|
|
23
|
-
* How the Worker serves its assets: the client build under `/assets/` straight from them (a path
|
|
24
|
-
* there they lack goes on to the Worker, as the app's own do), every other path through the
|
|
25
|
-
* Worker first, which answers a prerendered page from them itself; never an HTML rewrite or a
|
|
26
|
-
* fallback page of the assets' own.
|
|
27
|
-
*/
|
|
28
|
-
export const ASSETS_CONFIG = {
|
|
29
|
-
html_handling: "none",
|
|
30
|
-
not_found_handling: "none",
|
|
31
|
-
run_worker_first: ["/*", "!/assets/*"],
|
|
32
|
-
};
|
|
33
|
-
/** An asset's hash as a session's manifest names it: as the API reference's example has it, sha256 over its base64 and its extension, 32 hex digits. */
|
|
34
|
-
export const assetHash = (base64, path) => createHash("sha256")
|
|
35
|
-
.update(base64 + extname(path).slice(1))
|
|
36
|
-
.digest("hex")
|
|
37
|
-
.slice(0, 32);
|
|
38
|
-
/**
|
|
39
|
-
* The bundle the container at `url` built, asked with the instance token until it answers it
|
|
40
|
-
* (202 while it builds); undefined when it has no site to serve on the Worker (404), or the
|
|
41
|
-
* runtime has no such route: a 401 or a 404 without `SITES_BUNDLE_HEADER`, an image from before
|
|
42
|
-
* it, whose staff login refuses the path (or whose app has no page there). A failed build, or a
|
|
43
|
-
* token it refuses, is a refusal; any other answer (the object's 503 while the container starts
|
|
44
|
-
* again, an error of Cloudflare's, a body cut off) is asked again, and still one at the timeout
|
|
45
|
-
* is a retryable `DriverError`.
|
|
46
|
-
*/
|
|
47
|
-
export async function sitesBundle(cf, script, url, token, { startTimeoutMs, pollMs }) {
|
|
48
|
-
const until = Date.now() + startTimeoutMs;
|
|
49
|
-
for (;;) {
|
|
50
|
-
let last;
|
|
51
|
-
try {
|
|
52
|
-
const res = await cf.send(`${url}${SITES_BUNDLE_PATH}`, {
|
|
53
|
-
method: "GET",
|
|
54
|
-
headers: { authorization: `Bearer ${token}` },
|
|
55
|
-
});
|
|
56
|
-
const route = res.headers.has(SITES_BUNDLE_HEADER);
|
|
57
|
-
if (!route && (res.status === 401 || res.status === 404))
|
|
58
|
-
return void (await res.body?.cancel());
|
|
59
|
-
const text = await res.text();
|
|
60
|
-
const body = parsed(text);
|
|
61
|
-
if (route && res.status === 200 && body !== undefined)
|
|
62
|
-
return body;
|
|
63
|
-
const error = body?.error;
|
|
64
|
-
const said = typeof error === "string" ? error : `${SITES_BUNDLE_PATH} answered ${res.status}`;
|
|
65
|
-
if (route && res.status === 404)
|
|
66
|
-
return undefined;
|
|
67
|
-
if (route && res.status !== 200 && res.status !== 202) {
|
|
68
|
-
throw refused(`${script}: its container did not give the sites bundle (its sites are served from the container meanwhile): ${said}`);
|
|
69
|
-
}
|
|
70
|
-
last = !route ? said : res.status === 202 ? "it is building the bundle" : "its bundle was cut off";
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
if (!(error instanceof TypeError || (error instanceof Error && error.name === "TimeoutError")))
|
|
74
|
-
throw error;
|
|
75
|
-
// No answer this time (the container stopped under it, a timeout, a body cut off): the next ask starts it again.
|
|
76
|
-
last = errorMessage(error);
|
|
77
|
-
}
|
|
78
|
-
if (Date.now() >= until) {
|
|
79
|
-
throw new DriverError(`cloudflare: ${script}: no sites bundle from its container in ${Math.round(startTimeoutMs / 1000)} s (its sites are served from the container meanwhile): ${last}`, { retryable: true });
|
|
80
|
-
}
|
|
81
|
-
await delay(pollMs);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/** `text` as JSON; undefined when it is none (a body cut off where the JSON is). */
|
|
85
|
-
const parsed = (text) => {
|
|
86
|
-
try {
|
|
87
|
-
return JSON.parse(text);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
/** Puts `assets` in through an upload session for `script`'s next upload: the completion jwt that upload redeems. */
|
|
94
|
-
export async function uploadAssets(cf, script, assets) {
|
|
95
|
-
const byHash = new Map();
|
|
96
|
-
const manifest = {};
|
|
97
|
-
for (const { path, content } of assets) {
|
|
98
|
-
const hash = assetHash(content, path);
|
|
99
|
-
manifest[path] = { hash, size: Buffer.byteLength(content, "base64") };
|
|
100
|
-
byHash.set(hash, { hash, base64: content, contentType: mime.getType(path) ?? "application/octet-stream" });
|
|
101
|
-
}
|
|
102
|
-
const { jwt, buckets } = await cf.assetsSession(script, manifest);
|
|
103
|
-
// Every asset already in: the session's jwt is the completion one.
|
|
104
|
-
if (!buckets.length)
|
|
105
|
-
return jwt;
|
|
106
|
-
// Every bucket's files first, so a hash no asset has is refused before any request starts.
|
|
107
|
-
const files = buckets.map((bucket) => bucket.map((hash) => {
|
|
108
|
-
const file = byHash.get(hash);
|
|
109
|
-
if (!file)
|
|
110
|
-
throw refused(`${script}: the assets upload session asks for ${hash}, which no asset hashes to`);
|
|
111
|
-
return file;
|
|
112
|
-
}));
|
|
113
|
-
// Independent requests under one jwt: the one that completes the set answers the completion jwt.
|
|
114
|
-
const answers = await Promise.all(files.map((bucket) => cf.uploadAssets(jwt, bucket)));
|
|
115
|
-
const completion = answers.find((answer) => answer !== undefined);
|
|
116
|
-
if (!completion)
|
|
117
|
-
throw refused(`${script}: the last bucket of assets was uploaded, and Cloudflare gave no completion jwt`);
|
|
118
|
-
return completion;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* The public sites on the Worker: the bundle the container built, its assets uploaded, and the
|
|
122
|
-
* Worker uploaded again with them and the sites router as its entry (its bindings, image and
|
|
123
|
-
* revision as they were, so its container keeps running). When the container has none to give,
|
|
124
|
-
* the Worker is uploaded again as the deploy uploaded it. Either way without `SITES_PENDING`,
|
|
125
|
-
* which the deploy's upload carries: a Worker whose sites upload failed keeps it, which a read
|
|
126
|
-
* sees, so the next apply makes this upload again.
|
|
127
|
-
*/
|
|
128
|
-
export async function serveSites(cf, { script, url, token, metadata, ...polling }) {
|
|
129
|
-
const bundle = await sitesBundle(cf, script, url, token, polling);
|
|
130
|
-
const bindings = metadata.bindings.filter((b) => b.name !== SITES_PENDING);
|
|
131
|
-
if (!bundle)
|
|
132
|
-
return cf.upload(script, { ...metadata, bindings });
|
|
133
|
-
if (bundle.mainModule !== "index.js") {
|
|
134
|
-
throw refused(`${script}: the sites bundle's entry is ${bundle.mainModule}; the router imports sites/index.js`);
|
|
135
|
-
}
|
|
136
|
-
const jwt = await uploadAssets(cf, script, bundle.assets);
|
|
137
|
-
await cf.upload(script, {
|
|
138
|
-
...metadata,
|
|
139
|
-
main_module: SITES_MODULE,
|
|
140
|
-
bindings: [...bindings, { type: "assets", name: ASSETS }],
|
|
141
|
-
assets: { jwt, config: ASSETS_CONFIG },
|
|
142
|
-
modules: [
|
|
143
|
-
...metadata.modules,
|
|
144
|
-
await sitesRouter(),
|
|
145
|
-
...bundle.modules.map(({ name, type, content }) => ({
|
|
146
|
-
name: `sites/${name}`,
|
|
147
|
-
type,
|
|
148
|
-
content: Buffer.from(content, "base64"),
|
|
149
|
-
})),
|
|
150
|
-
],
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
//# sourceMappingURL=sites.js.map
|
package/dist/sites.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../src/sites.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SitesBundle } from "@sanoma-ai/workflows/shared";
|
|
2
|
-
/** The Worker's entry module, which imports `cloudflare:workers` and `instance.js`. */
|
|
3
|
-
export declare const MAIN_MODULE = "index.js";
|
|
4
|
-
/** The entry module of a Worker that serves the public sites, beside `MODULES` and the sites bundle under `sites/` (`sites.ts`). */
|
|
5
|
-
export declare const SITES_MODULE = "sites.js";
|
|
6
|
-
/** The Durable Object class the Worker exports, which the container application runs behind. */
|
|
7
|
-
export declare const OBJECT_CLASS = "SanomaInstance";
|
|
8
|
-
/** One module of the Worker: its file name, which the upload names it by, and its source. */
|
|
9
|
-
export interface WorkerModule {
|
|
10
|
-
name: string;
|
|
11
|
-
content: string | Uint8Array<ArrayBuffer>;
|
|
12
|
-
/** What the module is, as the upload types its part: an ES module unless it says. */
|
|
13
|
-
type?: SitesBundle["modules"][number]["type"];
|
|
14
|
-
}
|
|
15
|
-
/** The Worker's modules, read from the package once. */
|
|
16
|
-
export declare function workerModules(): Promise<WorkerModule[]>;
|
|
17
|
-
/** The sites router, `worker/sites.js`, read from the package once. */
|
|
18
|
-
export declare function sitesRouter(): Promise<WorkerModule>;
|
|
19
|
-
export { HEALTH_PATH, IMAGE, INACTIVITY_MS, Instance, NAMESPACE, type ObjectState, worker, } from "../worker/instance.js";
|
package/dist/worker.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
// The company's Worker, as the package ships it in `worker/`: the files `cloudflare.container`
|
|
3
|
-
// uploads, and the object's code, which the fake runs in this process.
|
|
4
|
-
const WORKER = new URL("../worker/", import.meta.url);
|
|
5
|
-
/** The Worker's entry module, which imports `cloudflare:workers` and `instance.js`. */
|
|
6
|
-
export const MAIN_MODULE = "index.js";
|
|
7
|
-
/** The modules uploaded, the entry first. */
|
|
8
|
-
const MODULES = [MAIN_MODULE, "instance.js"];
|
|
9
|
-
/** The entry module of a Worker that serves the public sites, beside `MODULES` and the sites bundle under `sites/` (`sites.ts`). */
|
|
10
|
-
export const SITES_MODULE = "sites.js";
|
|
11
|
-
/** The Durable Object class the Worker exports, which the container application runs behind. */
|
|
12
|
-
export const OBJECT_CLASS = "SanomaInstance";
|
|
13
|
-
let modules;
|
|
14
|
-
/** The Worker's modules, read from the package once. */
|
|
15
|
-
export function workerModules() {
|
|
16
|
-
modules ??= Promise.all(MODULES.map(async (name) => ({ name, content: await readFile(new URL(name, WORKER), "utf8") }))).catch((err) => {
|
|
17
|
-
modules = undefined;
|
|
18
|
-
throw err;
|
|
19
|
-
});
|
|
20
|
-
return modules;
|
|
21
|
-
}
|
|
22
|
-
let router;
|
|
23
|
-
/** The sites router, `worker/sites.js`, read from the package once. */
|
|
24
|
-
export function sitesRouter() {
|
|
25
|
-
router ??= readFile(new URL(SITES_MODULE, WORKER), "utf8").then((content) => ({ name: SITES_MODULE, content }));
|
|
26
|
-
return router;
|
|
27
|
-
}
|
|
28
|
-
export { HEALTH_PATH, IMAGE, INACTIVITY_MS, Instance, NAMESPACE, worker, } from "../worker/instance.js";
|
|
29
|
-
//# sourceMappingURL=worker.js.map
|
package/dist/worker.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,+FAA+F;AAC/F,uEAAuE;AAEvE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAEtD,uFAAuF;AACvF,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;AACtC,6CAA6C;AAC7C,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,aAAa,CAAU,CAAC;AACtD,oIAAoI;AACpI,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AAEvC,gGAAgG;AAChG,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAU7C,IAAI,OAA4C,CAAC;AAEjD,wDAAwD;AACxD,MAAM,UAAU,aAAa;IAC3B,OAAO,KAAK,OAAO,CAAC,GAAG,CACrB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAChG,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACvB,OAAO,GAAG,SAAS,CAAC;QACpB,MAAM,GAAG,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,IAAI,MAAyC,CAAC;AAE9C,uEAAuE;AACvE,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAChH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,OAAO,EACL,WAAW,EACX,KAAK,EACL,aAAa,EACb,QAAQ,EACR,SAAS,EAET,MAAM,GACP,MAAM,uBAAuB,CAAC"}
|