cat odoo-client.ts
04 / 19
@vvhybe/odoo-client
A type-safe JSON-RPC and XML-RPC client for Odoo 14-19, on native fetch.
- role
- author, maintainer
- year
- 2026
- access
- -rwxr-xr-x (open source)
- problem
- Talking to Odoo from JavaScript meant hand-writing JSON-RPC payloads, and every Odoo version moves an endpoint or an argument.
- approach
- One typed client: an ORM layer that doesn't care which wire is underneath, version quirks handled inside, retries and pagination built in.
- outcome
- Published on npm under MIT, tested with Vitest in CI, with every release gated on lint, types and tests.
TL;DR
// overview
Talks to Odoo from Node.js, Next.js or React with session, password or API-key auth. Pagination is an async generator, so for await walks a whole model without loading it at once.
Retries use exponential backoff and never retry auth errors. Errors are typed, mapped from Odoo's own exceptions, and the protocol is swappable: the same ORM calls go over JSON-RPC or XML-RPC.
// one ORM, two wires
OdooConnect is the only door. Under it, an ORM service turns search_read, create or write into RPC payloads, and a client sends them: JSON-RPC by default, keeping the session cookie itself in Node, or XML-RPC with protocol: 'xmlrpc'.
Version differences live in one place. Below 16, reads go to the old /web/dataset/search_read; from 19, name_search takes positional arguments. Code that calls the client never has to know.
// retries that know when to stop
Network errors and timeouts retry with exponential backoff. Authentication errors never do: a wrong password won't fix itself, so it surfaces at once.
Odoo's own exceptions come back typed: an odoo.exceptions.AccessError arrives as an OdooAccessError you can catch by class.
// a model of any size
paginate() is an async generator, a hundred records a page by default, so for await walks a whole model without holding it in memory.
// five names in three days
The commit log went Nodoo, Codoo, odoox, odoo-sdk, then @vvhybe/odoo-client. Naming was the hard part. Publishing is guarded: lint, format, typecheck, tests and build all run before npm sees a version.
// stack
- typescript
- node 18+
- vitest
- json-rpc
- xml-rpc