wire-compatible API replacements without the rebuild. pull a contract, serve it instantly, verify it, and heal it back onto the live api when it drifts. BYOK and local-first
1
stars
31
commits
JavaScript
primary language
Sep 3, 2026
updated
███ ███ ████████ ██ ████████ ███████
████ ████ ██ ██ ██ ██ ██ ██
██ ████ ██ ██████ ██ ██ ██ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ████████ ████████ ████████ ██ ██
meldr, wire-compatible API replacements without the rebuild
your openapi file is lying to you. the api shipped a field, an id went from int to string, a 201 quietly became a 202, and nothing told the yaml sitting in your repo. meldr sends one real request per operation, compares what came back against what the contract claims, and writes the difference back
thats a real run replayed at reading speed, the actual thing takes about a fifth
of a second. node tools/demo.mjs regenerates it
try it without installing anything
npx @ranveergill/meldr demo
one command, nothing installed, no account. it writes a contract and an api that drifted off it, then runs the loop
meldr verify --heal
verifying Meldr Petstore against http://localhost:4123
GET /v1/pets FAIL 200 $[0].id: expected integer, got string; $[1].id: expected integer, got string
POST /v1/pets FAIL 202 expected status 201, got 202; id: expected integer, got string
DELETE /v1/pets/{id} PASS 204
GET /v1/pets/{id} FAIL 200 id: expected integer, got string
1 passed · 3 failed
healing Meldr Petstore v1.0.0 against live http://localhost:4123
GET /v1/pets FIX $[0].id contract says integer, live sends string
GET /v1/pets FIX $[0].createdAt live sends "createdAt" (string), contract does not declare it
POST /v1/pets RISK responses.201 -> 202 contract's success is 201, live answers 202
✓ healed contracts\api.yaml, 2 fix(es) applied
1 held back · rerun with --all to take the risky ones too
re-verifying Meldr Petstore against http://localhost:4123
GET /v1/pets PASS 200
POST /v1/pets FAIL 202 expected status 201, got 202
DELETE /v1/pets/{id} PASS 204
GET /v1/pets/{id} PASS 200
3 passed · 1 failed
still red · `meldr verify --heal --all` takes the destructive fixes too
FIX goes in on sight. RISK waits for --all bcuz it deletes something, thats why that run ends one short. the patches go in through the $ref so one fix to Pet.id lands in components/schemas/Pet and every operation using it moves at once, read the git diff after, its a normal yaml diff
fixed on sight
type drift integer -> string, the stale format and example go with it
new fields live sends createdAt, contract learns createdAt
new statuses recorded with the shape they actually returned
waits for --all
moved success 201 -> 202, the old response node moves, it doesnt duplicate
required gone drops a name from required[] the api stopped sending
dead upstream op marked deprecated, never deleted
allOf/oneOf/anyOf get reported and never auto-patched, too easy to wreck
commands
demo red to green on a throwaway petstore, nothing to set up
init scaffold a project with a starter contract
pull ingest an OpenAPI 3.x contract (file or URL)
serve run a wire-compatible replacement server
gen generate a standalone, dependency-free editable server
verify verify a running implementation against the contract
draft BYOK: draft a contract from a description via your own AI key
record capture the real API so you can serve it back offline
heal self-maintain: pull the contract back onto the live api
quickstart
npm install -g @ranveergill/meldr
mkdir demo && cd demo
meldr init
meldr serve &
curl localhost:3000/ping
# {"status":"ok"}
meldr pull https://raw.githubusercontent.com/ranveerlabs/meldr/main/testdata/petstore.yaml
meldr serve &
curl localhost:3000/v1/pets/42
# {"id":42,"name":"Rex","tag":"friendly","status":"available"}
meldr verify
# 4 passed · 0 failed
meldr gen
node server.mjs
record it while you still can
if the api you depend on is going away, or behind a subscription, or rate limited to nothing, capture it once and serve it back forever
meldr record --base https://api.example.com --header "Authorization: Bearer $TOKEN"
meldr serve --from recording.json
one real request per operation, the actual bodies saved to json. after that the upstream can go dark and your dev loop doesnt notice. the recording is real data, so the ids and the pagination and the error shapes are the ones you will actually get, not something synthesised from the schema
access_token, refresh_token, client_secret and friends get replaced with [scrubbed] before anything is written, and it tells you how many it caught. still read the file before you commit it, a response body holds more than you expect
anything not in the recording falls back to the contract, and X-Meldr-Status still forces a declared response so your retry paths stay testable
one response per operation by default. list the ids you care about and it captures each
record:
cases:
getTrack:
- {id: 11dFghVXANMlKmJXsNCbNl}
- {id: 4cOdK2wGLETKBW3PvgPWqT}
replay picks the entry matching the id you asked for. ask for an id that was never taped and you get the first one back so you can keep poking around, --strict 404s instead and tells you which ids it does have
building against it
a mock that forgets everything is a read only view, you cant build a playlist editor against it. --stateful gives you a store
meldr serve --stateful --require-auth
POST to a collection keeps it, GET the item reads it back, PUT and PATCH update it, DELETE means the next read is a 404, and the list reflects all of it. the collection seeds itself from the contract on first touch so a fresh client isnt staring at an empty page
in memory by default, --state-file state.json writes it out and picks it back up next time so a session survives a restart
--require-auth 401s anything without a credential, honouring whatever securitySchemes the contract declares. any value passes, its there so you can build the token plumbing and the refresh-on-401 path, its not checking anything
both are off unless you ask, so verify and gen stay deterministic
pointing it at a real api
synthetic ids 404 and unauthenticated calls 401, so both are worth pinning. meldr.yaml holds them and ${ENV} is read at run time, the file stays commitable
headers:
Authorization: Bearer ${SPOTIFY_TOKEN}
params:
default: {limit: 5}
getTrack: {id: 11dFghVXANMlKmJXsNCbNl}
default applies to any param of that name, the operationId key wins over it. or straight on the command line
meldr verify --header "Authorization: Bearer $TOKEN" --param id=11dFghVXANMlKmJXsNCbNl
your headers go on last so a contract cant overwrite your auth. a 429 gets retried with the Retry-After it hands you rather than counted as drift, and --concurrency sets how many operations go at once, 4 by default
see it before it writes
meldr heal --diff
comments and quote style survive a heal, the diff is the three real changes and not a reflow of the whole file
upstream drift
same thing against someone elses contract instead of a live server
meldr heal --upstream https://api.example.com/openapi.yaml
new operations get spliced in dereferenced so nothing points at components you
dont have. operations upstream dropped get deprecated: true and keep their
bodies
ci gate
meldr heal --check writes nothing and exits 1 on drift. theres an action so you dont have to wire it yourself
- uses: ranveerlabs/meldr@main
with:
base: https://api.example.com
headers: Authorization: Bearer ${{ secrets.API_TOKEN }}
params: |
id=11dFghVXANMlKmJXsNCbNl
or by hand if you want the pieces
- run: meldr serve &
- run: meldr verify
- run: meldr heal --check --report drift.json
the action takes base, contract, upstream, report, working-directory, version
and fail-on-drift, and sets a drifted output so a later step can open the PR
drift.json is stable, kind/op/at/detail/safety/patchable per finding plus a summary. wire it to whatever opens the PR
the leftovers no rule can patch go to --ai, opt-in and BYOK, and it only ever
replaces paths. info, servers and components stay yours
config
meldr.yaml auto-generated on init
name: demo
contract: contracts/api.yaml
port: 3000
cors: false
byok & zero leaks
license: apache-2.0
31 commits
Hacker News (1)
JavaScript
100.0%
wire-compatible API replacements without the rebuild. pull a contract, serve it instantly, verify it, and heal it back onto the live api when it drifts. BYOK and local-first
1
stars
31
commits
JavaScript
primary language
Sep 3, 2026
updated
███ ███ ████████ ██ ████████ ███████
████ ████ ██ ██ ██ ██ ██ ██
██ ████ ██ ██████ ██ ██ ██ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ████████ ████████ ████████ ██ ██
meldr, wire-compatible API replacements without the rebuild
your openapi file is lying to you. the api shipped a field, an id went from int to string, a 201 quietly became a 202, and nothing told the yaml sitting in your repo. meldr sends one real request per operation, compares what came back against what the contract claims, and writes the difference back
thats a real run replayed at reading speed, the actual thing takes about a fifth
of a second. node tools/demo.mjs regenerates it
try it without installing anything
npx @ranveergill/meldr demo
one command, nothing installed, no account. it writes a contract and an api that drifted off it, then runs the loop
meldr verify --heal
verifying Meldr Petstore against http://localhost:4123
GET /v1/pets FAIL 200 $[0].id: expected integer, got string; $[1].id: expected integer, got string
POST /v1/pets FAIL 202 expected status 201, got 202; id: expected integer, got string
DELETE /v1/pets/{id} PASS 204
GET /v1/pets/{id} FAIL 200 id: expected integer, got string
1 passed · 3 failed
healing Meldr Petstore v1.0.0 against live http://localhost:4123
GET /v1/pets FIX $[0].id contract says integer, live sends string
GET /v1/pets FIX $[0].createdAt live sends "createdAt" (string), contract does not declare it
POST /v1/pets RISK responses.201 -> 202 contract's success is 201, live answers 202
✓ healed contracts\api.yaml, 2 fix(es) applied
1 held back · rerun with --all to take the risky ones too
re-verifying Meldr Petstore against http://localhost:4123
GET /v1/pets PASS 200
POST /v1/pets FAIL 202 expected status 201, got 202
DELETE /v1/pets/{id} PASS 204
GET /v1/pets/{id} PASS 200
3 passed · 1 failed
still red · `meldr verify --heal --all` takes the destructive fixes too
FIX goes in on sight. RISK waits for --all bcuz it deletes something, thats why that run ends one short. the patches go in through the $ref so one fix to Pet.id lands in components/schemas/Pet and every operation using it moves at once, read the git diff after, its a normal yaml diff
fixed on sight
type drift integer -> string, the stale format and example go with it
new fields live sends createdAt, contract learns createdAt
new statuses recorded with the shape they actually returned
waits for --all
moved success 201 -> 202, the old response node moves, it doesnt duplicate
required gone drops a name from required[] the api stopped sending
dead upstream op marked deprecated, never deleted
allOf/oneOf/anyOf get reported and never auto-patched, too easy to wreck
commands
demo red to green on a throwaway petstore, nothing to set up
init scaffold a project with a starter contract
pull ingest an OpenAPI 3.x contract (file or URL)
serve run a wire-compatible replacement server
gen generate a standalone, dependency-free editable server
verify verify a running implementation against the contract
draft BYOK: draft a contract from a description via your own AI key
record capture the real API so you can serve it back offline
heal self-maintain: pull the contract back onto the live api
quickstart
npm install -g @ranveergill/meldr
mkdir demo && cd demo
meldr init
meldr serve &
curl localhost:3000/ping
# {"status":"ok"}
meldr pull https://raw.githubusercontent.com/ranveerlabs/meldr/main/testdata/petstore.yaml
meldr serve &
curl localhost:3000/v1/pets/42
# {"id":42,"name":"Rex","tag":"friendly","status":"available"}
meldr verify
# 4 passed · 0 failed
meldr gen
node server.mjs
record it while you still can
if the api you depend on is going away, or behind a subscription, or rate limited to nothing, capture it once and serve it back forever
meldr record --base https://api.example.com --header "Authorization: Bearer $TOKEN"
meldr serve --from recording.json
one real request per operation, the actual bodies saved to json. after that the upstream can go dark and your dev loop doesnt notice. the recording is real data, so the ids and the pagination and the error shapes are the ones you will actually get, not something synthesised from the schema
access_token, refresh_token, client_secret and friends get replaced with [scrubbed] before anything is written, and it tells you how many it caught. still read the file before you commit it, a response body holds more than you expect
anything not in the recording falls back to the contract, and X-Meldr-Status still forces a declared response so your retry paths stay testable
one response per operation by default. list the ids you care about and it captures each
record:
cases:
getTrack:
- {id: 11dFghVXANMlKmJXsNCbNl}
- {id: 4cOdK2wGLETKBW3PvgPWqT}
replay picks the entry matching the id you asked for. ask for an id that was never taped and you get the first one back so you can keep poking around, --strict 404s instead and tells you which ids it does have
building against it
a mock that forgets everything is a read only view, you cant build a playlist editor against it. --stateful gives you a store
meldr serve --stateful --require-auth
POST to a collection keeps it, GET the item reads it back, PUT and PATCH update it, DELETE means the next read is a 404, and the list reflects all of it. the collection seeds itself from the contract on first touch so a fresh client isnt staring at an empty page
in memory by default, --state-file state.json writes it out and picks it back up next time so a session survives a restart
--require-auth 401s anything without a credential, honouring whatever securitySchemes the contract declares. any value passes, its there so you can build the token plumbing and the refresh-on-401 path, its not checking anything
both are off unless you ask, so verify and gen stay deterministic
pointing it at a real api
synthetic ids 404 and unauthenticated calls 401, so both are worth pinning. meldr.yaml holds them and ${ENV} is read at run time, the file stays commitable
headers:
Authorization: Bearer ${SPOTIFY_TOKEN}
params:
default: {limit: 5}
getTrack: {id: 11dFghVXANMlKmJXsNCbNl}
default applies to any param of that name, the operationId key wins over it. or straight on the command line
meldr verify --header "Authorization: Bearer $TOKEN" --param id=11dFghVXANMlKmJXsNCbNl
your headers go on last so a contract cant overwrite your auth. a 429 gets retried with the Retry-After it hands you rather than counted as drift, and --concurrency sets how many operations go at once, 4 by default
see it before it writes
meldr heal --diff
comments and quote style survive a heal, the diff is the three real changes and not a reflow of the whole file
upstream drift
same thing against someone elses contract instead of a live server
meldr heal --upstream https://api.example.com/openapi.yaml
new operations get spliced in dereferenced so nothing points at components you
dont have. operations upstream dropped get deprecated: true and keep their
bodies
ci gate
meldr heal --check writes nothing and exits 1 on drift. theres an action so you dont have to wire it yourself
- uses: ranveerlabs/meldr@main
with:
base: https://api.example.com
headers: Authorization: Bearer ${{ secrets.API_TOKEN }}
params: |
id=11dFghVXANMlKmJXsNCbNl
or by hand if you want the pieces
- run: meldr serve &
- run: meldr verify
- run: meldr heal --check --report drift.json
the action takes base, contract, upstream, report, working-directory, version
and fail-on-drift, and sets a drifted output so a later step can open the PR
drift.json is stable, kind/op/at/detail/safety/patchable per finding plus a summary. wire it to whatever opens the PR
the leftovers no rule can patch go to --ai, opt-in and BYOK, and it only ever
replaces paths. info, servers and components stay yours
config
meldr.yaml auto-generated on init
name: demo
contract: contracts/api.yaml
port: 3000
cors: false
byok & zero leaks
license: apache-2.0
Hacker News (1)
31 commits
JavaScript
100.0%