Skip to content

chore: sync release v1.0.0 to develop - #32

Merged
joeykamsteeg merged 2 commits into
developfrom
release/1.0.0
Aug 8, 2026
Merged

chore: sync release v1.0.0 to develop#32
joeykamsteeg merged 2 commits into
developfrom
release/1.0.0

Conversation

@joeykamsteeg

@joeykamsteeg joeykamsteeg commented Aug 8, 2026

Copy link
Copy Markdown
Member

1.0.0

Major Changes

  • 6f44c73: data parsing now follows the response's Content-Type header instead of trying JSON.parse on the body and falling back to text. When Content-Type is application/json (parameters like charset are ignored, matching is case-insensitive), the body is JSON-parsed, or data is null if the body isn't valid JSON. For any other (or missing) Content-Type, data is the raw response text with no JSON.parse attempt. Callers relying on JSON-shaped plaintext served with a non-JSON Content-Type being auto-parsed now receive the raw string instead.

  • 83a80c7: Add defineHeaders, a defineOptions-style factory that builds a HeadersInit while omitting any entry whose value is undefined (useful for conditionally-set headers like an optional auth token). FetchifyOptions gains an optional headers field of default headers sent with every request made by a createFetchify client.

    FetchifyMethod's second parameter changes from init?: RequestInit to options?: FetchifyMethodOptions, with headers, params (query parameters), and body promoted to first-class fields, merged/overridden against the client's defaults (headers/params support undefined to remove a client default or existing query parameter for a single request), plus init still available for other RequestInit fields (signal, credentials, etc.). Existing calls passing a raw RequestInit as the second argument need to move headers/body to the new top-level fields (or leave body as-is, since it maps directly) and everything else under options.init.

  • 87b2c2c: createFetchify client methods (get/post/put/patch/delete/head/options) now resolve to a FetchifyResponse<T> object ({ data: T | null; response: Response }) instead of the raw fetch Response. data holds the parsed response body — JSON-parsed when possible, falling back to raw text, and null when the response wasn't ok — and each method accepts an optional generic (e.g. client.get<User>("/user")) to type data. response is a clone of the original fetch Response, left unread for callers who need .json()/.blob()/status/headers directly. Call sites using the old const response = await client.get(...) pattern need to switch to const { data, response } = await client.get(...).

  • 9b75388: createFetchify clients now expose HTTP methods as lowercase properties (get, post, put, patch, delete, head, options) instead of uppercase (GET, POST, ...). The underlying fetch request still uses the uppercase HTTP verb; only the client property names changed casing.

  • f8f6152: Wire the configured cache adapter into createFetchify's request path. Previously FetchifyOptions.cache was accepted but never used; GET requests are now looked up in the cache before hitting the network, and successful (ok) GET responses are written back to the cache, keyed by method and resolved URL. Non-GET requests and non-ok responses never read from or write to the cache. Cache entry TTL is not configured on FetchifyOptions — it's entirely up to the adapter (see createCacheAdapter below).

    createCacheAdapter now accepts an optional second argument, options: { cacheTtlMs?: number }, letting a custom adapter declare its own default TTL used whenever set is called without an explicit ttlMs. An explicit ttlMs on a given set call always overrides the adapter's default. Omitting options (or cacheTtlMs) keeps the existing behavior of returning the input adapter unchanged.

    FetchifyResponse<T> gains a required cached: boolean field, true when the response was served from the cache adapter without a network request, false otherwise. Code constructing FetchifyResponse-shaped objects directly (e.g. in tests) needs to add this field.

  • 49ff85e: A non-ok (response.ok === false) HTTP response now rejects with a FetchifyError instead of resolving with data: null. FetchifyError carries status, statusText, an unread response clone, data (the error body, parsed with the same Content-Type-driven rule as a success response), and request ({ method, url }). Network failures continue to propagate unchanged, so a single try/catch now covers both. Requests that throw FetchifyError are still logged via a configured logger, with FetchifyLogEntry.status reflecting the response's real status code instead of null.

  • 272ee2e: Unify createMemoryCacheAdapter and createRedisCacheAdapter onto a single options-object parameter, and let both declare a default TTL via ttlMs.

    createMemoryCacheAdapter now takes an optional MemoryCacheAdapterOptions parameter ({ ttlMs?: number }, defaulting to {}); existing createMemoryCacheAdapter() calls are unaffected.

    createRedisCacheAdapter now takes a single required RedisCacheAdapterOptions parameter ({ client: RedisLike; ttlMs?: number }) instead of a bare client argument. Update createRedisCacheAdapter(client) calls to createRedisCacheAdapter({ client }).

    For both factories, ttlMs — when provided — becomes the adapter's default TTL for set calls that omit their own ttlMs, via createCacheAdapter's existing cacheTtlMs option. Omitting ttlMs keeps today's behavior: entries never expire unless a set call specifies its own ttlMs.

  • 8c6a9b6: Rename the package from fetchify to @kamsteegsoftware/fetchify, required to publish to GitHub Packages. Consumers must update their install and import specifiers accordingly (import ... from "@kamsteegsoftware/fetchify").

    Add a publish-next GitHub Actions workflow that publishes a snapshot pre-release build to GitHub Packages under the next dist-tag on every push to develop.

  • 18cf01e: Add a pluggable cache-adapter system: a CacheAdapter interface, a createCacheAdapter factory for authoring custom adapters (Redis, Upstash, etc.), and a built-in dependency-free in-memory adapter at fetchify/cache/memory. createFetchify's FetchifyOptions gains an optional cache field of type CacheAdapter.

    Remove the fetchify/native entry point. It has been an exact duplicate of the main fetchify entry point since the initial scaffold, with no platform-specific behavior. Consumers importing from fetchify/native should import from fetchify instead — the API is identical.

Minor Changes

  • e5f1ceb: createFetchify now accepts an optional timeout (milliseconds), applied as the default timeout for every request made by the client, and each FetchifyMethod call accepts a per-request timeout override (including 0 to disable an inherited client-level timeout). When a resolved timeout elapses before the underlying fetch call settles, the request is aborted and rejects with a new FetchifyTimeoutError instead of remaining pending indefinitely. The timeout only bounds the network fetch call itself (not beforeRequest hook execution or cache lookups), composes with a caller-supplied options.init.signal so either can abort the request independently, and has no effect on cache-served responses. With no timeout configured, requests behave exactly as they did before this option existed.

  • a4bd0d8: createFetchify now accepts an optional retry (FetchifyRetryOptions), applied as the default retry configuration for every request made by the client, and each FetchifyMethod call accepts a per-request retry override — either a replacement FetchifyRetryOptions object or false to disable retry for that call. A failed request is retried with capped exponential backoff and full jitter (or the response's Retry-After header, for a 429/503), re-running beforeRequest and getting a fresh timeout window per attempt. With no retryOn supplied, only idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE) are retried, and only for network failures, FetchifyTimeoutErrors, and FetchifyErrors with status 408, 429, 500, 502, 503, or 504POST/PATCH are never retried by default, and a caller-triggered AbortSignal is never retried under any configuration. Retry never applies to a cache-served GET. With no retry configured, requests behave exactly as they did before this option existed.

  • e965720: FetchifyMethodOptions.body now accepts any JSON-serializable value (plain object, array, string, number, boolean, null) in addition to standard BodyInit types. When the request's resolved Content-Type header is application/json, a non-BodyInit body is automatically serialized with JSON.stringify before being sent; otherwise body is forwarded to fetch unchanged, exactly as before. Standard BodyInit values (string, Blob, ArrayBuffer/typed arrays, FormData, URLSearchParams, ReadableStream) are never re-serialized, so an already-JSON.stringify'd string is never double-encoded, and options.init.body is never serialized.

  • 182fccf: createFetchify now accepts an optional hooks.beforeRequest async hook, invoked with the fully-resolved request (method, url, headers, body, init) immediately before each outgoing fetch call (skipped on cache hits). The hook may resolve to a partial override (url, headers, body, init) applied on top of the resolved request using the same merge rules as per-call options, or to undefined to leave the request unchanged. A new defineBeforeRequestHook helper mirrors defineOptions/defineHeaders as a typed authoring entry point for the hook function.

  • f86a631: Add an optional logger option to createFetchify (and FetchifyOptions/defineOptions), accepting a structural LoggerAdapter ({ log(entry: FetchifyLogEntry): void | Promise<void> }). When configured, logger.log(entry) is called once per request after it settles, with the HTTP method, resolved URL, response status (null if the request threw), duration in milliseconds, and whether the response was served from the cache. The call is fire-and-forget: it's never awaited, and any error the logger throws or rejects with is caught and discarded, so a misbehaving logger can't affect the resolved value or thrown error of the request it's logging. With no logger configured, no logging occurs.

    Add a built-in console-backed LoggerAdapter at the @kamsteegsoftware/fetchify/logger/console subpath. createConsoleLogger() prints one line per request — console.log for successful entries (with a trailing [cached] for cache hits), console.error for entries whose request threw — with no configuration options, for consumers who want request visibility without writing their own adapter.

  • 86f6061: Every FetchifyMethod call now accepts an optional invalidate?: boolean | (string | URL)[] option for explicit, opt-in cache invalidation: when a request's response resolves as a live ok response on a client configured with cache, invalidate: true deletes the GET cache entry for that request's own URL, and an array resolves each entry to a URL (a string the same way a request path resolves, joined against baseUrl; a URL used as-is) and deletes its GET cache entry. Invalidation is entirely explicit — omitting invalidate (or passing false/[]) has zero cache side effects beyond a request's existing GET read/write behavior, and the option is honored on any HTTP method, not just mutating verbs. Each entry is deleted independently, so a CacheAdapter.delete failure for one doesn't block the rest or affect the resolved response.

  • 817b9ca: Add defineOptions factory for authoring a typed FetchifyOptions object independently of createFetchify, e.g. createFetchify(defineOptions({ baseUrl: "https://api.example.com" })). The CreateFetchifyOptions type has been renamed to FetchifyOptions.

  • 11a0aee: Add a Redis-backed CacheAdapter at the @kamsteegsoftware/fetchify/cache/redis subpath. createRedisCacheAdapter(client) takes an already-connected client and maps CacheAdapter's get/set/delete/has onto Redis' GET/SET .. PX/DEL/EXISTS commands, with ttlMs enforced as Redis' native millisecond expiry.

    The client argument is typed against a minimal structural interface (get, set, del, exists) rather than a specific Redis package, so both a Bun RedisClient (Bun.redis / new Bun.RedisClient(...)) and an ioredis client can be passed in without fetchify depending on either.

  • c0a1103: Add an Upstash REST-backed CacheAdapter at the @kamsteegsoftware/fetchify/cache/upstash subpath. createUpstashCacheAdapter({ client, ttlMs }) takes an already-constructed Upstash REST client and maps CacheAdapter's get/set/delete/has onto its get/set/del/exists methods, with ttlMs enforced via Upstash's px expiry option.

    The client argument is typed against a minimal structural interface (get, set, del, exists) matching @upstash/redis's Redis client shape, rather than depending on the @upstash/redis package directly, so a real @upstash/redis instance can be passed in without fetchify adding it as a dependency.

  • 4210b57: Add createFetchify factory that returns an HTTP client with GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS methods, resolving relative request paths against an optional baseUrl.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

test: Run #49

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Duration ⏱️
288 288 0 0 618ms

🎉 All tests passed!

Github Test Reporter by CTRF 💚

🔄 This comment has been updated

@joeykamsteeg
joeykamsteeg merged commit 293ea5b into develop Aug 8, 2026
4 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.

1 participant