Skip to content

v5.0.0

Choose a tag to compare

@nktnet1 nktnet1 released this 30 Sep 05:52
· 40 commits to main since this release
0d1ad38

This is a major release that significantly redesigns and modernises the library. It introduces several breaking changes.

Native runtime and installation

  • Removed node-libcurl and replaced it with a Rust Node-API v8 binding. libcurl remains the underlying HTTP transport.
  • The postinstall script that downloaded node-libcurl binaries from GitHub is no longer needed. Native binaries are distributed through platform-specific optional npm packages. Installation and import do not download binaries through lifecycle scripts or compile native code.
  • Added prebuilt distribution for
    • Windows x64, arm64, and ia32
    • macOS x64 and arm64
    • Linux x64 and arm64 on both glibc and musl. GNU/Linux prebuilds require GLIBC 2.31 or newer.
  • Lowered the minimum Node.js version from 22 to 16.17.0. Node-API binaries are shared across compatible Node.js versions rather than built for each Node.js major.
  • Added explicit source builds and custom native binary loading. Linux and Windows bundle libcurl with HTTP/2 support by default. macOS uses system libcurl. Default TLS backends use OpenSSL on Linux, Schannel on Windows, and system trust on macOS.

Breaking changes

  • Multipart API: Replaced formData: HttpPostField[] with form: FormData. Payload precedence is now form, then json, then body, instead of json, then body, then formData.
  • Removed low-level API: Removed setEasyOptions, SetEasyOptionCallback, and HttpPostField. Use the dedicated proxy, TLS, local binding, and TCP keepalive options. Arbitrary libcurl option callbacks are no longer exposed.
  • Removed options: Replaced insecure with the inverse rejectUnauthorized option. Removed debug and its request-input dump in transport errors.
  • Timeout semantics: timeout now limits the wait for response headers, not the complete transfer. Use overallTimeout for the complete operation, including redirects and retries, and socketTimeout for inactivity.
  • Redirect headers: Caller-supplied headers are dropped on every redirect by default, including same-origin redirects. Forward selected headers through allowRedirectHeaders. Payload headers are removed when a redirect changes the method and discards the body.
  • Proxy configuration: Ambient proxy environment variables are ignored. Configure an explicit HTTP or HTTPS proxy through proxy.
  • Compression: gzip/deflate negotiation and decompression are enabled by default. Decompressed responses have content-encoding and content-length removed. Use gzip: false to keep the encoded body.
  • Query strings: qs now uses nested bracket notation and RFC 3986 encoding. Existing queries are parsed and re-serialised. Dates use ISO strings, and an explicit undefined removes a matching existing query value. Review signed URLs and exact URL assertions.
  • Request headers and validation: Undefined header values are rejected rather than skipped. Header arrays follow Node-style handling. Options and JSON serialisability are validated at runtime. Invalid headers, unsupported framing, and incorrect Content-Length values are rejected. Multipart requests cannot supply their own Content-Length.
  • Response headers: Repeated fields now follow Node-style joining and singleton rules rather than always becoming arrays. set-cookie is always an array when present. Trailers are validated but are not merged into response headers.
  • Request restrictions: Only HTTP and HTTPS URLs are supported, including redirect targets. CONNECT is rejected, TRACE cannot contain a non-empty body, and OPTIONS with content requires a non-empty Content-Type.
  • Errors and imports: Removed the sync-request-curl/errors entry point. Import errors from the package root. getBody() now throws ResponseError for HTTP status codes of 300 or above. Request-layer failures can throw RequestError with ETIMEDOUT, ERR_TOO_MANY_REDIRECTS, or ERR_REQUEST_FAILED. Native transport failures retain numeric CurlError codes.
  • TypeScript: Response.body is now typed as Buffer, and response mocks need isError(). JSON inputs use JsonLike instead of any. Use getBody() rather than getBody(undefined) for an unencoded body.

New features and improvements

  • Added GET retries with retry, retryDelay, and maxRetries. Retry and delay callbacks receive the error, buffered response, and attempt number. Retries are disabled by default. When enabled, defaults are five retries and a 200 ms delay.
  • Added private file and memory caches with freshness checks, Vary, conditional revalidation, permanent redirect reuse, and invalidation after successful unsafe requests. Added cacheNamespace and synchronous isMatch, isExpired, and canCache policy hooks. Caching is disabled by default and bypassed for requests carrying credentials, cookies, or bodies, and for custom transport settings.
  • Added authenticated HTTP/HTTPS proxies, caFile, localAddress, localInterface, and configurable tcpKeepAlive. Added connection reuse through keep-alive Node Agent instances, with native pool cleanup on agent destruction. Custom transport settings bypass shared agent pools.
  • Added synchronous FormData support for strings, numbers, booleans, Buffers, and Blobs. Includes filename and content-type handling, knownLength compatibility, raw part headers, and synchronous boundary, header, buffer, and length helpers. Stream-valued fields and asynchronous helpers are not supported.
  • Added URL object inputs, case-insensitive methods, and PROPFIND typing. Improved internationalised hostname handling and nested query serialisation, including circular-value and prototype-pollution safeguards.
  • Added response.isError(), which checks for status 0 or 400 and above. Response helpers now observe changes to public response properties and remain callable when detached. getJSON() continues to parse JSON without rejecting HTTP error statuses. Added the utf-16le encoding alias.
  • Preserved explicit JSON Content-Type, Authorization, and Accept-Encoding headers. Removed implicit Accept and raw-body Content-Type defaults and automatic Content-Length: 0 for empty GET, DELETE, and HEAD requests.
  • Hardened response framing and header parsing, including Node's configured header-size limit, non-ASCII header bytes, and folded headers. Redirects can proceed without waiting for the redirect body when caching and custom retry policies do not need it.
  • Consolidated public types and error classes at the package root while retaining ESM and callable CommonJS support. Added generated API documentation and expanded compatibility, native packaging, source-build, and cross-platform release checks.

Migration guide

  • Upgrade and remove the old installer workaround. Remove the GitHub binary-download postinstall step and any node-libcurl dependency or override used only for this package. Keep optional dependencies enabled so the package manager installs the matching native binary.

    npm install sync-request-curl@^5 --include=optional

    The --include=optional option is typically enabled by default, although specified explicitly above in case you have configured your package manager to not install optional packages. If you do not wish to install binaries via optional packages, refer to the build from source instructions.

  • Update imports. Move error imports to the root. Public types are also available there. CommonJS remains available through require('sync-request-curl').

    import request, { FormData, CurlError, RequestError, ResponseError } from 'sync-request-curl'
    import type { Options, Response } from 'sync-request-curl'
  • Replace multipart arrays. Use this package's FormData, not global web FormData or the separate form-data package. Read files into Buffers instead of passing file paths. Multipart payloads are buffered in memory.

    import { readFileSync } from 'node:fs'
    import request, { FormData } from 'sync-request-curl'
    
    const form = new FormData()
    form.append('file', readFileSync('./report.txt'), {
      filename: 'report.txt',
      contentType: 'text/plain',
    })
    form.append('description', 'Report upload')
    
    const response = request('POST', 'https://example.com/upload', { form })
  • Replace legacy transport options. Change a v4 whole-request timeout to overallTimeout. Replace proxy callbacks with proxy: { url, username, password }. Map insecure: true to rejectUnauthorized: false only when disabling certificate and hostname verification is intentional. Prefer caFile for custom trust, and remove debug and setEasyOptions.

  • Review changed defaults and error handling. Allow-list only headers safe to forward across redirect origins. Remove undefined header entries, review query encoding and response-header assertions, and set gzip: false where raw compressed bytes are required. Handle RequestError and ResponseError alongside CurlError rather than matching error text.

  • Build explicitly when a prebuilt binary is unavailable or optional dependencies are omitted. There is no automatic source-build fallback.

    npm exec --no -- sync-request-curl-build
    • Source builds require Rust 1.88 or newer, Cargo, and the platform's native build tools. Build with the Node.js architecture that will load the addon.
    • Use --libcurl=system or --libcurl=bundled to override platform defaults. System mode fails if a compatible system libcurl cannot be found.
    • Use SYNC_REQUEST_CURL_NATIVE_PATH to load an externally managed .node binary.

Pull Requests

What's Changed

  • feat: replace node-libcurl with Node-API libcurl binding by @nktnet1 in #188
  • feat: migration from c/c++ to rust by @nktnet1 in #189
  • feat: api v5 major changes by @nktnet1 in #190
  • feat: support native source builds and Windows x86 prebuilds by @nktnet1 in #191
  • fix: address cache isolation and native transport lifecycle bugs by @nktnet1 in #192
  • release: beta conditions by @nktnet1 in #193
  • refactor: share release validation and publishing between CLI and CI by @nktnet1 in #194
  • feat: keywords, error formatting, version labels by @nktnet1 in #195
  • feat: docs cleanup, build from source with options by @nktnet1 in #196
  • fix: type incompatibility by @nktnet1 in #197
  • fix: restore CommonJS root type exports, remove /types and /errors by @nktnet1 in #198
  • fix: then request feature parity by @nktnet1 in #199
  • fix: restore response timeout semantics by @nktnet1 in #200
  • fix: reuse cached permanent redirects by @nktnet1 in #201
  • feat: add synchronous cache policy hooks by @nktnet1 in #202
  • fix: resolve API and runtime inconsistencies by @nktnet1 in #203
  • fix: align sync-request compatibility docs and encoding by @nktnet1 in #204
  • fix: request, response, retry + header parity by @nktnet1 in #205
  • fix: valibot error safe parse to avoid exposing ValiError by @nktnet1 in #206
  • fix: restore then-request response parity by @nktnet1 in #207
  • feat: http-basic feature parity by @nktnet1 in #208
  • feat: add synchronous FormData compatibility by @nktnet1 in #209

Full Changelog: v4.0.3...v5.0.0