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
22 changes: 2 additions & 20 deletions src/cloudflare/internal/images-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,11 @@ function isDrawTransformer(input: unknown): input is DrawTransformer {
}

interface ServiceEntrypointStub {
details(imageId: string): Promise<ImageMetadata | null>;
image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;
image(imageId: string): ImageHandle;
upload(
image: ReadableStream<Uint8Array> | ArrayBuffer,
options?: ImageUploadOptions
): Promise<ImageMetadata>;
update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;
delete(imageId: string): Promise<boolean>;
list(options?: ImageListOptions): Promise<ImageList>;
}

Expand All @@ -263,11 +260,7 @@ class HostedImagesBindingImpl implements HostedImagesBinding {
this.#fetcher = fetcher;
}

async details(imageId: string): Promise<ImageMetadata | null> {
return this.#fetcher.details(imageId);
}

async image(imageId: string): Promise<ReadableStream<Uint8Array> | null> {
image(imageId: string): ImageHandle {
return this.#fetcher.image(imageId);
}

Expand All @@ -278,17 +271,6 @@ class HostedImagesBindingImpl implements HostedImagesBinding {
return this.#fetcher.upload(image, options);
}

async update(
imageId: string,
options: ImageUpdateOptions
): Promise<ImageMetadata> {
return this.#fetcher.update(imageId, options);
}

async delete(imageId: string): Promise<boolean> {
return this.#fetcher.delete(imageId);
}

async list(options?: ImageListOptions): Promise<ImageList> {
return this.#fetcher.list(options);
}
Expand Down
47 changes: 26 additions & 21 deletions src/cloudflare/internal/images.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,48 +138,53 @@ interface ImageList {
listComplete: boolean;
}

interface HostedImagesBinding {
interface ImageHandle {
/**
* Get metadata for a hosted image
* @param imageId The ID of the image (UUID or custom ID)
* @returns Image metadata, or null if not found
*/
details(imageId: string): Promise<ImageMetadata | null>;
details(): Promise<ImageMetadata | null>;

/**
* Get the raw image data for a hosted image
* @param imageId The ID of the image (UUID or custom ID)
* @returns ReadableStream of image bytes, or null if not found
*/
image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;

/**
* Upload a new hosted image
* @param image The image file to upload
* @param options Upload configuration
* @returns Metadata for the uploaded image
* @throws {@link ImagesError} if upload fails
*/
upload(
image: ReadableStream<Uint8Array> | ArrayBuffer,
options?: ImageUploadOptions
): Promise<ImageMetadata>;
bytes(): Promise<ReadableStream<Uint8Array> | null>;

/**
* Update hosted image metadata
* @param imageId The ID of the image
* @param options Properties to update
* @returns Updated image metadata
* @throws {@link ImagesError} if update fails
*/
update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;
update(options: ImageUpdateOptions): Promise<ImageMetadata>;

/**
* Delete a hosted image
* @param imageId The ID of the image
* @returns True if deleted, false if not found
*/
delete(imageId: string): Promise<boolean>;
delete(): Promise<boolean>;
}

interface HostedImagesBinding {
/**
* Get a handle for a hosted image
* @param imageId The ID of the image (UUID or custom ID)
* @returns A handle for per-image operations
*/
image(imageId: string): ImageHandle;

/**
* Upload a new hosted image
* @param image The image file to upload
* @param options Upload configuration
* @returns Metadata for the uploaded image
* @throws {@link ImagesError} if upload fails
*/
upload(
image: ReadableStream<Uint8Array> | ArrayBuffer,
options?: ImageUploadOptions
): Promise<ImageMetadata>;

/**
* List hosted images with pagination
Expand Down
18 changes: 10 additions & 8 deletions src/cloudflare/internal/test/images/images-api-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ export const test_images_get_success = {
* @param {Env} env
*/
async test(_, env) {
const metadata = await env.images.hosted.details('test-image-id');
const metadata = await env.images.hosted.image('test-image-id').details();
assert.notEqual(metadata, null);
assert.equal(metadata.id, 'test-image-id');
assert.equal(metadata.filename, 'test.jpg');
Expand All @@ -473,7 +473,7 @@ export const test_images_get_not_found = {
* @param {Env} env
*/
async test(_, env) {
const metadata = await env.images.hosted.details('not-found');
const metadata = await env.images.hosted.image('not-found').details();
assert.equal(metadata, null);
},
};
Expand All @@ -485,7 +485,7 @@ export const test_images_getImage_success = {
* @param {Env} env
*/
async test(_, env) {
const stream = await env.images.hosted.image('test-image-id');
const stream = await env.images.hosted.image('test-image-id').bytes();
assert.notEqual(stream, null);

const reader = stream.getReader();
Expand All @@ -506,7 +506,7 @@ export const test_images_getImage_not_found = {
* @param {Env} env
*/
async test(_, env) {
const stream = await env.images.hosted.image('not-found');
const stream = await env.images.hosted.image('not-found').bytes();
assert.equal(stream, null);
},
};
Expand Down Expand Up @@ -556,7 +556,7 @@ export const test_images_update_success = {
* @param {Env} env
*/
async test(_, env) {
const metadata = await env.images.hosted.update('test-image-id', {
const metadata = await env.images.hosted.image('test-image-id').update({
requireSignedURLs: true,
metadata: { updated: true },
creator: 'update-creator',
Expand All @@ -580,7 +580,9 @@ export const test_images_update_not_found = {
*/
let e;
try {
await env.images.hosted.update('not-found', { requireSignedURLs: true });
await env.images.hosted
.image('not-found')
.update({ requireSignedURLs: true });
} catch (err) {
e = err;
}
Expand All @@ -596,7 +598,7 @@ export const test_images_delete_success = {
* @param {Env} env
*/
async test(_, env) {
const result = await env.images.hosted.delete('test-image-id');
const result = await env.images.hosted.image('test-image-id').delete();
assert.equal(result, true);
},
};
Expand All @@ -607,7 +609,7 @@ export const test_images_delete_not_found = {
* @param {Env} env
*/
async test(_, env) {
const result = await env.images.hosted.delete('not-found');
const result = await env.images.hosted.image('not-found').delete();
assert.equal(result, false);
},
};
Expand Down
99 changes: 56 additions & 43 deletions src/cloudflare/internal/test/images/images-upstream-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

import { WorkerEntrypoint } from 'cloudflare:workers';
import { WorkerEntrypoint, RpcTarget } from 'cloudflare:workers';

/**
* @param {FormDataEntryValue | null} blob
Expand All @@ -20,18 +20,23 @@ async function imageAsString(blob) {
return blob.text();
}

export class ServiceEntrypoint extends WorkerEntrypoint {
/**
* @param {string} imageId
* @returns {Promise<ImageMetadata | null>}
*/
async details(imageId) {
if (imageId === 'not-found') {
class ImageHandleMock extends RpcTarget {
/** @type {string} */
#imageId;

/** @param {string} imageId */
constructor(imageId) {
super();
this.#imageId = imageId;
}

async details() {
if (this.#imageId === 'not-found') {
return null;
}

return {
id: imageId,
id: this.#imageId,
filename: 'test.jpg',
uploaded: '2024-01-01T00:00:00Z',
requireSignedURLs: false,
Expand All @@ -42,15 +47,54 @@ export class ServiceEntrypoint extends WorkerEntrypoint {
};
}

async image(imageId) {
if (imageId === 'not-found') {
async bytes() {
if (this.#imageId === 'not-found') {
return null;
}

const mockData = `MOCK_IMAGE_DATA_${imageId}`;
const mockData = `MOCK_IMAGE_DATA_${this.#imageId}`;
return new Blob([mockData]).stream();
}

/**
* @param {ImageUpdateOptions} body
* @returns {Promise<ImageMetadata>}
*/
async update(body) {
if (this.#imageId === 'not-found') {
throw new Error('Image not found');
}

return {
id: this.#imageId,
filename: 'updated.jpg',
uploaded: '2024-01-01T00:00:00Z',
requireSignedURLs:
body.requireSignedURLs !== undefined ? body.requireSignedURLs : false,
variants: ['public'],
meta: body.metadata || {},
draft: false,
creator: body.creator,
};
}

/**
* @returns {Promise<boolean>}
*/
async delete() {
return this.#imageId !== 'not-found';
}
}

export class ServiceEntrypoint extends WorkerEntrypoint {
/**
* @param {string} imageId
* @returns {ImageHandleMock}
*/
image(imageId) {
return new ImageHandleMock(imageId);
}

async upload(image, options) {
// Handle both ReadableStream and ArrayBuffer
const buffer =
Expand All @@ -77,37 +121,6 @@ export class ServiceEntrypoint extends WorkerEntrypoint {
};
}

/**
* @param {string} imageId
* @param {ImageUpdateOptions} body
* @returns {Promise<ImageMetadata>}
*/
async update(imageId, body) {
if (imageId === 'not-found') {
throw new Error('Image not found');
}

return {
id: imageId,
filename: 'updated.jpg',
uploaded: '2024-01-01T00:00:00Z',
requireSignedURLs:
body.requireSignedURLs !== undefined ? body.requireSignedURLs : false,
variants: ['public'],
meta: body.metadata || {},
draft: false,
creator: body.creator,
};
}

/**
* @param {string} imageId
* @returns {Promise<boolean>}
*/
async delete(imageId) {
return imageId !== 'not-found';
}

/**
* @param {ImageListOptions} [options]
* @returns {Promise<ImageList>}
Expand Down
Loading
Loading