RFC: wrangler as an API #1993
Replies: 11 comments 3 replies
-
I agree that integration tests are a great User 0 case for this. But I'm curious, there are community dev tools out there that use the CF API endpoints directly (https://denoflare.dev/) or libraries that do the same (https://github.com/fab-spec/fab/tree/main/packages/deployer-cf-workers), and I'm guessing a bunch of internal tools at workplaces that do this. I'm wondering what other ones are people aware of? Would be good to know if they have any specific needs a Wrangler API still wouldn't support. |
Beta Was this translation helpful? Give feedback.
-
I bet @jplhomer would know |
Beta Was this translation helpful? Give feedback.
-
Super cool idea ❤️ RE: other tooling, I don't know if there are specific tools. I had considered adding tooling to Flareact that made it super easy to create things like Durable Objects + add them to Over at Shopify, we're embarking on a couple related projects:
|
Beta Was this translation helpful? Give feedback.
-
For the Pages local dev server, we need the following from a dev server API:
The following are nice-to-haves:
This is all we use today in our local-only dev server. We haven't yet figured out how/if we'll make remote work. |
Beta Was this translation helpful? Give feedback.
-
Some thoughts about a design for "wrangler as an API"After some internal discussion and exploration, we've tentatively settled on a design for worker testing that looks like treating workers as an // index.ts
export default {
scheduled: (event, env, ctx) => {
ctx.waitUntil(async () => {
await env.MY_KV_STORE.put("last-trigger": new Date())
})
},
fetch: async (request) => {
const name = await request.headers.get("NAME") ?? "world";
return new Response(`Hello, ${name}!`);
}
}
// index.spec.ts
import { makeWorker } from "wrangler/test";
import type { Worker } from "wrangler/test";
import defaultExports from "./index.ts";
const worker: Worker = makeWorker("./index.ts");
it("says hello world", async () => {
const response = await worker.fetch("some-url.com");
await expect(response.text()).resolves.toEqual("Hello, world!");
// this is functionally equivalent to `worker.fetch`
const response2 = await worker.dispatchEvent("fetch",
new Request("some-url.com", { headers: { NAME: "Xanathar" } })
);
await expect(response.text()).resolves.toEqual("Hello, Xanathar!");
});
it("writes to KV when triggered", async () => {
await worker.cron("* * * * *"); // or `worker.dispatchEvent("cron", "* * * * *")`;
expect(worker.MY_KV_STORE).hasProperty<number>("last-trigger");
}) This implies that "wrangler as a library" should also have this sort of design, e.g. to allow export const makeWorker = (file: string, options?: MakeWorkerOptions): DevSession => {
return wrangler.dev({ file, ...options })
} Additionally, it makes sense to have commands be import { dev } from 'wrangler';
const devSession = dev({
source: "path/to/index.ts",
sourceMaps: true,
// ...
});
devSession.on("reload", (reloadEvent) => handleReloadEvent(reloadEvent)); Combined with the import { dev } from 'wrangler';
import process from 'node:process';
const devSession = dev({
source: "path/to/index.ts",
sourceMaps: true,
// ...
});
devSession.on("reload", (reloadEvent) => handleReloadEvent(reloadEvent));
process.on("SIGKILL", () => { devSession.dispatchEvent("kill"); }); Internally, this would be useful for decoupling our own code such that every command looks essentially like:
This separation of user-facing output from core wrangler APIs will be really good dogfooding of our own API, and will allow us to spot issues with the design before we stabilize. Lastly, taking some inspiration from This also provides an idiomatic way to "wait for completion" of longer-running commands after setting up various lifecycle hooks: import { dev } from 'wrangler';
import process from 'node:process';
const devSession = dev({
source: "path/to/index.ts",
sourceMaps: true,
// ...
});
devSession.on("reload", (reloadEvent) => handleReloadEvent(reloadEvent));
process.on("SIGKILL", () => { devSession.dispatchEvent("kill"); });
await devSession; I've done a little experimenting and it seems like this should work fairly well. I'll start with the I'm not convinced we need to implement the entire spec for, say, Thoughts? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Following a discussion with @IgorMinar, we've decided to focus solely on implementing an We also considered the Lastly, for an MVP we'll hold off on implementing I'll have a draft PR up shortly that more explicitly demonstrates what I have in mind. |
Beta Was this translation helpful? Give feedback.
-
I have a use case where I would love to programmatically publish a worker where the files are never actually written to disk. I create them in memory and would love to be able to pass that to some sort of "wrangler api" to publish it for me. |
Beta Was this translation helpful? Give feedback.
-
This might be something you would be interested to track #1538 |
Beta Was this translation helpful? Give feedback.
-
Has this API idea been abandoned? I'm considering making a NX plugin to build and deploy workers and can obviously wrap wrangler CLI to accomplish that but it would be cleaner having direct access to an API instead to avoid the extra 'hop'. I could also use the CF HTTP API directly but eh... |
Beta Was this translation helpful? Give feedback.
-
Update from internal: We are currently extensively reworking and planning the Programmatic API, if you have additional questions feel free to ping one of us here or on Discord 😄 |
Beta Was this translation helpful? Give feedback.
-
Premise: wrangler should be usable in "code", enabling folks to build their own tooling and abstractions on top of it, and potentially preventing feature creep in wrangler itself. Some usecases that have come up -
The first usecase that's come up is writing integration tests (tracking at RFC: Writing tests with wrangler 2.x #1183). At it's core, the usecase here is to program wrangler itself, and build one's own scaffoling on top of it.
Something similar, is the Pages+Wrangler convergence. We would like Pages to have every feature that wrangler has, and it shouldn't require a rewrite to do. A major portion of Pages functionality should able to be built on top of wrangler (and newer features that are still generic should be built into wrangler). So if wrangler had a first class api, the pages cli/commands/configuration would simply be an abstraction on top of it. This not only makes Pages better, but it proves that anyone wanting to build a platform on top of Workers should be able to just reuse the tooling that wrangler provides.
A simpler/smaller usecase, is to enable alternate configuration formats. The JS community doesn't commonly use toml, and would prefer to use json/js/typescript/whatever. We probably shouldn't be adding newer configuration formats, but having a first class api means we can get, at minimum, static type checking for free. (related: 🚀 Feature Request: improve the wrangler.toml config experience #1165)
Internal to Cloudflare, folks write Workers to control Cloudflare itself, but the configuration is a bit different. Specifically, account ids aren't necessary (a lot of these workers are run for every worker on the control plane, for example), some bindings are different, and the way they're published is dramatically different. That said, a lot of things are similar; they'd like to use typescript/wasm, third party dependencies from npm, as well as the bindings we already enable, and so on. Having an api will let them use functionality that's already built in wrangler, and run it next to their own tooling/infrastructure.
I think a first step here is to enable integrations tests as described in #1183, that should flesh out some of the bsaic things we need to move forward on the other usecases.
Beta Was this translation helpful? Give feedback.
All reactions