Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .changeset/connect-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
"wrangler": minor
"miniflare": minor
"@cloudflare/workers-utils": minor
"@cloudflare/config": minor
---

Add `connect` trigger for raw sockets

You can now configure a Worker to receive raw socket connections during `wrangler dev`, delivered directly to the Worker's `connect(socket, env, ctx)` handler:

```jsonc
{
"connect": [{ "protocol": "tcp", "port": 5432 }],
}
```

Each entry opens a listening socket on `127.0.0.1` (or the given `address`) that forwards incoming connections straight to the Worker, bypassing the local dev HTTP entry point. This requires the `experimental` compatibility flag. Only `"tcp"` is supported at the moment.

`@cloudflare/config` also supports declaring this trigger via `triggers.connect(...)`, which lowers to the `connect` field above:

```ts
import { defineWorker, triggers } from "@cloudflare/config";

export default defineWorker({
triggers: [
triggers.connect({ protocol: "tcp", port: 5432, address: "127.0.0.1" }),
],
});
```
8 changes: 8 additions & 0 deletions .changeset/default-analytics-engine-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@cloudflare/config": patch
"miniflare": patch
---

Default local Analytics Engine dataset names in Miniflare

Analytics Engine dataset bindings without an explicit `name` now fallback to the worker and binding name as a default.
7 changes: 7 additions & 0 deletions .changeset/inherit-r2-creds-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/config": minor
---

Add R2 local S3 credentials to the shared config binding shape

R2 bindings now support `localDev.experimentalS3Credentials`, matching Wrangler's existing local S3 endpoint credentials configuration.
9 changes: 9 additions & 0 deletions .changeset/kv-bulk-put-local-base64-binary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"wrangler": patch
---

Fixes `kv bulk put` corrupting binary values written to local KV

Values marked `base64: true` were stored incorrectly whenever they contained bytes that do not form valid UTF-8, which covers images, compressed data and most other binary payloads. A Worker reading such a key back under `wrangler dev` got a different, longer value than the one that was written: a 12 byte PNG header came back as 20 bytes.

`kv bulk put` writes to local KV by default, so the plain command was the affected one. Remote writes were never affected, and neither were entries without `base64` or values written with `kv key put`.
7 changes: 7 additions & 0 deletions .changeset/remove-miniflare-vitest-assets-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": major
---

Remove the deprecated `hasAssetsAndIsVitest` option

This internal assets testing option is no longer supported.
8 changes: 8 additions & 0 deletions .changeset/remove-workflow-remote-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"wrangler": patch
"@cloudflare/config": patch
---

Remove unsupported `remote` configuration from Workflow bindings

Workflow bindings no longer accept `remote` in configuration, as remote Workflow bindings have never actually been supported.
7 changes: 7 additions & 0 deletions .changeset/reshape-miniflare-r2-s3-credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": major
---

Change R2 local S3 credentials configuration

R2 bindings now use `localDev.experimentalS3Credentials` instead of `s3Credentials` for local S3 endpoint credentials.
74 changes: 71 additions & 3 deletions packages/config/src/__tests__/convert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,15 +247,37 @@ describe("convertToWranglerConfig", () => {
]);
});

it("maps r2 with name and jurisdiction", ({ expect }) => {
it("maps r2 with name, jurisdiction, and local S3 credentials", ({
expect,
}) => {
const result = convertToWranglerConfig({
...baseConfig,
env: {
MY_R2: { type: "r2", name: "my-bucket", jurisdiction: "eu" },
MY_R2: {
type: "r2",
name: "my-bucket",
jurisdiction: "eu",
localDev: {
experimentalS3Credentials: {
accessKeyId: "access-key",
secretAccessKey: "secret-key",
},
},
},
},
});
expect(result.r2_buckets).toEqual([
{ binding: "MY_R2", bucket_name: "my-bucket", jurisdiction: "eu" },
{
binding: "MY_R2",
bucket_name: "my-bucket",
jurisdiction: "eu",
local_dev: {
experimental_s3_credentials: {
accessKeyId: "access-key",
secretAccessKey: "secret-key",
},
},
},
]);
});

Expand Down Expand Up @@ -999,6 +1021,52 @@ describe("convertToWranglerConfig", () => {
consumers: [{ queue: "c-queue" }],
});
});

it("maps connect trigger to connect", ({ expect }) => {
const result = convertToWranglerConfig({
...baseConfig,
triggers: [
{
type: "connect",
protocol: "tcp",
port: 5432,
address: "127.0.0.1",
},
],
});
expect(result.connect).toEqual([
{ protocol: "tcp", port: 5432, address: "127.0.0.1" },
]);
});

it("maps connect trigger without an address", ({ expect }) => {
const result = convertToWranglerConfig({
...baseConfig,
triggers: [{ type: "connect", protocol: "tcp", port: 5432 }],
});
expect(result.connect).toEqual([{ protocol: "tcp", port: 5432 }]);
});

it("collects multiple connect triggers into a single connect array", ({
expect,
}) => {
const result = convertToWranglerConfig({
...baseConfig,
triggers: [
{ type: "connect", protocol: "tcp", port: 5432 },
{
type: "connect",
protocol: "tcp",
port: 6379,
address: "0.0.0.0",
},
],
});
expect(result.connect).toEqual([
{ protocol: "tcp", port: 5432 },
{ protocol: "tcp", port: 6379, address: "0.0.0.0" },
]);
});
});

describe("domains", () => {
Expand Down
51 changes: 51 additions & 0 deletions packages/config/src/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,57 @@ describe("InputWorkerSchema", () => {
}
});

it("accepts a connect trigger", ({ expect }) => {
const result = InputWorkerSchema.safeParse({
...baseConfig,
triggers: [
{
type: "connect",
protocol: "tcp",
port: 5432,
address: "127.0.0.1",
},
],
});

expect(result.success).toBe(true);
});

it("rejects a connect trigger with an invalid protocol", ({ expect }) => {
const result = InputWorkerSchema.safeParse({
...baseConfig,
triggers: [{ type: "connect", protocol: "ftp", port: 5432 }],
});

expect(result.success).toBe(false);
});

it("rejects unknown keys inside a connect trigger", ({ expect }) => {
const result = InputWorkerSchema.safeParse({
...baseConfig,
triggers: [
{
type: "connect",
protocol: "tcp",
port: 5432,
hostname: "127.0.0.1",
},
],
});

expect(result.success).toBe(false);
if (!result.success) {
const issue = result.error.issues.find(
(i) => i.code === "unrecognized_keys"
);
expect(issue).toBeDefined();
expect(issue?.path).toEqual(["triggers", 0]);
expect((issue as { keys?: string[] } | undefined)?.keys).toContain(
"hostname"
);
}
});

it("still accepts unknown keys on `unsafe:*` bindings (looseObject escape hatch)", ({
expect,
}) => {
Expand Down
10 changes: 8 additions & 2 deletions packages/config/src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ interface R2BindingOptions {
jurisdiction?: string;
/** Whether the R2 bucket should be remote or not in local development. */
remote?: boolean;
/** Settings that only apply to local development. */
localDev?: {
/** EXPERIMENTAL: credentials for the local S3-compatible endpoint. */
experimentalS3Credentials?: {
accessKeyId: string;
secretAccessKey: string;
};
};
}

/**
Expand Down Expand Up @@ -610,8 +618,6 @@ interface WorkflowBindingOptions {
workerName: string;
/** The exported class name of the Workflow. */
exportName: string;
/** Whether the Workflow binding should be remote or not in local development. */
remote?: boolean;
}

/**
Expand Down
26 changes: 25 additions & 1 deletion packages/config/src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,12 +445,20 @@ function convertBindingsAndAssets(
break;
}
case "r2": {
const experimentalS3Credentials =
binding.localDev?.experimentalS3Credentials;
r2Buckets.push(
omitUndefined({
binding: name,
bucket_name: binding.name,
jurisdiction: binding.jurisdiction,
remote: binding.remote,
local_dev:
experimentalS3Credentials === undefined
? undefined
: {
experimental_s3_credentials: experimentalS3Credentials,
},
})
);
break;
Expand Down Expand Up @@ -770,7 +778,7 @@ function convertExports(
}

// ═══════════════════════════════════════════════════════════════════════════
// TRIGGERS (scheduled + fetch + queue consumer + email)
// TRIGGERS (scheduled + fetch + queue consumer + email + connect)
// ═══════════════════════════════════════════════════════════════════════════

function convertTriggers(
Expand All @@ -789,6 +797,9 @@ function convertTriggers(
const queueConsumers: NonNullable<
NonNullable<RawConfig["queues"]>["consumers"]
> = result.queues?.consumers ? [...result.queues.consumers] : [];
const connectHandlers: NonNullable<RawConfig["connect"]> = result.connect
? [...result.connect]
: [];
let addresses: string[] | undefined;

for (const trigger of triggers) {
Expand Down Expand Up @@ -827,6 +838,16 @@ function convertTriggers(
);
break;
}
case "connect": {
connectHandlers.push(
omitUndefined({
protocol: trigger.protocol,
port: trigger.port,
address: trigger.address,
})
);
break;
}
}
}

Expand All @@ -839,6 +860,9 @@ function convertTriggers(
if (queueConsumers.length) {
result.queues = { ...(result.queues ?? {}), consumers: queueConsumers };
}
if (connectHandlers.length) {
result.connect = connectHandlers;
}
// An empty array removes managed addresses; undefined means no email trigger.
if (addresses !== undefined) {
result.addresses = addresses;
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./public";
export {
AnalyticsEngineDatasetBindingSchema,
AssetsSchema,
BindingSchema,
BrowserBindingSchema,
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type {
export { bindings } from "./bindings";
export type {
Triggers,
ConnectTrigger,
EmailTrigger,
FetchTrigger,
QueueConsumerTrigger,
Expand Down
27 changes: 23 additions & 4 deletions packages/config/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ export const R2BindingSchema = z.strictObject({
name: z.string().optional(),
jurisdiction: z.string().optional(),
remote: z.boolean().optional(),
localDev: z
.strictObject({
experimentalS3Credentials: z
// AWS SDK may add additional keys as internal metadata like `$source`.
.object({
accessKeyId: z.string(),
secretAccessKey: z.string(),
})
.optional(),
})
.optional(),
});

export const AnalyticsEngineDatasetBindingSchema = z.strictObject({
type: z.literal("analytics-engine-dataset"),
name: z.string().optional(),
});

export const FlagshipBindingSchema = z.strictObject({
Expand Down Expand Up @@ -87,10 +103,7 @@ export const KnownBindingSchema = z.discriminatedUnion("type", [
namespace: z.string(),
remote: z.boolean().optional(),
}),
z.strictObject({
type: z.literal("analytics-engine-dataset"),
name: z.string().optional(),
}),
AnalyticsEngineDatasetBindingSchema,
z.strictObject({
type: z.literal("artifacts"),
namespace: z.string(),
Expand Down Expand Up @@ -464,6 +477,12 @@ const TriggerSchema = z.discriminatedUnion("type", [
type: z.literal("scheduled"),
schedule: z.string(),
}),
z.strictObject({
type: z.literal("connect"),
protocol: z.enum(["tcp"]),
port: z.number(),
address: z.string().optional(),
}),
]);

const UnsafeSchema = z.strictObject({
Expand Down
Loading
Loading