Skip to content

feat: add Format constants for convert media types, including JXL - #55

Merged
Sreini merged 1 commit into
masterfrom
feat/jxl-format
Nov 4, 2026
Merged

Sreini merged 1 commit into
masterfrom
feat/jxl-format

Conversation

@Sreini

@Sreini Sreini commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Adds a tinify.Format constant object so the media types accepted by convert
are discoverable at runtime, not only in the type system — matching the
Format API being added to the other five Tinify client libraries.

JXL itself already landed here in 1.8.3 (ea25ea9); this PR is about making the
full set discoverable and consistent across languages.

Members

WEBP, PNG, JPEG, JPG, AVIF, JXL, ANY (*/*) — mirroring the API
allowlist exactly.

SupportedImageTypes is now derived from Format

Rather than duplicating the literal list, the union is computed from the
constant object, so the two cannot drift apart:

type FormatValues = (typeof Format)[keyof typeof Format];
export type SupportedImageTypes = Exclude<FormatValues, typeof Format.ANY>;

The derived union is equivalent to the old one, verified with a negative
typecheck: it still rejects unknown media types, and still excludes the */*
wildcard from the array form. Both @ts-expect-error assertions fired:

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

Verification

Full suite green locally: npm test → 85 passing, and the typing test compiles
with the new tinify.Format.* cases added to it.

Note on the remote

This branch was pushed to upstream (tinify/tinify-nodejs), since the local
checkout's origin points at a personal fork while master tracks upstream.

Part of a coordinated change across all six Tinify client libraries, so the same
Format API is available in each.

@rkoopmans

Copy link
Copy Markdown
Contributor

👍 imo we can create a release (bump version and changelog)

@rkoopmans

Copy link
Copy Markdown
Contributor

Also update the README.md with an example of using the new Format 🏅

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The PR description’s claimed negative type assertions are not present in the typing test, and there’s a small correctness issue in the new Format documentation (JPG described as an alias despite differing value).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a runtime-discoverable tinify.Format constants object so consumers can reference supported convert media types at runtime (not only via TypeScript literal unions), aligning this client with the other Tinify libraries.

Changes:

  • Introduces src/tinify/Format.ts and wires it onto the public tinify export as tinify.Format.
  • Refactors SupportedImageTypes / WildcardOrSupportedImageTypes to be derived from Format (avoiding drift).
  • Updates typing tests and docs/changelog to include the new constants and formats (e.g., JXL, AVIF).
File summaries
File Description
test/tinify-typing-test.ts Extends TS typing coverage for convert to include new formats and tinify.Format constants.
src/tinify/Source.ts Derives SupportedImageTypes and wildcard type from Format rather than duplicating literals.
src/tinify/Format.ts Adds the runtime Format constants object for supported convert media types.
src/tinify.ts Adds Format onto the Tinify instance type so it’s available in generated typings.
src/index.ts Attaches Format to the exported tinify object at runtime (tinify.Format = Format).
README.md Updates format lists to include JXL in the documented capabilities.
CHANGES.md Documents the new tinify.Format API under Unreleased.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 20 to +28
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],
})

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.

Comment thread src/tinify/Format.ts Outdated
Comment on lines +29 to +30
/** JPEG, an alias of `JPEG`. */
JPG: "image/jpg",

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. Reworded to "JPEG, under the alternative media type the API also accepts. This is a distinct value from JPEG, not an alias for it."

Both are separate members of the API's mediaType enum, so they are two accepted spellings rather than equal constants. Applied the same wording to the other five client libraries for consistency.

Comment thread src/tinify/Source.ts
Comment on lines +9 to +11
type FormatValues = (typeof Format)[keyof typeof Format];

export type SupportedImageTypes = Exclude<FormatValues, typeof Format.ANY>;

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.

@Sreini

Sreini commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both of your comments in 080cf91:

Release — bumped to 1.9.0 and promoted the changelog entry out of Unreleased. Minor rather than patch, since tinify.Format is new public API.

README — added a worked example next to the existing convert snippet:

// 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");

Two things worth your call before merge:

  1. The other five libraries are still on Unreleased. You asked for a release here; say the word and I will bump and promote changelogs in java, .NET, php, python and ruby too.
  2. tinify-python Cannot assign to 'key' because it is a read-only property.  #28 had a real defect, surfaced by your _typed.py comment — the Format constants were inferred as str and failed to typecheck against ConvertTypes, so they could not actually be passed to convert(). Fixed there in 175af41; details in that thread.

Adds a Format constant object so the media types accepted by convert()
are discoverable at runtime as well as in the type system, matching the
other Tinify client libraries and including JPEG XL (image/jxl), which
the API already accepts.

SupportedImageTypes is now derived from Format rather than duplicating
the literal list, so the two cannot drift apart. The derived union is
equivalent to the old one: it still rejects unknown media types and
still excludes the "*/*" wildcard from array form. That equivalence is
enforced by @ts-expect-error assertions rather than asserted in prose --
an unfired @ts-expect-error is itself a compile error, so they cannot
rot.

JPG documents image/jpg as the alternative spelling the API also
accepts. It normalises to image/jpeg before conversion, so the two
constants hold different strings but select the same format.

Adds a README example and bumps to 1.9.0, promoting the changelog entry
out of Unreleased.
@Sreini
Sreini merged commit 6de5a80 into master Nov 4, 2026
75 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants