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
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.9.0
* Added `tinify.Format` constants for the media types `convert` accepts. `SupportedImageTypes` is now derived from them.

## 1.8.3
* add JXL to supported image formats
* add node 26 as test target
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

# Tinify API client for Node.js

A lightweight Node.js client for the Tinify API, used for [tinypng API](https://tinypng.com) and [TinyJPG](https://tinyjpg.com). This library lets you intelligently **compress**, **resize**, **convert**, and **store** images (AVIF, WebP, JPEG, PNG) with minimal effort. Read more at [http://tinify.com](http://tinify.com).
A lightweight Node.js client for the Tinify API, used for [tinypng API](https://tinypng.com) and [TinyJPG](https://tinyjpg.com). This library lets you intelligently **compress**, **resize**, **convert**, and **store** images (AVIF, JXL, WebP, JPEG, PNG) with minimal effort. Read more at [http://tinify.com](http://tinify.com).

## 🚀 Features

- Compress and optimize images in AVIF, WebP, JPEG, and PNG formats
- Compress and optimize images in AVIF, JXL, WebP, JPEG, and PNG formats
- Resize with intelligent cropping
- Convert between formats
- Preserve metadata (copyright, GPS, creation time)
Expand Down Expand Up @@ -65,6 +65,17 @@ extension.then(ext => {
converted.toFile("panda-sticker." + ext);
})

// Convert format using the Format constants
// (tinify.Format.WEBP, PNG, JPEG, JPG, AVIF, JXL and ANY)
tinify.fromFile("panda-sticker.jpg")
.convert({type: tinify.Format.JXL})
.toFile("panda-sticker.jxl");

// Pass several and the API returns the smallest result
tinify.fromFile("panda-sticker.jpg")
.convert({type: [tinify.Format.JXL, tinify.Format.WEBP]})
.toFile("panda-sticker.min");

// Preserve metadata
tinify.fromFile("original.jpg")
.preserve("copyright", "location", "creation")
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tinify",
"version": "1.8.3",
"version": "1.9.0",
"description": "Node.js client for the Tinify API. Tinify compresses your images intelligently. Read more at https://tinify.com.",
"keywords": [
"tinify",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import tinify from "./tinify"

import Client from "./tinify/Client"
import Format from "./tinify/Format"
import Result from "./tinify/Result"
import ResultMeta from "./tinify/ResultMeta"
import Source from "./tinify/Source"
Expand All @@ -14,6 +15,7 @@ import {
} from "./tinify/Error"

tinify.Client = Client
tinify.Format = Format
tinify.ResultMeta = ResultMeta
tinify.Result = Result
tinify.Source = Source
Expand Down
2 changes: 2 additions & 0 deletions src/tinify.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {nodeify, Callback} from "./tinify/compat"

import Client from "./tinify/Client"
import Format from "./tinify/Format"
import Result from "./tinify/Result"
import ResultMeta from "./tinify/ResultMeta"
import Source from "./tinify/Source"
Expand All @@ -19,6 +20,7 @@ class Tinify {
compressionCount?: number

Client!: typeof Client
Format!: typeof Format
Source!: typeof Source
Result!: typeof Result
ResultMeta!: typeof ResultMeta
Expand Down
47 changes: 47 additions & 0 deletions src/tinify/Format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Media types that images can be converted to.
*
* ```js
* const tinify = require("tinify")
*
* tinify.fromFile("input.png")
* .convert({type: tinify.Format.JXL})
* .toFile("output.jxl")
* ```
*
* Multiple types may be supplied, in which case the API returns the smallest
* result:
*
* ```js
* .convert({type: [tinify.Format.JXL, tinify.Format.WEBP]})
* ```
*/
const Format = {
/** WebP. */
WEBP: "image/webp",

/** PNG. */
PNG: "image/png",

/** JPEG. */
JPEG: "image/jpeg",

/**
* JPEG, under the alternative `image/jpg` media type the API also accepts.
* The API normalises this to `image/jpeg`, so the result is identical to
* `JPEG`. The two constants hold different strings but select the same
* format.
*/
JPG: "image/jpg",

/** AVIF. */
AVIF: "image/avif",

/** JPEG XL. */
JXL: "image/jxl",

/** Wildcard, returns the smallest of the supported types. */
ANY: "*/*",
} as const

export default Format
13 changes: 6 additions & 7 deletions src/tinify/Source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import tinify from "../tinify"
import {readFile, Callback} from "./compat"

import Client from "./Client"
import Format from "./Format"
import Result from "./Result"
import ResultMeta from "./ResultMeta"

export type SupportedImageTypes = "image/webp"
| "image/png"
| "image/jpg"
| "image/jpeg"
| "image/jxl"
| "image/avif";
type FormatValues = (typeof Format)[keyof typeof Format];

export type SupportedImageTypes = Exclude<FormatValues, typeof Format.ANY>;
Comment on lines +9 to +11

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changing this one — the premise looks wrong. This file already uses semicolons on exactly these declarations, on master, before this PR:

export type SupportedImageTypes = "image/webp"
  | "image/png"
  | "image/jpg";                                    // line 13

export type WildcardOrSupportedImageTypes = SupportedImageTypes
  | "*/*";                                          // line 16

export type ConvertOptions = {
  type: WildcardOrSupportedImageTypes | SupportedImageTypes[];   // line 19
}

The convention here is semicolon-free statements (imports, expressions) but semicolons terminating type alias and member declarations. The new aliases follow that, so removing them would be the inconsistent change. Happy to go either way if the maintainers prefer a different rule, but this matches what is already there.


// The wildcard "*/*" returns the smallest of Tinify's supported image types.
export type WildcardOrSupportedImageTypes = SupportedImageTypes
| "*/*"; // The wildcard "*/*" returns the smallest of Tinify's supported image types, currently JPEG, PNG and WebP.
| typeof Format.ANY;

export type ConvertOptions = {
type: WildcardOrSupportedImageTypes | SupportedImageTypes[];
Expand Down
16 changes: 16 additions & 0 deletions test/tinify-typing-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,24 @@ async function run() {
await tinify.fromFile("/foo/bar").convert({ type: "image/webp" })
await tinify.fromFile("/foo/bar").convert({ type: "image/png" })
await tinify.fromFile("/foo/bar").convert({ type: "image/jpg" })
await tinify.fromFile("/foo/bar").convert({ type: "image/jxl" })
await tinify.fromFile("/foo/bar").convert({ type: "image/avif" })
await tinify.fromFile("/foo/bar").convert({ type: "*/*" })

// The Format constants are accepted everywhere a literal is.
await tinify.fromFile("/foo/bar").convert({ type: tinify.Format.JXL })
await tinify.fromFile("/foo/bar").convert({ type: tinify.Format.ANY })
await tinify.fromFile("/foo/bar").convert({
type: [tinify.Format.JXL, tinify.Format.WEBP],
})

Comment on lines 20 to +28

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 080cf91 — the two assertions are now in this file:

// @ts-expect-error an unknown media type is not assignable
await tinify.fromFile("/foo/bar").convert({ type: "image/bogus" })
// @ts-expect-error the "*/*" wildcard is not valid inside the array form
await tinify.fromFile("/foo/bar").convert({ type: ["*/*"] })

They are self-enforcing: an @ts-expect-error that does not fire is itself a compile error, so if the derived union ever widens, npm test fails on the directive rather than silently passing.

// SupportedImageTypes is derived from Format; these assert the derived
// union is no wider than the literal list it replaced.
// @ts-expect-error an unknown media type is not assignable
await tinify.fromFile("/foo/bar").convert({ type: "image/bogus" })
// @ts-expect-error the "*/*" wildcard is not valid inside the array form
await tinify.fromFile("/foo/bar").convert({ type: ["*/*"] })

await tinify.fromBuffer("foo")
.resize({method: "fit", width: 150, height: 100})
.preserve("copyright", "creation")
Expand Down
Loading