chore: sync release v1.0.0 to develop - #32
Merged
Merged
Conversation
test: Run #49
🎉 All tests passed!Github Test Reporter by CTRF 💚 🔄 This comment has been updated |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
1.0.0
Major Changes
6f44c73:
dataparsing now follows the response'sContent-Typeheader instead of tryingJSON.parseon the body and falling back to text. WhenContent-Typeisapplication/json(parameters likecharsetare ignored, matching is case-insensitive), the body is JSON-parsed, ordataisnullif the body isn't valid JSON. For any other (or missing)Content-Type,datais the raw response text with noJSON.parseattempt. Callers relying on JSON-shaped plaintext served with a non-JSONContent-Typebeing auto-parsed now receive the raw string instead.83a80c7: Add
defineHeaders, adefineOptions-style factory that builds aHeadersInitwhile omitting any entry whose value isundefined(useful for conditionally-set headers like an optional auth token).FetchifyOptionsgains an optionalheadersfield of default headers sent with every request made by acreateFetchifyclient.FetchifyMethod's second parameter changes frominit?: RequestInittooptions?: FetchifyMethodOptions, withheaders,params(query parameters), andbodypromoted to first-class fields, merged/overridden against the client's defaults (headers/paramssupportundefinedto remove a client default or existing query parameter for a single request), plusinitstill available for otherRequestInitfields (signal,credentials, etc.). Existing calls passing a rawRequestInitas the second argument need to moveheaders/bodyto the new top-level fields (or leavebodyas-is, since it maps directly) and everything else underoptions.init.87b2c2c:
createFetchifyclient methods (get/post/put/patch/delete/head/options) now resolve to aFetchifyResponse<T>object ({ data: T | null; response: Response }) instead of the rawfetchResponse.dataholds the parsed response body — JSON-parsed when possible, falling back to raw text, andnullwhen the response wasn'tok— and each method accepts an optional generic (e.g.client.get<User>("/user")) to typedata.responseis a clone of the originalfetchResponse, left unread for callers who need.json()/.blob()/status/headersdirectly. Call sites using the oldconst response = await client.get(...)pattern need to switch toconst { data, response } = await client.get(...).9b75388:
createFetchifyclients now expose HTTP methods as lowercase properties (get,post,put,patch,delete,head,options) instead of uppercase (GET,POST, ...). The underlyingfetchrequest still uses the uppercase HTTP verb; only the client property names changed casing.f8f6152: Wire the configured
cacheadapter intocreateFetchify's request path. PreviouslyFetchifyOptions.cachewas accepted but never used;GETrequests are now looked up in the cache before hitting the network, and successful (ok)GETresponses are written back to the cache, keyed by method and resolved URL. Non-GETrequests and non-ok responses never read from or write to the cache. Cache entry TTL is not configured onFetchifyOptions— it's entirely up to the adapter (seecreateCacheAdapterbelow).createCacheAdapternow accepts an optional second argument,options: { cacheTtlMs?: number }, letting a custom adapter declare its own default TTL used wheneversetis called without an explicitttlMs. An explicitttlMson a givensetcall always overrides the adapter's default. Omittingoptions(orcacheTtlMs) keeps the existing behavior of returning the input adapter unchanged.FetchifyResponse<T>gains a requiredcached: booleanfield,truewhen the response was served from the cache adapter without a network request,falseotherwise. Code constructingFetchifyResponse-shaped objects directly (e.g. in tests) needs to add this field.49ff85e: A non-ok (
response.ok === false) HTTP response now rejects with aFetchifyErrorinstead of resolving withdata: null.FetchifyErrorcarriesstatus,statusText, an unreadresponseclone,data(the error body, parsed with the sameContent-Type-driven rule as a success response), andrequest({ method, url }). Network failures continue to propagate unchanged, so a singletry/catchnow covers both. Requests that throwFetchifyErrorare still logged via a configuredlogger, withFetchifyLogEntry.statusreflecting the response's real status code instead ofnull.272ee2e: Unify
createMemoryCacheAdapterandcreateRedisCacheAdapteronto a single options-object parameter, and let both declare a default TTL viattlMs.createMemoryCacheAdapternow takes an optionalMemoryCacheAdapterOptionsparameter ({ ttlMs?: number }, defaulting to{}); existingcreateMemoryCacheAdapter()calls are unaffected.createRedisCacheAdapternow takes a single requiredRedisCacheAdapterOptionsparameter ({ client: RedisLike; ttlMs?: number }) instead of a bareclientargument. UpdatecreateRedisCacheAdapter(client)calls tocreateRedisCacheAdapter({ client }).For both factories,
ttlMs— when provided — becomes the adapter's default TTL forsetcalls that omit their ownttlMs, viacreateCacheAdapter's existingcacheTtlMsoption. OmittingttlMskeeps today's behavior: entries never expire unless asetcall specifies its ownttlMs.8c6a9b6: Rename the package from
fetchifyto@kamsteegsoftware/fetchify, required to publish to GitHub Packages. Consumers must update their install and import specifiers accordingly (import ... from "@kamsteegsoftware/fetchify").Add a
publish-nextGitHub Actions workflow that publishes a snapshot pre-release build to GitHub Packages under thenextdist-tag on every push todevelop.18cf01e: Add a pluggable cache-adapter system: a
CacheAdapterinterface, acreateCacheAdapterfactory for authoring custom adapters (Redis, Upstash, etc.), and a built-in dependency-free in-memory adapter atfetchify/cache/memory.createFetchify'sFetchifyOptionsgains an optionalcachefield of typeCacheAdapter.Remove the
fetchify/nativeentry point. It has been an exact duplicate of the mainfetchifyentry point since the initial scaffold, with no platform-specific behavior. Consumers importing fromfetchify/nativeshould import fromfetchifyinstead — the API is identical.Minor Changes
e5f1ceb:
createFetchifynow accepts an optionaltimeout(milliseconds), applied as the default timeout for every request made by the client, and eachFetchifyMethodcall accepts a per-requesttimeoutoverride (including0to disable an inherited client-level timeout). When a resolved timeout elapses before the underlyingfetchcall settles, the request is aborted and rejects with a newFetchifyTimeoutErrorinstead of remaining pending indefinitely. The timeout only bounds the networkfetchcall itself (notbeforeRequesthook execution or cache lookups), composes with a caller-suppliedoptions.init.signalso either can abort the request independently, and has no effect on cache-served responses. With notimeoutconfigured, requests behave exactly as they did before this option existed.a4bd0d8:
createFetchifynow accepts an optionalretry(FetchifyRetryOptions), applied as the default retry configuration for every request made by the client, and eachFetchifyMethodcall accepts a per-requestretryoverride — either a replacementFetchifyRetryOptionsobject orfalseto disable retry for that call. A failed request is retried with capped exponential backoff and full jitter (or the response'sRetry-Afterheader, for a429/503), re-runningbeforeRequestand getting a freshtimeoutwindow per attempt. With noretryOnsupplied, only idempotent methods (GET,HEAD,OPTIONS,PUT,DELETE) are retried, and only for network failures,FetchifyTimeoutErrors, andFetchifyErrors with status408,429,500,502,503, or504—POST/PATCHare never retried by default, and a caller-triggeredAbortSignalis never retried under any configuration. Retry never applies to a cache-servedGET. With noretryconfigured, requests behave exactly as they did before this option existed.e965720:
FetchifyMethodOptions.bodynow accepts any JSON-serializable value (plain object, array, string, number, boolean,null) in addition to standardBodyInittypes. When the request's resolvedContent-Typeheader isapplication/json, a non-BodyInitbody is automatically serialized withJSON.stringifybefore being sent; otherwisebodyis forwarded tofetchunchanged, exactly as before. StandardBodyInitvalues (string,Blob,ArrayBuffer/typed arrays,FormData,URLSearchParams,ReadableStream) are never re-serialized, so an already-JSON.stringify'd string is never double-encoded, andoptions.init.bodyis never serialized.182fccf:
createFetchifynow accepts an optionalhooks.beforeRequestasync hook, invoked with the fully-resolved request (method,url,headers,body,init) immediately before each outgoingfetchcall (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 toundefinedto leave the request unchanged. A newdefineBeforeRequestHookhelper mirrorsdefineOptions/defineHeadersas a typed authoring entry point for the hook function.f86a631: Add an optional
loggeroption tocreateFetchify(andFetchifyOptions/defineOptions), accepting a structuralLoggerAdapter({ 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 (nullif 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 nologgerconfigured, no logging occurs.Add a built-in console-backed
LoggerAdapterat the@kamsteegsoftware/fetchify/logger/consolesubpath.createConsoleLogger()prints one line per request —console.logfor successful entries (with a trailing[cached]for cache hits),console.errorfor entries whose request threw — with no configuration options, for consumers who want request visibility without writing their own adapter.86f6061: Every
FetchifyMethodcall now accepts an optionalinvalidate?: boolean | (string | URL)[]option for explicit, opt-in cache invalidation: when a request's response resolves as a liveokresponse on a client configured withcache,invalidate: truedeletes theGETcache entry for that request's own URL, and an array resolves each entry to a URL (astringthe same way a request path resolves, joined againstbaseUrl; aURLused as-is) and deletes itsGETcache entry. Invalidation is entirely explicit — omittinginvalidate(or passingfalse/[]) has zero cache side effects beyond a request's existingGETread/write behavior, and the option is honored on any HTTP method, not just mutating verbs. Each entry is deleted independently, so aCacheAdapter.deletefailure for one doesn't block the rest or affect the resolved response.817b9ca: Add
defineOptionsfactory for authoring a typedFetchifyOptionsobject independently ofcreateFetchify, e.g.createFetchify(defineOptions({ baseUrl: "https://api.example.com" })). TheCreateFetchifyOptionstype has been renamed toFetchifyOptions.11a0aee: Add a Redis-backed
CacheAdapterat the@kamsteegsoftware/fetchify/cache/redissubpath.createRedisCacheAdapter(client)takes an already-connected client and mapsCacheAdapter'sget/set/delete/hasonto Redis'GET/SET .. PX/DEL/EXISTScommands, withttlMsenforced as Redis' native millisecond expiry.The
clientargument is typed against a minimal structural interface (get,set,del,exists) rather than a specific Redis package, so both a BunRedisClient(Bun.redis/new Bun.RedisClient(...)) and anioredisclient can be passed in withoutfetchifydepending on either.c0a1103: Add an Upstash REST-backed
CacheAdapterat the@kamsteegsoftware/fetchify/cache/upstashsubpath.createUpstashCacheAdapter({ client, ttlMs })takes an already-constructed Upstash REST client and mapsCacheAdapter'sget/set/delete/hasonto itsget/set/del/existsmethods, withttlMsenforced via Upstash'spxexpiry option.The
clientargument is typed against a minimal structural interface (get,set,del,exists) matching@upstash/redis'sRedisclient shape, rather than depending on the@upstash/redispackage directly, so a real@upstash/redisinstance can be passed in withoutfetchifyadding it as a dependency.4210b57: Add
createFetchifyfactory that returns an HTTP client withGET,POST,PUT,PATCH,DELETE,HEAD, andOPTIONSmethods, resolving relative request paths against an optionalbaseUrl.