Skip to content
Open
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
78 changes: 75 additions & 3 deletions src/cloudflare/internal/images-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,83 @@ function isDrawTransformer(input: unknown): input is DrawTransformer {
return input instanceof DrawTransformer;
}

interface ServiceEntrypointStub {
details(imageId: string): Promise<ImageMetadata | null>;
image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;
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>;
}

class HostedImagesBindingImpl implements HostedImagesBinding {
readonly #fetcher: ServiceEntrypointStub;

constructor(fetcher: ServiceEntrypointStub) {
this.#fetcher = fetcher;
}

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

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

async upload(
image: ReadableStream<Uint8Array> | ArrayBuffer,
options?: ImageUploadOptions
): Promise<ImageMetadata> {
let processedImage: ReadableStream<Uint8Array> | ArrayBuffer = image;

if (options?.encoding === 'base64') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do this on the server side?
Best not to have any code on the client side - we want to remove this file eventually!

const stream =
image instanceof ReadableStream
? image
: new ReadableStream({
start(controller): void {
controller.enqueue(new Uint8Array(image));
controller.close();
},
});

processedImage = stream.pipeThrough(createBase64DecoderTransformStream());
}

return this.#fetcher.upload(processedImage, 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 || {});
}
}

class ImagesBindingImpl implements ImagesBinding {
readonly #fetcher: Fetcher;
readonly #fetcher: Fetcher & ServiceEntrypointStub;
readonly #hosted: HostedImagesBinding;

constructor(fetcher: Fetcher) {
constructor(fetcher: Fetcher & ServiceEntrypointStub) {
this.#fetcher = fetcher;
this.#hosted = new HostedImagesBindingImpl(fetcher);
}

get hosted(): HostedImagesBinding {
return this.#hosted;
}

async info(
Expand Down Expand Up @@ -356,5 +428,5 @@ async function throwErrorIfErrorResponse(
}

export default function makeBinding(env: { fetcher: Fetcher }): ImagesBinding {
return new ImagesBindingImpl(env.fetcher);
return new ImagesBindingImpl(env.fetcher as Fetcher & ServiceEntrypointStub);
}
99 changes: 99 additions & 0 deletions src/cloudflare/internal/images.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,100 @@ type ImageOutputOptions = {
anim?: boolean;
};

interface ImageMetadata {
id: string;
filename?: string;
uploaded?: string;
requireSignedURLs: boolean;
meta?: Record<string, unknown>;
variants: string[];
draft?: boolean;
creator?: string;
}

interface ImageUploadOptions {
id?: string;
filename?: string;
requireSignedURLs?: boolean;
metadata?: Record<string, unknown>;
creator?: string;
/**
* If 'base64', the input data will be decoded from base64 before processing
*/
encoding?: 'base64';
}

interface ImageUpdateOptions {
requireSignedURLs?: boolean;
metadata?: Record<string, unknown>;
creator?: string;
}

interface ImageListOptions {
limit?: number;
cursor?: string;
sortOrder?: 'asc' | 'desc';
creator?: string;
}

interface ImageList {
images: ImageMetadata[];
cursor?: string;
listComplete: boolean;
}

interface HostedImagesBinding {
/**
* 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>;

/**
* 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>;

/**
* 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>;

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

/**
* List hosted images with pagination
* @param options List configuration
* @returns List of images with pagination info
* @throws {@link ImagesError} if list fails
*/
list(options?: ImageListOptions): Promise<ImageList>;
}

interface ImagesBinding {
/**
* Get image metadata (type, width and height)
Expand All @@ -115,6 +209,11 @@ interface ImagesBinding {
stream: ReadableStream<Uint8Array>,
options?: ImageInputOptions
): ImageTransformer;

/**
* Access hosted images CRUD operations
*/
readonly hosted: HostedImagesBinding;
}

interface ImageTransformer {
Expand Down
1 change: 1 addition & 0 deletions src/cloudflare/internal/test/images/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ wd_test(
size = "enormous",
src = "images-api-test.wd-test",
args = ["--experimental"],
compat_date = "2024-04-03",
data = glob(["*.js"]) + ["//src/cloudflare/internal/test:instrumentation-test-helper.js"],
)
Loading
Loading