PlumeKit Documentation

Changelog

Release notes for the PlumeKit 3.1 series, newest first.

3.1.2

A disconnecting client can no longer wedge a worker

A worker isolate runs one guest call at a time. A request the runtime dropped mid-flight, as it does when a client disconnects, never released its place in the queue, so every later request in that isolate waited for ever at zero CPU. Mobile clients hit this most, since they cancel requests routinely.

A request that has queued for longer than any healthy call now takes over on a fresh instance, and logs that it did. The client's body is also read before the request queues, so a slow upload no longer blocks the isolate. Nothing to do beyond deploying.

Outbound HTTP takes a per-request timeout

FetchRequest gains a timeoutSeconds for upstreams that need longer than the 120 seconds every request used to be held to:

SWIFT
try await HTTP.current.request(FetchRequest(
    method: "POST", url: endpoint, body: body, timeoutSeconds: 300))

Unset means 120 as before (FetchRequest.defaultTimeoutSeconds). Both the native and Cloudflare adapters honour it.

3.1.1

A worker's first deploy no longer hangs

Deploying a worker that had never been deployed before could hang for ever. Several Cloudflare list endpoints ignore the page parameter and return the whole collection on every request, and the API client paged them until a page came back empty, which never happened. A worker that had been deployed already short-circuited the loop on its migration tag, so the hang only showed on a first deploy, including the first deploy of a new --env environment.

Paging now stops on a short page, or on a page identical to the one before it. There is nothing to do: a deploy interrupted by the hang can simply be run again, and it adopts the versionless worker Cloudflare was left holding.

Deploys say what went wrong, and where they are

A few paths used to fail with nothing printed at all: a D1 query whose response did not have the expected shape, a request body that could not be encoded, and the migrate and seed steps that call them. They now name the database, the step and what came back.

plumekit deploy also traces its progress as it runs (provisioning, assets, the migration tag, the script upload), so a slow or stalled deploy says where it has got to. That trace goes to stderr on purpose: stdout is block-buffered when the CLI is redirected to a file or a pipe, so progress written there would only appear once the process had already exited.

3.1.0

This release adds deploy environments and two testing hooks. Everything is additive: existing apps build and deploy unchanged, with nothing to fix.

Deploy environments

One project can now ship parallel deployments of the same app: production plus a test or staging copy, each with its own worker, database, secrets and domains. Declare one as a section under the target and drive it with --env:

TOML
[targets.cloudflare.env.test]
database_name = "bookmarks_test"
domains = ["test.bookmarks.com"]
Terminal
$plumekit deploy --env test$plumekit secret set AUTH_SECRET --env test$plumekit migrate --remote --env test

An environment inherits behaviour from the base section (account, compatibility date and flags, crons, queue settings; vars merge) but never identity: the worker name defaults to <base-name>-<env>, resource names and pinned ids resolve only within the environment's own section, and domains are not inherited, so a test deployment can never point at production's database or claim its domain by omission. Bundles land in dist/cloudflare-<env>, the first deploy provisions and pins the environment's own resources, a misspelt --env is an error rather than a fresh stack, and every Cloudflare environment build sets PLUMEKIT_DEPLOY_ENV so the app can ask which deployment it is running in. The full guide is Deploying → Deploy environments.

TestApp: secrets and outbound-HTTP stubs

TestApp.boot takes two new optional parameters. secrets: binds named secrets/vars the app under test should see, and http: stubs the outbound HTTP binding so handlers that call third parties can be tested hermetically:

SWIFT
let app = try await TestApp.boot(buildApp, migrations: runMigrations,
    secrets: ["PLUMEKIT_DEPLOY_ENV": "test"],
    http: { request in FetchResponse(status: 200, body: Array("ok".utf8)) })

Both default to the old behaviour; existing tests run unchanged.