diff --git a/README.md b/README.md index eae8b48..a2d3044 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,23 @@ if (result.success) { } ``` +### Delete Images + +Delete one or more existing images from HCTI and clear them from the CDN using their image IDs. + +```typescript +const result = await client.deleteImage('your_image_id'); + +if (!result.success) { + console.error('Error:', result.error); +} + +const batchResult = await client.deleteImageBatch([ + 'first_image_id', + 'second_image_id' +]); +``` + ## Creating Image URLs For use cases where you want to generate a URL on the server and use it safely on the client (without exposing your API key), you can generate a signed URL. diff --git a/package-lock.json b/package-lock.json index 6733ffd..1ccb618 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@html-css-to-image/client", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@html-css-to-image/client", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "devDependencies": { "@types/node": "^25.0.3", diff --git a/package.json b/package.json index b1f34bc..c0cb40b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@html-css-to-image/client", - "version": "0.4.0", + "version": "0.5.0", "type": "module", "description": "Lightweight TypeScript client for the HTML/CSS to Image API. Generate images from HTML/CSS, URLs, or templates in your TypeScript or JavaScript projects.", "main": "dist/index.js", diff --git a/src/HtmlCssToImageClient.ts b/src/HtmlCssToImageClient.ts index 3491a2c..3ffb127 100644 --- a/src/HtmlCssToImageClient.ts +++ b/src/HtmlCssToImageClient.ts @@ -1,7 +1,7 @@ import type {IHtmlCssToImageClient} from "./IHtmlCssToImageClient.js"; import {CreateTemplatedImageRequest} from "./types/request.js"; import type {BaseCreateImageRequest, CreateHtmlCssImageRequest, CreateUrlImageRequest, PDFOptions, PdfValueInput} from "./types/request.js"; -import type {CreateImageBatchResponse, CreateImageBatchSuccessResponse, CreateImageResponse} from "./types/response.js"; +import type {CreateImageBatchResponse, CreateImageBatchSuccessResponse, CreateImageErrorResponse, CreateImageResponse, DeleteImageResponse} from "./types/response.js"; import * as crypto from 'node:crypto'; export type FetchFunction = (input: string | URL | Request, init?: RequestInit) => Promise; @@ -96,6 +96,30 @@ export class HtmlCssToImageClient implements IHtmlCssToImageClient { } + async deleteImage(imageId: string): Promise { + const response = await this.fetch(`${this.baseUrl}/v1/image/${encodeURIComponent(imageId)}`, { + method: 'DELETE', + headers: { + 'Authorization': this.authHeader, + }, + }); + + return await this.responseToDeleteImageResponse(response); + } + + async deleteImageBatch(imageIds: readonly string[]): Promise { + const response = await this.fetch(`${this.baseUrl}/v1/image/batch`, { + method: 'DELETE', + headers: { + 'Authorization': this.authHeader, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ids: imageIds}), + }); + + return await this.responseToDeleteImageResponse(response); + } + private mapToInternal(request: CreateHtmlCssImageRequest | CreateUrlImageRequest | CreateTemplatedImageRequest, in_batch: boolean): any { switch (request.__type) { case 'html_css': @@ -210,6 +234,14 @@ export class HtmlCssToImageClient implements IHtmlCssToImageClient { } } + private async responseToDeleteImageResponse(response: Response): Promise { + if (response.ok) { + return {success: true}; + } + + return await this.responseToType(response); + } + private mapHtmlCssToInternalRequest(request: CreateHtmlCssImageRequest, in_batch: boolean): InternalCreateHtmlCssImageRequest | InternalCreateHtmlCssImageRequestWithOptionalHtml { const {pdf_options, google_fonts, __type, ...shared_request} = request; let new_request: InternalCreateHtmlCssImageRequest|InternalCreateHtmlCssImageRequestWithOptionalHtml; diff --git a/src/IHtmlCssToImageClient.ts b/src/IHtmlCssToImageClient.ts index 3e1f71a..f583a78 100644 --- a/src/IHtmlCssToImageClient.ts +++ b/src/IHtmlCssToImageClient.ts @@ -1,9 +1,11 @@ import type {CreateHtmlCssImageRequest, CreateTemplatedImageRequest, CreateUrlImageRequest} from "./types/request.js"; -import type {CreateImageBatchResponse, CreateImageResponse} from "./types/response.js"; +import type {CreateImageBatchResponse, CreateImageResponse, DeleteImageResponse} from "./types/response.js"; export interface IHtmlCssToImageClient { createImage(request: CreateHtmlCssImageRequest|CreateUrlImageRequest|CreateTemplatedImageRequest) : Promise; createImageBatch(variations: T[],default_options?:T) : Promise; + deleteImage(imageId: string): Promise; + deleteImageBatch(imageIds: readonly string[]): Promise; generateCreateAndRenderUrl(request: CreateUrlImageRequest): string; generateTemplatedImageUrl>(template_id: string, template_values: T, template_version?: number):string; generateTemplatedImageUrl(request: CreateTemplatedImageRequest):string; diff --git a/src/index.ts b/src/index.ts index 7095d78..871dd42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,8 @@ export type { CreateImageErrorResponse, CreateImageResponse, CreateImageSuccessResponse, + DeleteImageResponse, + DeleteImageSuccessResponse, ValidationError } from './types/response.js'; export { HtmlCssToImageClient } from './HtmlCssToImageClient.js'; diff --git a/src/types/response.ts b/src/types/response.ts index 27441d2..553c9e6 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -21,6 +21,12 @@ export interface CreateImageBatchSuccessResponse{ images: CreateImageSuccessResponse[]; } +export interface DeleteImageSuccessResponse { + success: true; +} + export type CreateImageBatchResponse = CreateImageBatchSuccessResponse | CreateImageErrorResponse; -export type CreateImageResponse = CreateImageSuccessResponse | CreateImageErrorResponse; \ No newline at end of file +export type CreateImageResponse = CreateImageSuccessResponse | CreateImageErrorResponse; + +export type DeleteImageResponse = DeleteImageSuccessResponse | CreateImageErrorResponse; diff --git a/tests/client.test.ts b/tests/client.test.ts index 7ad40b4..7c73056 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -94,6 +94,67 @@ describe('HtmlCssToImageClient', () => { assert.strictEqual(result.success, true); }); + test('deleteImage sends an authenticated DELETE request', async () => { + const mockFetch = async (url: string, options: any) => { + assert.strictEqual(url, 'https://hcti.io/v1/image/image%2Fid'); + assert.strictEqual(options.method, 'DELETE'); + assert.strictEqual(options.headers.Authorization, `Basic ${Buffer.from(`${apiId}:${apiKey}`).toString('base64')}`); + assert.strictEqual(options.body, undefined); + + return { + ok: true, + status: 202, + } as Response; + }; + + const client = new HtmlCssToImageClient(apiId, apiKey, mockFetch as any); + const result = await client.deleteImage('image/id'); + + assert.deepStrictEqual(result, {success: true}); + }); + + test('deleteImage returns API error details', async () => { + const mockFetch = async () => ({ + ok: false, + status: 404, + json: async () => ({ + error: 'Not Found', + message: 'Image not found', + }), + } as Response); + + const client = new HtmlCssToImageClient(apiId, apiKey, mockFetch as any); + const result = await client.deleteImage('missing-image'); + + assert.strictEqual(result.success, false); + if (!result.success) { + assert.strictEqual(result.error, 'Not Found'); + assert.strictEqual(result.message, 'Image not found'); + } + }); + + test('deleteImageBatch sends image IDs in an authenticated DELETE request', async () => { + const mockFetch = async (url: string, options: any) => { + assert.strictEqual(url, 'https://hcti.io/v1/image/batch'); + assert.strictEqual(options.method, 'DELETE'); + assert.strictEqual(options.headers.Authorization, `Basic ${Buffer.from(`${apiId}:${apiKey}`).toString('base64')}`); + assert.strictEqual(options.headers['Content-Type'], 'application/json'); + assert.deepStrictEqual(JSON.parse(options.body), { + ids: ['image-1', 'image-2'], + }); + + return { + ok: true, + status: 202, + } as Response; + }; + + const client = new HtmlCssToImageClient(apiId, apiKey, mockFetch as any); + const result = await client.deleteImageBatch(['image-1', 'image-2']); + + assert.deepStrictEqual(result, {success: true}); + }); + test('generateCreateAndRenderUrl includes CSS and an explicit transparent background value', () => { const client = new HtmlCssToImageClient(apiId, apiKey); const url = client.generateCreateAndRenderUrl(new CreateUrlImageRequest({