@sanoma-ai/connector-cloudflare 0.0.0-stage → 0.1.0
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/LICENSE +105 -0
- package/README.md +161 -2
- package/dist/connector.d.ts +72 -0
- package/dist/connector.js +140 -0
- package/dist/connector.js.map +1 -0
- package/dist/driver.d.ts +15 -0
- package/dist/driver.js +163 -0
- package/dist/driver.js.map +1 -0
- package/dist/fake.d.ts +75 -0
- package/dist/fake.js +380 -0
- package/dist/fake.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/ops.d.ts +238 -0
- package/dist/ops.js +359 -0
- package/dist/ops.js.map +1 -0
- package/dist/resources.d.ts +37 -0
- package/dist/resources.js +8 -0
- package/dist/resources.js.map +1 -0
- package/dist/steps.d.ts +6 -0
- package/dist/steps.js +39 -0
- package/dist/steps.js.map +1 -0
- package/dist/worker.d.ts +12 -0
- package/dist/worker.js +21 -0
- package/dist/worker.js.map +1 -0
- package/features/cloudflare.feature +122 -0
- package/logo.svg +1 -0
- package/package.json +81 -3
- package/worker/index.js +22 -0
- package/worker/instance.d.ts +43 -0
- package/worker/instance.js +214 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-ALv2
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Sanoma-AI
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sanoma-ai/connector-cloudflare
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Cloudflare Containers](https://developers.cloudflare.com/containers/) as a host that stops when quiet, and its [Durable Object](https://developers.cloudflare.com/durable-objects/)'s [alarm](https://developers.cloudflare.com/durable-objects/api/alarms/) as the wakeup that starts it again, for [`@sanoma-ai/workflows`](https://www.npmjs.com/package/@sanoma-ai/workflows). Both are one Worker per company, `sanoma-<name>`, on the account's workers.dev subdomain.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @sanoma-ai/connector-cloudflare
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Both types are writable: `read` and `import` (effect `read`), and the change operations only the apply workflow may call, `plan` (effect `read`), `create`, `update` and `delete` (effect `write`). The container is a host ([Hosts](https://github.com/Sanoma-AI/sanoma/blob/main/packages/workflows/README.md#hosts)) and the alarm a wakeup, with one more operation, `register`, which the worker calls:
|
|
12
|
+
|
|
13
|
+
| Type | Operations | Import id |
|
|
14
|
+
| ----------- | ----------------------------------------------------------------------------------------- | -------------- |
|
|
15
|
+
| `container` | `cloudflare.container.read`, `.import`, `.plan`, `.create`, `.update`, `.delete` | its name |
|
|
16
|
+
| `alarm` | `cloudflare.alarm.read`, `.import`, `.plan`, `.create`, `.update`, `.delete`, `.register` | its host's URL |
|
|
17
|
+
|
|
18
|
+
A data file declares them with the constructors in `@sanoma-ai/connector-cloudflare/resources`. Declaring the alarm is the whole switch for scaling to zero: without it, nothing starts a stopped container at a deadline, and a sleeping run waits for the next request.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// resources/infra.ts
|
|
22
|
+
import { cloudflare } from "@sanoma-ai/connector-cloudflare/resources";
|
|
23
|
+
import { secret } from "sanoma/resources";
|
|
24
|
+
|
|
25
|
+
export const host = cloudflare.container({
|
|
26
|
+
name: "acme", // the Worker sanoma-acme, at https://sanoma-acme.<subdomain>.workers.dev
|
|
27
|
+
image: "docker.io/sanomaai/runtime:0.7.0",
|
|
28
|
+
source: repo, // a declared github.repository, or a clone URL
|
|
29
|
+
ref: "main",
|
|
30
|
+
port: 3000,
|
|
31
|
+
secrets: {
|
|
32
|
+
SANOMA_DATABASE_URL: secret("DATABASE_URL"),
|
|
33
|
+
SANOMA_SECRETS_KEY: secret("SECRETS_KEY"),
|
|
34
|
+
SANOMA_WAKEUP_TOKEN: secret("SANOMA_WAKEUP_TOKEN", { generate: true }), // made at the first apply
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
export const wakeup = cloudflare.alarm({ host });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Field | What it is |
|
|
41
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `name` | The company's name, its identity: lowercase letters, digits and dashes, up to 56; the Worker and its application are `sanoma-<name>` |
|
|
43
|
+
| `image` | The Sanoma runtime image, pinned to a tag or digest, never `latest`; a registry Cloudflare pulls from (Docker Hub, or Cloudflare's own) |
|
|
44
|
+
| `source` | The company repository: a clone URL, or a declared `github.repository` |
|
|
45
|
+
| `ref` | The branch it runs, never a commit: apply resolves it and sets the hidden `revision`, so a merge is drift on the container |
|
|
46
|
+
| `port` | The port the runtime listens on in the container (its `PORT`) |
|
|
47
|
+
| `secrets` | Environment variables, each a `secret(...)`: the Worker's secret bindings, write-only, never in an output, the ledger or a checkpoint |
|
|
48
|
+
| `domains` | Optional: a public site's domain, by site name, passed as `SANOMA_DOMAINS`; routing a domain to the Worker is not done here yet |
|
|
49
|
+
| `id`, `url`, `healthy` | Cloudflare's: the application's id, the Worker's workers.dev URL, and whether it answers |
|
|
50
|
+
|
|
51
|
+
The host's outputs are `{ url, revision, healthy }`. Its capabilities: stops when quiet, a public URL, it runs an image rather than building one, no fixed egress. `healthy` is the application existing and the Worker answering `/__sanoma/health`, which does not start a stopped container: a stopped host is healthy, and a read or a drift check leaves it stopped.
|
|
52
|
+
|
|
53
|
+
The alarm's only field is `host`, the container it wakes (its URL); its state is `{ host, at, generation, rang, healthy }`, the wake it holds, when its alarm last rang the host's `/wake` for it, and whether the host's Worker took the company's token. `register` takes `{ at, generation }`, an ISO 8601 time and a generation from 1, and the Worker keeps it only when it is newer, so a retry or a late duplicate changes nothing. It names no host: on the host, the worker reads the host's URL from `SANOMA_HOST_URL`, which the Worker gives the container; off it, from the credential `alarm.create` stored.
|
|
54
|
+
|
|
55
|
+
The logo is Cloudflare's cloud from [Simple Icons](https://simpleicons.org/?q=cloudflare) (CC0), there only to identify the service an operation calls.
|
|
56
|
+
|
|
57
|
+
## The Worker
|
|
58
|
+
|
|
59
|
+
The package ships the Worker it uploads, `worker/`: `index.js`, which exports the Durable Object class `SanomaInstance` over `DurableObject` from `cloudflare:workers`, and `instance.js`, the object's code with no imports (typed by `instance.d.ts`), which the fake runs in Node so both kits check the code Cloudflare runs. Every request to the Worker goes to its one object, which:
|
|
60
|
+
|
|
61
|
+
- starts the container on a request at the revision and image the Worker's `SANOMA_REVISION` and `SANOMA_IMAGE` name, with the Worker's text bindings (plain and secret) as its environment and both as labels (the image's as a digest, since a label holds 64 bytes); a running container that does not carry both is destroyed and started again (a deploy);
|
|
62
|
+
- waits for the container's `/healthz` (up to ten minutes, the clone and install included), sets its inactivity timeout to ten minutes, and proxies the request to its `port`;
|
|
63
|
+
- answers `GET /__sanoma/health` with `{ revision, running }` without starting the container, and with `?start=1` once the container answers, which is what a create or update waits for;
|
|
64
|
+
- holds the company's one wake at `/__sanoma/wake`, with `Authorization: Bearer <SANOMA_WAKEUP_TOKEN>`: `GET` shows `{ at, generation, rang? }`, `PUT { at, generation }` sets the object's alarm when the generation is newer, `DELETE` clears it. Without the token among the host's secrets it answers 404, with another one 401;
|
|
65
|
+
- at the alarm, starts the container and calls its `/wake?generation=N`, then notes when (`rang`); a failure throws, so Cloudflare rings the alarm again.
|
|
66
|
+
|
|
67
|
+
Cloudflare stops the container after ten quiet minutes; the object, its storage and its alarm stay, and the next request or alarm starts the container again. A create uploads the Worker with the object's migration (`new_sqlite_classes`) and its class as a container's (`containers: [{ class_name }]`, as Wrangler sends it), turns its workers.dev URL on, and makes the container application for the object's namespace (the `namespace_id` the Worker's settings then show; `basic`, one instance at most, none until a request); an update uploads it again and, for a new image, rolls the application out to it (`rolling`, all at once). Either waits for the Worker to answer at the new revision. The image's label is the one the Worker names, not the one Cloudflare started: whether a container started while a rollout is still going runs the new image is what the live run checks. A secret's new value goes up with the next update for another reason (a plan compares secret names, never values: the Worker's settings never show them), and a running container keeps the old one until it starts again. A delete deletes the application, then the Worker with its object's storage. A Worker of that name that is not Sanoma's (no `SANOMA_INSTANCE` binding) reads as gone, and a create or a delete refuses to touch it.
|
|
68
|
+
|
|
69
|
+
## Scale to zero
|
|
70
|
+
|
|
71
|
+
1. **The connector.** Add `cloudflare` to the connectors in `sanoma.config.ts`: the worker on the host calls `cloudflare.alarm.register` through it.
|
|
72
|
+
2. **Credentials.** On the connector's page, `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` (below).
|
|
73
|
+
3. **Go live.** `infra.ts` declares the host with `SANOMA_WAKEUP_TOKEN: secret("SANOMA_WAKEUP_TOKEN", { generate: true })` among its secrets (apply makes it), and, after it, `export const wakeup = cloudflare.alarm({ host })`. One apply makes the Worker, waits for it, then the alarm, which checks the Worker holds the token.
|
|
74
|
+
4. **A sleeping run.** Start a workflow that sleeps past ten quiet minutes. Before the container stops the worker registers the sleep's end; at `at` the object's alarm starts the container and calls `/wake`, and the run continues.
|
|
75
|
+
|
|
76
|
+
To stop scaling to zero, delete the `wakeup` line and apply: the alarm is deleted, and the next request still starts the container.
|
|
77
|
+
|
|
78
|
+
## Driver
|
|
79
|
+
|
|
80
|
+
`@sanoma-ai/connector-cloudflare/driver` calls Cloudflare through its official SDK, [`cloudflare`](https://www.npmjs.com/package/cloudflare) (Stainless), with its own retries off (the runtime retries what is retryable), and calls the company's Worker with `fetch`. The `cloudflare` connector the package root exports carries it and its fake (`withSetup` from `@sanoma-ai/workflows`), so a config names the connector alone and workflows never import the driver (the lint refuses that).
|
|
81
|
+
|
|
82
|
+
`./driver` (`cloudflareDriver`; `cloudflareDriver({ timeoutMs, startTimeoutMs, pollMs })`: 30 s a request, ten minutes for the Worker to answer at a new revision, two seconds between tries) and `./fake` (`fakeCloudflare`) are internal entries, for tests.
|
|
83
|
+
|
|
84
|
+
A read is the Worker's settings (its bindings: what it runs is its plain-text bindings, never a secret's value), the application of its name (its image) and the account's subdomain (its URL); a plan compares the declared image, source, ref, revision, port, domains (left out too) and secret names with them. The requests are made one after another, in the order the fixtures hold.
|
|
85
|
+
|
|
86
|
+
### Environment
|
|
87
|
+
|
|
88
|
+
It reads these on every call, never when the config loads:
|
|
89
|
+
|
|
90
|
+
| Variable | What it is |
|
|
91
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `CLOUDFLARE_API_TOKEN` | An API token with Workers Scripts Edit and Workers Containers Edit (Write, in the API's terms) |
|
|
93
|
+
| `CLOUDFLARE_ACCOUNT_ID` | The account's id, 32 hexadecimal digits, on its dashboard's overview |
|
|
94
|
+
|
|
95
|
+
When either is unset, a call fails, not retryable, naming it. The driver declares both in its `env` ([Credentials](https://www.npmjs.com/package/@sanoma-ai/workflows#credentials)): the worker starts without them, and the app's connector page shows their status and lets you set them. The alarm's operations need neither: they call the Worker with `SANOMA_WAKEUP_TOKEN` (`alarm.create` mints it when none is stored), and `alarm.create` stores `SANOMA_HOST_URL` once the host answered, for a register made off the host; it keeps the first one stored, which the Worker's own binding overrides on the host. A read without the token reads the alarm as unhealthy, so a plan to delete it still goes on.
|
|
96
|
+
|
|
97
|
+
### Errors and retries
|
|
98
|
+
|
|
99
|
+
A 429, a 5xx, a timeout or no reply is retryable; any other 4xx is not. `status` and `vendorCode` (Cloudflare's first error `code`) are kept. The driver's own refusals are not retryable: `exists` (a Worker of that name that is not Sanoma's, which is neither overwritten nor deleted), `plan_changed` (an update whose plan changed since it was approved, or is not an update: a new name is a delete and a create), `no_token` and `token` (a host without the company's `SANOMA_WAKEUP_TOKEN`, or with another one), and a Worker that did not answer at its revision in time, which says what it answered.
|
|
100
|
+
|
|
101
|
+
### Plan
|
|
102
|
+
|
|
103
|
+
Containers need the Workers Paid plan. An instance is billed while it runs, by its `basic` size, and not while it is stopped; the Worker and its object are billed per request.
|
|
104
|
+
|
|
105
|
+
### Testing the driver
|
|
106
|
+
|
|
107
|
+
`test/driver.test.ts` and the features replay Cloudflare's replies (`test/fixtures`) with [`@sanoma-ai/testing/replay`](https://github.com/Sanoma-AI/sanoma/blob/main/packages/testing/README.md#replaying-a-vendors-api): `pnpm vitest run connectors/cloudflare` needs no account. The fixtures are **written from Cloudflare's API reference, not recorded**: no account was at hand when the connector was written. A recording replaces them (`SANOMA_LIVE=1 SANOMA_RECORD=1` on the features test), losing the account's id and subdomain, ids and tokens; until then, a reply the reference gets wrong is wrong here too. The upload's `containers` field is Wrangler's and not in the reference or the SDK's types: the live run is what checks it. The driver tests only replay.
|
|
108
|
+
|
|
109
|
+
### Running it live
|
|
110
|
+
|
|
111
|
+
`test/live.ts`, run by hand, runs the Host and Wakeup kits against a real account, then prints how long a cold wake took (a request to a stopped container until it answers). It makes a Worker and an application named `sanoma-host-check` (`CLOUDFLARE_TEST_NAME` changes it), takes about an hour (each stop is Cloudflare's own ten quiet minutes), and deletes both at the end:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
pnpm build
|
|
115
|
+
SANOMA_LIVE=1 CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... \
|
|
116
|
+
CLOUDFLARE_TEST_CHECKOUT=~/src/host-check-company CLOUDFLARE_TEST_DATABASE_URL=postgresql://... \
|
|
117
|
+
node connectors/cloudflare/test/live.ts
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
It needs:
|
|
121
|
+
|
|
122
|
+
- a published runtime image Cloudflare can pull, `docker.io/sanomaai/runtime:<version>` (the `sanoma` package's version; `CLOUDFLARE_TEST_IMAGE` names another);
|
|
123
|
+
- `CLOUDFLARE_TEST_CHECKOUT`: a clone of a company repository whose `origin` Cloudflare can clone and this machine can push to (each deploy check pushes an empty commit), holding the kits' company: [`connectors/docker/test/fixtures/company`](../docker/test/fixtures/company) with `sanoma` from npm, its config registering `wakeupCheckWorkflows` (the host checks' workflows and a tick every minute);
|
|
124
|
+
- `CLOUDFLARE_TEST_DATABASE_URL`: a Postgres both the container and this machine reach, empty, the company's;
|
|
125
|
+
- Postgres here for the operator that applies the host (`pnpm db:up`).
|
|
126
|
+
|
|
127
|
+
## Steps and features
|
|
128
|
+
|
|
129
|
+
The connector carries steps for [scenarios](https://github.com/Sanoma-AI/sanoma/blob/main/packages/workflows/README.md#steps-a-connector-ships) (`cloudflareSteps`, attached with `withSetup`):
|
|
130
|
+
|
|
131
|
+
| Keyword | Step | What it does |
|
|
132
|
+
| ------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
+
| Given | `a Cloudflare container for {string} at revision {string}` | Creates the company's container through `cloudflare.container.create`, its token generated, named `host` (`$host.state.url`) |
|
|
134
|
+
| Then | `a Cloudflare wake is registered with generation {int}` | The run registered a wake with that generation |
|
|
135
|
+
|
|
136
|
+
`features/cloudflare.feature` creates, reads, plans and deletes a container, and creates an alarm and registers with it, on the fake and on the replies in `test/fixtures` (named after each scenario): `test/features.test.ts` runs them with [`describeFeatures`](https://github.com/Sanoma-AI/sanoma/blob/main/packages/testing/README.md#a-connectors-own-features). The package ships `features/`.
|
|
137
|
+
|
|
138
|
+
## Testing
|
|
139
|
+
|
|
140
|
+
`@sanoma-ai/connector-cloudflare/fake` is an in-memory Cloudflare account, the fake the connector carries: each sandbox run makes one of its own. Its Workers answer on ports of this machine, one per Worker name, and run the package's own object code over a `ctx` in this process: storage in memory, an alarm on a timer (again after a throw), and a container that runs what `start` returns for the image and revision it was started at (by default a stub that answers `/healthz` and `/wake`). An upload restarts the object over its new bindings; a rollout stops the container, as Cloudflare replaces it.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { fakeCloudflare } from "@sanoma-ai/connector-cloudflare/fake";
|
|
144
|
+
import { inProcessHost } from "@sanoma-ai/testing/host";
|
|
145
|
+
|
|
146
|
+
const fake = fakeCloudflare({
|
|
147
|
+
start: ({ revision, env }) => inProcessHost(config, databaseUrl)({ port: 0, revision, secrets: env }),
|
|
148
|
+
});
|
|
149
|
+
// ...apply a container, then:
|
|
150
|
+
await fake.stop("acme"); // as ten quiet minutes would: the next request starts it
|
|
151
|
+
await fake.fire(); // rings every armed alarm now
|
|
152
|
+
fake.wakes(); // each /wake the containers answered
|
|
153
|
+
await fake.close();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`test/host.test.ts` and `test/wakeup.test.ts` run the Host and Wakeup kits over the fake this way, the company's runtime in this process; `test/worker.test.ts` runs the object's code over a stubbed `ctx`. Nothing here runs the Worker in `workerd`: [Miniflare](https://developers.cloudflare.com/workers/testing/miniflare/) would, which this package does not add.
|
|
157
|
+
|
|
158
|
+
## Later
|
|
159
|
+
|
|
160
|
+
Custom domains (routing `domains` to the Worker), the `durable_object` scheduling policy and snapshots, static egress, outbound Workers, and observability (the Worker's and the container's logs) are not done here yet.
|
|
161
|
+
|
|
162
|
+
License: FSL-1.1-ALv2.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type Connector } from "@sanoma-ai/workflows";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* `cloudflare.container`: the company's Worker, `sanoma-<name>`, on the account's workers.dev
|
|
5
|
+
* subdomain, whose one Durable Object (`SanomaInstance`) starts the runtime image in a Container
|
|
6
|
+
* on a request and stops it ten minutes after the last (`Host`: stops when quiet, its URL public).
|
|
7
|
+
* What it runs is in the Worker's plain-text bindings, so a read tells its revision with no
|
|
8
|
+
* container running. A deploy uploads the Worker again; a new image rolls the application out.
|
|
9
|
+
*/
|
|
10
|
+
export declare const container: import("@sanoma-ai/workflows").Resource<z.ZodObject<{
|
|
11
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
|
+
name: z.ZodString;
|
|
13
|
+
image: z.ZodString;
|
|
14
|
+
source: z.ZodString;
|
|
15
|
+
ref: z.ZodString;
|
|
16
|
+
revision: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
17
|
+
secrets: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
18
|
+
port: z.ZodNumber;
|
|
19
|
+
domains: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
20
|
+
url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21
|
+
healthy: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
22
|
+
}, z.core.$strip>, "cloudflare.container", {
|
|
23
|
+
readonly source: {
|
|
24
|
+
readonly type: "github.repository";
|
|
25
|
+
readonly by: readonly ["http_clone_url"];
|
|
26
|
+
};
|
|
27
|
+
}, true>;
|
|
28
|
+
/**
|
|
29
|
+
* `cloudflare.alarm({ host })`: the Durable Object alarm of the host's own Worker, which starts
|
|
30
|
+
* the container at the time the worker registered and calls its `/wake?generation=N`. The alarm
|
|
31
|
+
* is the host's object's, so it has no id of its own: its binding is the host's URL, and a new
|
|
32
|
+
* host is a new alarm. The host's `secrets` must carry `SANOMA_WAKEUP_TOKEN`, which `create`
|
|
33
|
+
* mints when none is stored.
|
|
34
|
+
*/
|
|
35
|
+
export declare const alarm: import("@sanoma-ai/workflows").Resource<z.ZodObject<{
|
|
36
|
+
host: z.ZodString;
|
|
37
|
+
at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
generation: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
39
|
+
rang: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
healthy: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
41
|
+
}, z.core.$strip>, "cloudflare.alarm", {
|
|
42
|
+
readonly host: {
|
|
43
|
+
readonly type: readonly ["cloudflare.container"];
|
|
44
|
+
readonly by: readonly ["url"];
|
|
45
|
+
};
|
|
46
|
+
}, true>;
|
|
47
|
+
/** `alarm.register`, in the alarm type's own group, beside its resource operations. */
|
|
48
|
+
declare const registerSpec: {
|
|
49
|
+
register: {
|
|
50
|
+
effect: "write";
|
|
51
|
+
idempotent: true;
|
|
52
|
+
description: string;
|
|
53
|
+
input: z.ZodObject<{
|
|
54
|
+
at: z.ZodISODateTime;
|
|
55
|
+
generation: z.ZodNumber;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
output: z.ZodObject<{}, z.core.$strip>;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** The connector's specs: both types' operations, and `register` in the alarm's group. */
|
|
61
|
+
type CloudflareSpecs = {
|
|
62
|
+
container: (typeof container)["ops"];
|
|
63
|
+
alarm: (typeof alarm)["ops"] & typeof registerSpec;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The connector's operations: each type's (`read`, `import`, `plan`, `create`, `update`,
|
|
67
|
+
* `delete`) and `alarm.register` in the alarm's group (Supabase's pattern).
|
|
68
|
+
*/
|
|
69
|
+
export declare const cloudflareOps: Connector<"cloudflare", CloudflareSpecs>;
|
|
70
|
+
/** The connector's type: both types, with `alarm.register`. */
|
|
71
|
+
export type CloudflareOps = typeof cloudflareOps;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineConnector, defineResource, vendorInfo } from "@sanoma-ai/workflows";
|
|
2
|
+
import { DOMAIN_NAME, SITE_NAME } from "@sanoma-ai/workflows/shared";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/*
|
|
5
|
+
* Cloudflare, over its official SDK (`cloudflare`): one Worker per company that runs the company
|
|
6
|
+
* in a Container behind its one Durable Object (`cloudflare.container`, a host), and the same
|
|
7
|
+
* object's alarm, which wakes it (`cloudflare.alarm`, a wakeup). Cloudflare's OpenTofu provider
|
|
8
|
+
* has no container resource, so the schemas are a cut of the API's own fields, by hand.
|
|
9
|
+
*/
|
|
10
|
+
/** A company's name as its Worker's is made of it (`sanoma-<name>`): a DNS label of 63 characters at most. */
|
|
11
|
+
const NAME = /^[a-z0-9](?:[a-z0-9-]{0,54}[a-z0-9])?$/;
|
|
12
|
+
const containerSchema = z.object({
|
|
13
|
+
id: z.string().nullish().describe("The container application's id, which Cloudflare sets"),
|
|
14
|
+
name: z
|
|
15
|
+
.string()
|
|
16
|
+
.regex(NAME, "lower case letters, digits and dashes, 56 at most")
|
|
17
|
+
.describe("The company's name: its Worker is `sanoma-<name>`, its URL `https://sanoma-<name>.<account>.workers.dev`"),
|
|
18
|
+
image: z
|
|
19
|
+
.string()
|
|
20
|
+
.regex(/^[^\s@]+(:[\w][\w.-]{0,127}|@sha256:[0-9a-f]{64})$/, "pin the image to a tag or a digest")
|
|
21
|
+
.refine((image) => !image.endsWith(":latest"), "pin the image to a version, not `latest`")
|
|
22
|
+
.describe("The Sanoma runtime image on Docker Hub, pinned to a tag or a digest: `docker.io/sanomaai/runtime:<version>`"),
|
|
23
|
+
source: z.string().min(1).describe("The company repository's clone URL, or a declared GitHub repository"),
|
|
24
|
+
ref: z.string().min(1).describe("The branch it runs, such as `main`, never a commit"),
|
|
25
|
+
revision: z.string().nullish().describe("The commit it runs, which apply sets from `ref`"),
|
|
26
|
+
secrets: z
|
|
27
|
+
.record(z.string().regex(/^[A-Z_][A-Z0-9_]*$/), z.string())
|
|
28
|
+
.nullish()
|
|
29
|
+
.describe("Environment variables holding secrets, each a `secret(...)`: SANOMA_DATABASE_URL, SANOMA_SECRETS_KEY, SANOMA_WAKEUP_TOKEN"),
|
|
30
|
+
port: z.number().int().min(1).max(65_535).describe("The port the runtime listens on in the container (its `PORT`)"),
|
|
31
|
+
domains: z
|
|
32
|
+
.record(z.string().regex(SITE_NAME), z.string().regex(DOMAIN_NAME))
|
|
33
|
+
// Empty, it would read back as none, a change on every plan.
|
|
34
|
+
.refine((domains) => Object.keys(domains).length > 0, "leave `domains` out rather than empty")
|
|
35
|
+
.nullish()
|
|
36
|
+
.describe("A public site's domain, by site name"),
|
|
37
|
+
url: z.string().nullish().describe("The Worker's URL on workers.dev, which Cloudflare sets"),
|
|
38
|
+
healthy: z
|
|
39
|
+
.boolean()
|
|
40
|
+
.nullish()
|
|
41
|
+
.describe("Whether the application exists and the Worker answers /__sanoma/health, which starts the container"),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* `cloudflare.container`: the company's Worker, `sanoma-<name>`, on the account's workers.dev
|
|
45
|
+
* subdomain, whose one Durable Object (`SanomaInstance`) starts the runtime image in a Container
|
|
46
|
+
* on a request and stops it ten minutes after the last (`Host`: stops when quiet, its URL public).
|
|
47
|
+
* What it runs is in the Worker's plain-text bindings, so a read tells its revision with no
|
|
48
|
+
* container running. A deploy uploads the Worker again; a new image rolls the application out.
|
|
49
|
+
*/
|
|
50
|
+
export const container = defineResource({
|
|
51
|
+
vendor: "cloudflare",
|
|
52
|
+
type: "container",
|
|
53
|
+
title: "Container",
|
|
54
|
+
identity: "name",
|
|
55
|
+
schema: containerSchema,
|
|
56
|
+
fields: {
|
|
57
|
+
immutable: ["name"],
|
|
58
|
+
vendorOwned: ["id", "url", "healthy"],
|
|
59
|
+
writeOnly: ["secrets"],
|
|
60
|
+
references: { source: { type: "github.repository", by: ["http_clone_url"] } },
|
|
61
|
+
},
|
|
62
|
+
find: ({ name }) => name,
|
|
63
|
+
bindingId: (state) => String(state.name),
|
|
64
|
+
writable: true,
|
|
65
|
+
host: {
|
|
66
|
+
capabilities: { lifetime: "stops-when-quiet", publicUrl: true, buildsFromGit: false, staticEgress: false },
|
|
67
|
+
inputs: { source: "source", ref: "ref", secrets: "secrets", port: "port", domains: "domains" },
|
|
68
|
+
revision: "revision",
|
|
69
|
+
outputs: (state) => ({
|
|
70
|
+
url: String(state.url ?? ""),
|
|
71
|
+
...(typeof state.revision === "string" && { revision: state.revision }),
|
|
72
|
+
healthy: state.healthy === true,
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const alarmSchema = z.object({
|
|
77
|
+
host: z
|
|
78
|
+
.string()
|
|
79
|
+
.min(1)
|
|
80
|
+
.describe("The host's URL, whose Worker holds the alarm: a data file names the host's declaration"),
|
|
81
|
+
at: z.string().nullish().describe("When the instance asked to be woken last, null until it has"),
|
|
82
|
+
generation: z.number().int().nullish().describe("The generation of that registration, 0 until one"),
|
|
83
|
+
rang: z
|
|
84
|
+
.string()
|
|
85
|
+
.nullish()
|
|
86
|
+
.describe("When the alarm last rang the host's /wake for that registration, null until it has"),
|
|
87
|
+
healthy: z.boolean().nullish().describe("Whether the host's Worker answers its wake route with this company's token"),
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* `cloudflare.alarm({ host })`: the Durable Object alarm of the host's own Worker, which starts
|
|
91
|
+
* the container at the time the worker registered and calls its `/wake?generation=N`. The alarm
|
|
92
|
+
* is the host's object's, so it has no id of its own: its binding is the host's URL, and a new
|
|
93
|
+
* host is a new alarm. The host's `secrets` must carry `SANOMA_WAKEUP_TOKEN`, which `create`
|
|
94
|
+
* mints when none is stored.
|
|
95
|
+
*/
|
|
96
|
+
export const alarm = defineResource({
|
|
97
|
+
vendor: "cloudflare",
|
|
98
|
+
type: "alarm",
|
|
99
|
+
title: "Alarm",
|
|
100
|
+
schema: alarmSchema,
|
|
101
|
+
fields: {
|
|
102
|
+
immutable: ["host"],
|
|
103
|
+
vendorOwned: ["at", "generation", "rang", "healthy"],
|
|
104
|
+
writeOnly: [],
|
|
105
|
+
references: { host: { type: ["cloudflare.container"], by: ["url"] } },
|
|
106
|
+
},
|
|
107
|
+
bindingId: (state) => String(state.host),
|
|
108
|
+
writable: true,
|
|
109
|
+
// The host's object holds the registered wake: apply asks before it deletes one (not stateless).
|
|
110
|
+
wakeup: {
|
|
111
|
+
inputs: { host: "host" },
|
|
112
|
+
register: "cloudflare.alarm.register",
|
|
113
|
+
outputs: (state) => ({ healthy: state.healthy === true }),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const types = defineConnector("cloudflare", { container, alarm }, vendorInfo(import.meta.url));
|
|
117
|
+
/** `alarm.register`, in the alarm type's own group, beside its resource operations. */
|
|
118
|
+
const registerSpec = {
|
|
119
|
+
register: {
|
|
120
|
+
effect: "write",
|
|
121
|
+
// The object applies a generation only when it is newer: a retry changes nothing.
|
|
122
|
+
idempotent: true,
|
|
123
|
+
description: "Set the host's alarm to wake it at `at` with `generation`, replacing the wake registered before unless that one is newer",
|
|
124
|
+
input: z.object({
|
|
125
|
+
at: z.iso.datetime({ offset: true }).describe("When to wake the host, ISO 8601"),
|
|
126
|
+
generation: z.number().int().min(1).describe("Counts up from 1: only a newer one replaces the wake"),
|
|
127
|
+
}),
|
|
128
|
+
output: z.object({}),
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
const register = defineConnector("cloudflare", { alarm: registerSpec });
|
|
132
|
+
/**
|
|
133
|
+
* The connector's operations: each type's (`read`, `import`, `plan`, `create`, `update`,
|
|
134
|
+
* `delete`) and `alarm.register` in the alarm's group (Supabase's pattern).
|
|
135
|
+
*/
|
|
136
|
+
export const cloudflareOps = {
|
|
137
|
+
...types,
|
|
138
|
+
alarm: { ...types.alarm, ...register.alarm },
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.js","sourceRoot":"","sources":["../src/connector.ts"],"names":[],"mappings":"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"}
|
package/dist/driver.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface CloudflareDriverOptions {
|
|
2
|
+
/** How long one request may take, in milliseconds. Default 30 000. */
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
/** How long a create or update waits for the Worker to answer at its new revision. Default 10 minutes: a cold start clones and installs. */
|
|
5
|
+
startTimeoutMs?: number;
|
|
6
|
+
/** How long between two tries of the Worker while it starts. Default 2 000. */
|
|
7
|
+
pollMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The real Cloudflare account at `CLOUDFLARE_ACCOUNT_ID`, with `CLOUDFLARE_API_TOKEN`: the
|
|
11
|
+
* company's Worker and its container application (`cloudflare.container`), and its object's alarm
|
|
12
|
+
* over the Worker's wake route with the company's `SANOMA_WAKEUP_TOKEN` (`cloudflare.alarm`). See
|
|
13
|
+
* the README.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cloudflareDriver(options?: CloudflareDriverOptions): import("@sanoma-ai/workflows").Driver;
|