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
12 changes: 12 additions & 0 deletions .changeset/dependabot-update-13241.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"miniflare": patch
"wrangler": patch
---

Update dependencies of "miniflare", "wrangler"

The following dependency versions have been updated:

| Dependency | From | To |
| ---------- | ------------ | ------------ |
| workerd | 1.20260401.1 | 1.20260402.1 |
7 changes: 7 additions & 0 deletions .changeset/images-chainable-handle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": patch
---

Update Images binding local mock to use chainable handle pattern

`hosted.image(imageId)` now returns a handle with `details()`, `bytes()`, `update()`, and `delete()` methods, aligning with the updated workerd API (https://github.com/cloudflare/workerd/pull/6288).
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ it("produces queue message with mocked send", async ({ expect }) => {
metrics: {
backlogCount: 0,
backlogBytes: 0,
oldestMessageTimestamp: 0,
oldestMessageTimestamp: new Date(0),
},
},
}));
Expand Down
2 changes: 1 addition & 1 deletion packages/miniflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@cspotcode/source-map-support": "0.8.1",
"sharp": "^0.34.5",
"undici": "catalog:default",
"workerd": "1.20260401.1",
"workerd": "1.20260402.1",
"ws": "catalog:default",
"youch": "4.1.0-beta.10"
},
Expand Down
96 changes: 54 additions & 42 deletions packages/miniflare/src/workers/images/images.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Image data is stored as KV values, metadata as KV metadata
// Transforms and info operations are handled via HTTP loopback to Node.js Sharp

import { WorkerEntrypoint } from "cloudflare:workers";
import { RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
import { CoreBindings, CoreHeaders } from "../core/constants";

interface Env {
Expand All @@ -29,23 +29,70 @@ async function base64DecodeStream(
return base64DecodeArrayBuffer(buffer);
}

export default class ImagesService extends WorkerEntrypoint<Env> {
async details(imageId: string): Promise<ImageMetadata | null> {
const result = await this.env.IMAGES_STORE.getWithMetadata<ImageMetadata>(
imageId,
class ImageHandleImpl extends RpcTarget {
readonly #imageId: string;
readonly #store: KVNamespace;

constructor(imageId: string, store: KVNamespace) {
super();
this.#imageId = imageId;
this.#store = store;
}

async details(): Promise<ImageMetadata | null> {
const result = await this.#store.getWithMetadata<ImageMetadata>(
this.#imageId,
"arrayBuffer"
);
return result.metadata ?? null;
}

async image(imageId: string): Promise<ReadableStream<Uint8Array> | null> {
const data = await this.env.IMAGES_STORE.get(imageId, "arrayBuffer");
async bytes(): Promise<ReadableStream<Uint8Array> | null> {
const data = await this.#store.get(this.#imageId, "arrayBuffer");
if (data === null) {
return null;
}
return new Blob([data]).stream();
}

async update(options: ImageUpdateOptions): Promise<ImageMetadata> {
const existing = await this.#store.getWithMetadata<ImageMetadata>(
this.#imageId,
"arrayBuffer"
);
if (existing.value === null || existing.metadata === null) {
throw new Error(`Image not found: ${this.#imageId}`);
}

const updatedMetadata: ImageMetadata = {
...existing.metadata,
requireSignedURLs:
options.requireSignedURLs ?? existing.metadata.requireSignedURLs,
meta: options.metadata ?? existing.metadata.meta,
creator: options.creator ?? existing.metadata.creator,
};

await this.#store.put(this.#imageId, existing.value, {
metadata: updatedMetadata,
});
return updatedMetadata;
}

async delete(): Promise<boolean> {
const existing = await this.#store.get(this.#imageId, "arrayBuffer");
if (existing === null) {
return false;
}
await this.#store.delete(this.#imageId);
return true;
}
}

export default class ImagesService extends WorkerEntrypoint<Env> {
image(imageId: string): ImageHandleImpl {
return new ImageHandleImpl(imageId, this.env.IMAGES_STORE);
}

async upload(
image: ReadableStream<Uint8Array> | ArrayBuffer,
options?: ImageUploadOptions
Expand Down Expand Up @@ -80,41 +127,6 @@ export default class ImagesService extends WorkerEntrypoint<Env> {
return metadata;
}

async update(
imageId: string,
options: ImageUpdateOptions
): Promise<ImageMetadata> {
const existing = await this.env.IMAGES_STORE.getWithMetadata<ImageMetadata>(
imageId,
"arrayBuffer"
);
if (existing.value === null || existing.metadata === null) {
throw new Error(`Image not found: ${imageId}`);
}

const updatedMetadata: ImageMetadata = {
...existing.metadata,
requireSignedURLs:
options.requireSignedURLs ?? existing.metadata.requireSignedURLs,
meta: options.metadata ?? existing.metadata.meta,
creator: options.creator ?? existing.metadata.creator,
};

await this.env.IMAGES_STORE.put(imageId, existing.value, {
metadata: updatedMetadata,
});
return updatedMetadata;
}

async delete(imageId: string): Promise<boolean> {
const existing = await this.env.IMAGES_STORE.get(imageId, "arrayBuffer");
if (existing === null) {
return false;
}
await this.env.IMAGES_STORE.delete(imageId);
return true;
}

async list(options?: ImageListOptions): Promise<ImageList> {
const limit = options?.limit ?? 50;

Expand Down
16 changes: 8 additions & 8 deletions packages/miniflare/test/plugins/images/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe.skip("Images hosted CRUD", () => {

await images.hosted.upload(imageBuffer(), { id: "blob-test" });

const stream = await images.hosted.image("blob-test");
const stream = await images.hosted.image("blob-test").bytes();
assert(stream !== null);
const data = new Uint8Array(await new Response(stream).arrayBuffer());
expect(data).toEqual(TEST_IMAGE_BYTES);
Expand All @@ -65,7 +65,7 @@ describe.skip("Images hosted CRUD", () => {
);
expect(metadata.id).toBe("base64-test");

const stream = await images.hosted.image("base64-test");
const stream = await images.hosted.image("base64-test").bytes();
assert(stream !== null);
const data = new Uint8Array(await new Response(stream).arrayBuffer());
expect(data).toEqual(TEST_IMAGE_BYTES);
Expand All @@ -78,7 +78,7 @@ describe.skip("Images hosted CRUD", () => {
useDispose(mf);
const images = await mf.getImagesBinding("IMAGES");

const metadata = await images.hosted.details("does-not-exist");
const metadata = await images.hosted.image("does-not-exist").details();
expect(metadata).toBe(null);
});

Expand All @@ -89,7 +89,7 @@ describe.skip("Images hosted CRUD", () => {
useDispose(mf);
const images = await mf.getImagesBinding("IMAGES");

const stream = await images.hosted.image("does-not-exist");
const stream = await images.hosted.image("does-not-exist").bytes();
expect(stream).toBe(null);
});

Expand All @@ -100,7 +100,7 @@ describe.skip("Images hosted CRUD", () => {

await images.hosted.upload(imageBuffer(), { id: "update-test" });

const metadata = await images.hosted.update("update-test", {
const metadata = await images.hosted.image("update-test").update({
requireSignedURLs: true,
});
expect(metadata.requireSignedURLs).toBe(true);
Expand All @@ -113,10 +113,10 @@ describe.skip("Images hosted CRUD", () => {

await images.hosted.upload(imageBuffer(), { id: "delete-test" });

const deleted = await images.hosted.delete("delete-test");
const deleted = await images.hosted.image("delete-test").delete();
expect(deleted).toBe(true);

const metadata = await images.hosted.details("delete-test");
const metadata = await images.hosted.image("delete-test").details();
expect(metadata).toBe(null);
});

Expand All @@ -125,7 +125,7 @@ describe.skip("Images hosted CRUD", () => {
useDispose(mf);
const images = await mf.getImagesBinding("IMAGES");

const deleted = await images.hosted.delete("does-not-exist");
const deleted = await images.hosted.image("does-not-exist").delete();
expect(deleted).toBe(false);
});

Expand Down
6 changes: 5 additions & 1 deletion packages/vitest-pool-workers/src/worker/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,11 @@ class QueueController<Body = unknown> /* MessageBatch */ {
(message) => new QueueMessage(kConstructFlag, this, message)
);
const metadata: MessageBatchMetadata = {
metrics: { backlogCount: 0, backlogBytes: 0, oldestMessageTimestamp: 0 },
metrics: {
backlogCount: 0,
backlogBytes: 0,
oldestMessageTimestamp: new Date(0),
},
};

// Match `JSG_READONLY_INSTANCE_PROPERTY` behaviour
Expand Down
2 changes: 1 addition & 1 deletion packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"miniflare": "workspace:*",
"path-to-regexp": "6.3.0",
"unenv": "2.0.0-rc.24",
"workerd": "1.20260401.1"
"workerd": "1.20260402.1"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.721.0",
Expand Down
Loading
Loading