From c7891b77a15e4a4484c8de305b83aad74dbc39d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:49:38 +0000 Subject: [PATCH 1/4] Remove handshake timing figures from the README The Connection reuse section quoted latency figures for a cold and a reused connection. They depend on the network path between the client and the API and go stale, so the section now only states that a new connection pays a TCP and TLS handshake and a request on a reused connection does not. Co-Authored-By: Claude Fable 5.1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41f25b6..11d66e6 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ end. ## Connection reuse -The `ipdata` application owns a dedicated `httpc` profile, also named `ipdata`, that keeps persistent HTTPS connections (HTTP/1.1 keep-alive) to the API for as long as the application runs. Start the application once per node and keep it running: every lookup from every process then goes out on an already established connection instead of paying a new TCP and TLS handshake (roughly 660 to 980 ms for a cold connection to api.ipdata.co against 220 to 300 ms for a request on a reused one). Idle connections are closed after 60 seconds. HTTP/2 is not available with `httpc`, so the saving is the TLS handshake, not multiplexing; an HTTP/2 client such as `gun` would need a connection-owner process and a new client lifecycle, which is out of scope for this library. TLS 1.3 session tickets are enabled so that a connection opened after the idle timeout can resume the previous TLS session. +The `ipdata` application owns a dedicated `httpc` profile, also named `ipdata`, that keeps persistent HTTPS connections (HTTP/1.1 keep-alive) to the API for as long as the application runs. Start the application once per node and keep it running: every lookup from every process then goes out on an already established connection. A new connection pays a TCP and TLS handshake; a request on a reused connection does not. Idle connections are closed after 60 seconds. HTTP/2 is not available with `httpc`, so the saving is the TLS handshake, not multiplexing; an HTTP/2 client such as `gun` would need a connection-owner process and a new client lifecycle, which is out of scope for this library. TLS 1.3 session tickets are enabled so that a connection opened after the idle timeout can resume the previous TLS session. A client map returned by `ipdata:new/1,2` is plain immutable data: it holds no process, socket or other resource, so it can be shared freely between processes, kept in application state or rebuilt at any time, and there is nothing to close. `application:stop(ipdata)` stops the profile and closes its connections. If a request is made before the application has been started, the profile is started on demand (`inets` and `ssl` must be running). From 8b4d0da23c07d7f25182dcd8f4194800afe5b493 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:50:57 +0000 Subject: [PATCH 2/4] Rename the workflow to CI and refresh its actions and toolchain The workflow has run the test suite on pull requests and on pushes to main since the connection-reuse change, but it was still named after its publish job. It is now named CI; the publish job keeps its name and its tag gate. - fail-fast is off, so a failing OTP release no longer cancels the other jobs and every release reports its own result. - actions/checkout and actions/cache move to their current majors, which run on Node.js 24; the runner had been warning that the v4 releases target the deprecated Node.js 20. - Each OTP release is paired with the newest rebar3 release that supports it (3.24 for OTP 25, 3.25 for OTP 26, 3.28 for OTP 27 and later) instead of one 3.24 pin for all of them, and the cache key includes the rebar3 version so a build directory is never shared between rebar3 releases. - OTP 28 and 29 join the test matrix; the publish job stays on OTP 27, now with rebar3 3.28. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/publish.yml | 38 +++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fd98658..081fdce 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish to Hex.pm +name: CI on: push: @@ -13,31 +13,47 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: + # Let every OTP release report its own result instead of cancelling + # the remaining jobs as soon as one of them fails. + fail-fast: false matrix: + # Each OTP release is paired with the newest rebar3 release that + # supports it: 3.24 is the last one for OTP 25, 3.25 the last one + # for OTP 26, and 3.28 covers OTP 27 and later. + # # ubuntu-latest resolves to Ubuntu 26.04, for which no OTP 25 builds # are published (erlef/setup-beam supports OTP 26+ there and 24.3+ on # ubuntu-24.04), so the OTP 25 job runs on ubuntu-24.04. include: - otp: '25' + rebar3: '3.24' os: ubuntu-24.04 - otp: '26' + rebar3: '3.25' os: ubuntu-latest - otp: '27' + rebar3: '3.28' + os: ubuntu-latest + - otp: '28' + rebar3: '3.28' + os: ubuntu-latest + - otp: '29' + rebar3: '3.28' os: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: erlef/setup-beam@v1 with: otp-version: ${{ matrix.otp }} - rebar3-version: '3.24' + rebar3-version: ${{ matrix.rebar3 }} - name: Restore dependencies cache - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: _build - key: ${{ runner.os }}-otp-${{ matrix.otp }}-build-${{ hashFiles('rebar.config') }} - restore-keys: ${{ runner.os }}-otp-${{ matrix.otp }}-build- + key: ${{ runner.os }}-otp-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-build-${{ hashFiles('rebar.config') }} + restore-keys: ${{ runner.os }}-otp-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-build- - name: Compile run: rebar3 compile @@ -59,19 +75,19 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: erlef/setup-beam@v1 with: otp-version: '27' - rebar3-version: '3.24' + rebar3-version: '3.28' - name: Restore dependencies cache - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: _build - key: ${{ runner.os }}-otp-27-build-${{ hashFiles('rebar.config') }} - restore-keys: ${{ runner.os }}-otp-27-build- + key: ${{ runner.os }}-otp-27-rebar3-3.28-build-${{ hashFiles('rebar.config') }} + restore-keys: ${{ runner.os }}-otp-27-rebar3-3.28-build- - name: Publish to Hex.pm run: rebar3 hex publish --repo hexpm --yes From bea389d7cf98ad823b56f9a40cee639169f92170 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:50:57 +0000 Subject: [PATCH 3/4] Match the README lookup example to the API's 8.8.8.8 response The "Look Up a Specific IP" example showed a city, city-level coordinates and a time zone for 8.8.8.8 that the API does not return for that address: it is an anycast address located at country level only, so city is null and the coordinates are country-level. The example now shows the values of the API's own sample response for the address (the same sample serves as a test fixture), explains that fields without data are returned as null, and shows the datacenter flag the address does carry. Co-Authored-By: Claude Fable 5.1 --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 11d66e6..74eb48c 100644 --- a/README.md +++ b/README.md @@ -79,23 +79,25 @@ maps:get(<<"country_name">>, Result). %% Access response fields maps:get(<<"ip">>, Result). %% => <<"8.8.8.8">> maps:get(<<"country_name">>, Result). %% => <<"United States">> -maps:get(<<"city">>, Result). %% => <<"Ashburn">> -maps:get(<<"latitude">>, Result). %% => 39.03 -maps:get(<<"longitude">>, Result). %% => -77.5 +maps:get(<<"country_code">>, Result). %% => <<"US">> + +%% Fields the API has no data for are null. 8.8.8.8 is an anycast address +%% located at country level only, so it has no city and country-level coordinates. +maps:get(<<"city">>, Result). %% => null +maps:get(<<"latitude">>, Result). %% => 39.7599983215332 +maps:get(<<"longitude">>, Result). %% => -98.5 %% Nested data ASN = maps:get(<<"asn">>, Result). maps:get(<<"name">>, ASN). %% => <<"Google LLC">> Threat = maps:get(<<"threat">>, Result). -maps:get(<<"is_vpn">>, Threat). %% => false +maps:get(<<"is_vpn">>, Threat). %% => false +maps:get(<<"is_datacenter">>, Threat). %% => true %% Flags the API includes only when they are true: read them with a default maps:get(<<"is_anycast">>, Result, false). %% => true maps:get(<<"is_satellite">>, Result, false). %% => false - -TimeZone = maps:get(<<"time_zone">>, Result). -maps:get(<<"name">>, TimeZone). %% => <<"America/New_York">> ``` ### Filter Response Fields From 6b407a4f4a9e4b753db022aef3889e55a21a69fa Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:55:42 +0000 Subject: [PATCH 4/4] Drop OTP 29 from the test matrix meck 0.9.2, the test dependency, does not compile on OTP 29: its meck_matcher module uses a bare catch, which OTP 29 deprecates, and the dependency build fails on that warning. meck 1.1.1 replaced the catch, but meck 1.1.0 dropped OTP 25, which this matrix keeps, so the matrix stops at OTP 28 for now. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/publish.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 081fdce..ad38095 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,6 +21,10 @@ jobs: # supports it: 3.24 is the last one for OTP 25, 3.25 the last one # for OTP 26, and 3.28 covers OTP 27 and later. # + # OTP 29 is not listed yet: the test dependency meck 0.9.2 does not + # compile there (its bare `catch` is deprecated in OTP 29), and the + # meck releases that do compile dropped OTP 25. + # # ubuntu-latest resolves to Ubuntu 26.04, for which no OTP 25 builds # are published (erlef/setup-beam supports OTP 26+ there and 24.3+ on # ubuntu-24.04), so the OTP 25 job runs on ubuntu-24.04. @@ -37,9 +41,6 @@ jobs: - otp: '28' rebar3: '3.28' os: ubuntu-latest - - otp: '29' - rebar3: '3.28' - os: ubuntu-latest steps: - uses: actions/checkout@v7