Skip to content

Commit ad26e7c

Browse files
committed
IMAGES-2139: update images binding mock to chainable handle pattern
- images.worker.ts: replace flat methods with ImageHandleImpl (RpcTarget) and synchronous image(imageId) factory on the service entrypoint - index.spec.ts: update tests to use new handle API - ts-expect-error annotations pending workerd PR #6288 types landing
1 parent b539dc7 commit ad26e7c

3 files changed

Lines changed: 77 additions & 50 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"miniflare": patch
3+
---
4+
5+
Update Images binding local mock to use chainable handle pattern
6+
7+
`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).

packages/miniflare/src/workers/images/images.worker.ts

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Image data is stored as KV values, metadata as KV metadata
33
// Transforms and info operations are handled via HTTP loopback to Node.js Sharp
44

5-
import { WorkerEntrypoint } from "cloudflare:workers";
5+
import { RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
66
import { CoreBindings, CoreHeaders } from "../core/constants";
77

88
interface Env {
@@ -29,23 +29,70 @@ async function base64DecodeStream(
2929
return base64DecodeArrayBuffer(buffer);
3030
}
3131

32-
export default class ImagesService extends WorkerEntrypoint<Env> {
33-
async details(imageId: string): Promise<ImageMetadata | null> {
34-
const result = await this.env.IMAGES_STORE.getWithMetadata<ImageMetadata>(
35-
imageId,
32+
class ImageHandleImpl extends RpcTarget {
33+
readonly #imageId: string;
34+
readonly #store: KVNamespace;
35+
36+
constructor(imageId: string, store: KVNamespace) {
37+
super();
38+
this.#imageId = imageId;
39+
this.#store = store;
40+
}
41+
42+
async details(): Promise<ImageMetadata | null> {
43+
const result = await this.#store.getWithMetadata<ImageMetadata>(
44+
this.#imageId,
3645
"arrayBuffer"
3746
);
3847
return result.metadata ?? null;
3948
}
4049

41-
async image(imageId: string): Promise<ReadableStream<Uint8Array> | null> {
42-
const data = await this.env.IMAGES_STORE.get(imageId, "arrayBuffer");
50+
async bytes(): Promise<ReadableStream<Uint8Array> | null> {
51+
const data = await this.#store.get(this.#imageId, "arrayBuffer");
4352
if (data === null) {
4453
return null;
4554
}
4655
return new Blob([data]).stream();
4756
}
4857

58+
async update(options: ImageUpdateOptions): Promise<ImageMetadata> {
59+
const existing = await this.#store.getWithMetadata<ImageMetadata>(
60+
this.#imageId,
61+
"arrayBuffer"
62+
);
63+
if (existing.value === null || existing.metadata === null) {
64+
throw new Error(`Image not found: ${this.#imageId}`);
65+
}
66+
67+
const updatedMetadata: ImageMetadata = {
68+
...existing.metadata,
69+
requireSignedURLs:
70+
options.requireSignedURLs ?? existing.metadata.requireSignedURLs,
71+
meta: options.metadata ?? existing.metadata.meta,
72+
creator: options.creator ?? existing.metadata.creator,
73+
};
74+
75+
await this.#store.put(this.#imageId, existing.value, {
76+
metadata: updatedMetadata,
77+
});
78+
return updatedMetadata;
79+
}
80+
81+
async delete(): Promise<boolean> {
82+
const existing = await this.#store.get(this.#imageId, "arrayBuffer");
83+
if (existing === null) {
84+
return false;
85+
}
86+
await this.#store.delete(this.#imageId);
87+
return true;
88+
}
89+
}
90+
91+
export default class ImagesService extends WorkerEntrypoint<Env> {
92+
image(imageId: string): ImageHandleImpl {
93+
return new ImageHandleImpl(imageId, this.env.IMAGES_STORE);
94+
}
95+
4996
async upload(
5097
image: ReadableStream<Uint8Array> | ArrayBuffer,
5198
options?: ImageUploadOptions
@@ -80,41 +127,6 @@ export default class ImagesService extends WorkerEntrypoint<Env> {
80127
return metadata;
81128
}
82129

83-
async update(
84-
imageId: string,
85-
options: ImageUpdateOptions
86-
): Promise<ImageMetadata> {
87-
const existing = await this.env.IMAGES_STORE.getWithMetadata<ImageMetadata>(
88-
imageId,
89-
"arrayBuffer"
90-
);
91-
if (existing.value === null || existing.metadata === null) {
92-
throw new Error(`Image not found: ${imageId}`);
93-
}
94-
95-
const updatedMetadata: ImageMetadata = {
96-
...existing.metadata,
97-
requireSignedURLs:
98-
options.requireSignedURLs ?? existing.metadata.requireSignedURLs,
99-
meta: options.metadata ?? existing.metadata.meta,
100-
creator: options.creator ?? existing.metadata.creator,
101-
};
102-
103-
await this.env.IMAGES_STORE.put(imageId, existing.value, {
104-
metadata: updatedMetadata,
105-
});
106-
return updatedMetadata;
107-
}
108-
109-
async delete(imageId: string): Promise<boolean> {
110-
const existing = await this.env.IMAGES_STORE.get(imageId, "arrayBuffer");
111-
if (existing === null) {
112-
return false;
113-
}
114-
await this.env.IMAGES_STORE.delete(imageId);
115-
return true;
116-
}
117-
118130
async list(options?: ImageListOptions): Promise<ImageList> {
119131
const limit = options?.limit ?? 50;
120132

packages/miniflare/test/plugins/images/index.spec.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ describe("Images hosted CRUD", () => {
4141

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

44-
const stream = await images.hosted.image("blob-test");
44+
// @ts-expect-error updated types pending workerd PR #6288
45+
const stream = await images.hosted.image("blob-test").bytes();
4546
assert(stream !== null);
4647
const data = new Uint8Array(await new Response(stream).arrayBuffer());
4748
expect(data).toEqual(TEST_IMAGE_BYTES);
@@ -64,7 +65,8 @@ describe("Images hosted CRUD", () => {
6465
);
6566
expect(metadata.id).toBe("base64-test");
6667

67-
const stream = await images.hosted.image("base64-test");
68+
// @ts-expect-error updated types pending workerd PR #6288
69+
const stream = await images.hosted.image("base64-test").bytes();
6870
assert(stream !== null);
6971
const data = new Uint8Array(await new Response(stream).arrayBuffer());
7072
expect(data).toEqual(TEST_IMAGE_BYTES);
@@ -77,7 +79,8 @@ describe("Images hosted CRUD", () => {
7779
useDispose(mf);
7880
const images = await mf.getImagesBinding("IMAGES");
7981

80-
const metadata = await images.hosted.details("does-not-exist");
82+
// @ts-expect-error updated types pending workerd PR #6288
83+
const metadata = await images.hosted.image("does-not-exist").details();
8184
expect(metadata).toBe(null);
8285
});
8386

@@ -88,7 +91,8 @@ describe("Images hosted CRUD", () => {
8891
useDispose(mf);
8992
const images = await mf.getImagesBinding("IMAGES");
9093

91-
const stream = await images.hosted.image("does-not-exist");
94+
// @ts-expect-error updated types pending workerd PR #6288
95+
const stream = await images.hosted.image("does-not-exist").bytes();
9296
expect(stream).toBe(null);
9397
});
9498

@@ -99,7 +103,8 @@ describe("Images hosted CRUD", () => {
99103

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

102-
const metadata = await images.hosted.update("update-test", {
106+
// @ts-expect-error updated types pending workerd PR #6288
107+
const metadata = await images.hosted.image("update-test").update({
103108
requireSignedURLs: true,
104109
});
105110
expect(metadata.requireSignedURLs).toBe(true);
@@ -112,10 +117,12 @@ describe("Images hosted CRUD", () => {
112117

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

115-
const deleted = await images.hosted.delete("delete-test");
120+
// @ts-expect-error updated types pending workerd PR #6288
121+
const deleted = await images.hosted.image("delete-test").delete();
116122
expect(deleted).toBe(true);
117123

118-
const metadata = await images.hosted.details("delete-test");
124+
// @ts-expect-error updated types pending workerd PR #6288
125+
const metadata = await images.hosted.image("delete-test").details();
119126
expect(metadata).toBe(null);
120127
});
121128

@@ -124,7 +131,8 @@ describe("Images hosted CRUD", () => {
124131
useDispose(mf);
125132
const images = await mf.getImagesBinding("IMAGES");
126133

127-
const deleted = await images.hosted.delete("does-not-exist");
134+
// @ts-expect-error updated types pending workerd PR #6288
135+
const deleted = await images.hosted.image("does-not-exist").delete();
128136
expect(deleted).toBe(false);
129137
});
130138

0 commit comments

Comments
 (0)