@fernandoslim/odoo-jsonrpc

中文文档
2.2.0发布于 2026/10/08ISCTypeScriptESM + CJS零依赖近 30 天下载 2万

JSON-RPC和JSON-2 API(Odoo 19+)的轻量级Odoo客户端,零依赖.

中文使用文档
文档目录33 节
  1. 特征
  2. 安装
  3. 快速启动
  4. 认证
  5. 有用户名+密码
  6. 带有 API 密钥
  7. 带有当前会话标识
  8. 关于反应类型的分支
  9. 云花岗工人
  10. JSON-2(奥杜19+)
  11. 选项
  12. API 参考
  13. 拥挤
  14. 创建
  15. 读取
  16. 更新
  17. 删除
  18. 服务器动作
  19. 搜索读取( S)
  20. 搜索
  21. 搜索读取
  22. 域过滤器
  23. 关系场(多为2个/一为2个)
  24. 外地翻译
  25. 外部标识符
  26. 错误
  27. 处理错误 Try
  28. 断开连接
  29. 类型脚本
  30. 基准
  31. 更改日志
  32. 2.2.0 (中文(简体) )
  33. 许可证

奥多·杰森-RPC

JSON- RPC 和新的轻量级 Odoo 客户端 JSON-2 API(奥多19+)零依赖关系。 类型脚本第一. 在节点18+, Bun, Deno,和Cloudflare工人上工作.

依据 奥杜阿等 (XML-RPC). 感谢 维特洛法。 。 。 .

特征

  • 零运行时间依赖关系
  • 双重无害环境管理+共同JS与 .d.ts + 键 .d.cts
  • 两项协议:JSON-RPC(每个Odoo版本)和JSON-2(Odoo 19+,Odoo 22后留下的)
  • JSON- RPC 的三个认证模式: 证书、 API 密钥、 现有会话
  • fetch-基于 到处跑 fetch 可用
  • 小表面 : create, (中文(简体) ), read, (中文(简体) ), update, (中文(简体) ), delete, (中文(简体) ), search, (中文(简体) ), searchRead, (中文(简体) ), action, (中文(简体) ), call, (中文(简体) ), call_kw,和外部ID帮助器——两个协议的代码相同
  • 已打入错误( E)OdooError)带有HTTP状态,型号,方法和Odoo的例外名
  • 可选请求超时
  • Go 风格错误帮助器( S)Try避免尝试/捕获噪音

安装

npm install @fernandoslim/odoo-jsonrpc
# or
pnpm add @fernandoslim/odoo-jsonrpc
# or
bun add @fernandoslim/odoo-jsonrpc

德诺/JSR:

deno add jsr:@fernandoslim/odoo-jsonrpc

快速启动

import OdooJSONRpc from '@fernandoslim/odoo-jsonrpc';

const odoo = new OdooJSONRpc({
  baseUrl: process.env.ODOO_BASE_URL!, // e.g. https://my-odoo.example.com (port is optional)
  db: process.env.ODOO_DB!,
  username: process.env.ODOO_USERNAME!,
  password: process.env.ODOO_PASSWORD!,
});

await odoo.connect();

const partnerId = await odoo.create('res.partner', {
  name: 'Kool Keith',
  email: '[email protected]',
});

const [partner] = await odoo.read<{ id: number; name: string; email: string }>(
  'res.partner',
  partnerId,
  ['name', 'email']
);

认证

connect() 必须 在任何其他方法之前调用(但 initialize) (中文(简体) ).

有用户名+密码

返回完整的会话响应,包括 username, (中文(简体) ), partner_id, (中文(简体) ), server_version等 (中文(简体) ). 另设置一个会话 cookie 供后续调用 .

const odoo = new OdooJSONRpc({
  baseUrl: 'https://my-odoo.example.com',
  port: 443,
  db: 'my-db',
  username: 'admin',
  password: 'secret',
});
await odoo.connect();

带有 API 密钥

轻取. 仅返回 { uid }请求使用 execute_kw 通过 /jsonrpc。 。 。 .

const odoo = new OdooJSONRpc({
  baseUrl: 'https://my-odoo.example.com',
  port: 443,
  db: 'my-db',
  username: 'admin',
  apiKey: 'c721a30555935cbabe8851df3f3eb9e60e850711',
});
await odoo.connect();

带有当前会话标识

重用来自另一个来源的会话( cookie, 缓存等) .

const odoo = new OdooJSONRpc({
  baseUrl: 'https://my-odoo.example.com',
  port: 443,
  db: 'my-db',
  sessionId: '12eb065d6b17d27723a72f5dcb0d85071ae346e2',
});
await odoo.connect();

关于反应类型的分支

connect() 返回( n) OdooAuthenticateWithCredentialsResponse 或者说 OdooAuthenticateWithApiKeyResponse。使用 isCredentialsResponse 类型守护 :

import OdooJSONRpc, { isCredentialsResponse } from '@fernandoslim/odoo-jsonrpc';

const auth = await odoo.connect();
if (isCredentialsResponse(auth)) {
  console.log('Logged in as', auth.username);
} else {
  console.log('Logged in with API key, uid:', auth.uid);
}

云花岗工人

import { Hono } from 'hono';
import OdooJSONRpc, { Try } from '@fernandoslim/odoo-jsonrpc';

type Bindings = {
  ODOO_BASE_URL: string;
  ODOO_PORT: number;
  ODOO_DB: string;
  ODOO_USERNAME: string;
  ODOO_API_KEY: string;
};

const app = new Hono<{ Bindings: Bindings }>();
const odoo = new OdooJSONRpc();

app.use('/odoo/*', async (c, next) => {
  if (!odoo.is_connected) {
    await odoo.connect({
      baseUrl: c.env.ODOO_BASE_URL,
      port: c.env.ODOO_PORT,
      db: c.env.ODOO_DB,
      username: c.env.ODOO_USERNAME,
      apiKey: c.env.ODOO_API_KEY,
    });
  }
  return next();
});

app.get('/odoo/contacts/:id', async (c) => {
  const id = Number(c.req.param('id'));
  const [contacts, error] = await Try(() =>
    odoo.read<{ id: number; name: string; email: string }>('res.partner', id, ['name', 'email'])
  );
  if (error) return c.text(error.message, 422);
  if (!contacts.length) return c.text('not found', 404);
  return c.json(contacts[0], 200);
});

JSON-2(奥杜19+)

Odoo 19介绍了外部的JSON-2 API(POST /json/2/<model>/<method>,无记名 API 密钥)并贬值 /jsonrpc 财务报告和审定财务报表 /xmlrpc,哪个 Odoo 22 删除。通过 protocol: 'json2';其他一切不变.

const odoo = new OdooJSONRpc({
  baseUrl: 'https://my-odoo.example.com',
  db: 'my-db', // sent as X-Odoo-Database; optional on single-database servers
  apiKey: process.env.ODOO_API_KEY!,
  protocol: 'json2',
});

await odoo.connect(); // optional: checks the key and returns { uid }
const partners = await odoo.searchRead('res.partner', [['is_company', '=', true]], ['name'], { limit: 10 });
  • 没有登录步骤:每个请求都带有密钥. connect() 只是验证它(res.users.context_get)),而第一个调用单独连接.
  • 不需要用户名: 密钥识别用户 .
  • 所有帮助者( A)create, (中文(简体) ), read, (中文(简体) ), update, (中文(简体) ), delete, (中文(简体) ), search, (中文(简体) ), searchRead, (中文(简体) ), action, (中文(简体) ), getFields, (中文(简体) ), updateFieldTranslations,外部IDs)发送命名参数JSON-2预期.
  • 用于任何其他方法 call(model, method, params) 带有已命名参数: ids, (中文(简体) ), context,而且方法本身的论据.
await odoo.call('sale.order', 'action_confirm', { ids: [42] });
const count = await odoo.call<number>('res.partner', 'search_count', { domain: [['customer_rank', '>', 0]] });
  • call_kw(model, method, args, kwargs) 是立场。 在JSON-2上,它只在没有立场争论的情况下工作,或者只使用ID(英语:[[42]]); 任何其他抛出并指向您 call()。 。 。 .
  • call() 也从事JSON-RPC的研究工作(成为 execute_kw ,但参数名称跟随服务器:Odoo 17之前, search 带 args没有 domain。 。 。 .

API 密钥在 Odoo 下创建 首选项 — 账户安全 — 新建 API 密钥。对于非管理者,Odoo限制键的持续时间.

选项

选项默认含义
baseUrl—— 说例如服务器 URL. https://my-odoo.example.com
port—— 说可选文件:附件 baseUrl 当给定时
db—— 说数据库. JSON-RPC规定;JSON-2可选
protocol'jsonrpc''jsonrpc' 或者说 'json2'
timeoutMs无中止需要更长时间的任何请求; 它扔出一个 OdooError
username + 键 password 编号 : apiKey 编号 : sessionId—— 说全权证书(JSON-2仅使用) apiKey(中文(简体) )

API 参考

方法签署回返
connectconnect(config?)认证回复
disconnectdisconnect()boolean
callcall(model, method, params?)any (名称参数)
call_kwcall_kw(model, method, args, kwargs?)any (职位)
createcreate(model, values)number (编)
read<T>read(model, id | ids, fields)T[]
updateupdate(model, id, values)boolean
deletedelete(model, id)boolean
searchsearch(model, domain)number[]
searchRead<T>searchRead(model, domain, fields, opts?)T[]
getFieldsgetFields(model)字段映射
actionaction(model, action, ids)boolean
updateFieldTranslationsupdateFieldTranslations(model, id, field, translations)boolean
createExternalIdcreateExternalId(model, recordId, externalId, moduleName?)number
searchByExternalIdsearchByExternalId(externalId)number
readByExternalId<T>readByExternalId(externalId, fields?)T
updateByExternalIdupdateByExternalId(externalId, params)boolean
deleteByExternalIddeleteByExternalId(externalId)boolean

如果您需要的方法没有包起来, 请使用 call (名称参数)或 call_kw (职). 见 外部 API在Odoo 19+上, /doc 在服务器上记录每一个模型和方法.

拥挤

创建

const partnerId = await odoo.create('res.partner', { name: 'Kool Keith' });

要在创建时附加外部ID, 请打电话 createExternalId 后来 见 外部标识符。 。 。 .

读取

返回数组。 通过一个单一的ID或一系列的ID. 总是通过一个 fields 列表 — 基本模型( 如 res.partner)有100+字段.

type Partner = { id: number; name: string; email: string };
const records = await odoo.read<Partner>('res.partner', [54, 1568], ['name', 'email']);

更新

const ok = await odoo.update('res.partner', 54, { street: '334 Living Astro Blvd.' });

删除

const ok = await odoo.delete('res.partner', 54);

服务器动作

await odoo.action('account.move', 'action_post', [126996, 126995]);

Odoo 服务器动作通常返回 false 在成功方面.

搜索读取( S)

返回匹配的记录ID.

const ids = await odoo.search('res.partner', [['country_id', '=', 'US']]);

搜索读取

返回匹配选定字段的记录。 支助 offset, (中文(简体) ), limit, (中文(简体) ), order,以及 context。 。 。 .

type Partner = { id: number; name: string; city: string };
const records = await odoo.searchRead<Partner>(
  'res.partner',
  [['country_id', '=', 'US']],
  ['name', 'city'],
  { limit: 5, offset: 10, order: 'name desc', context: { lang: 'en_US' } }
);

// Empty domain = all records
const all = await odoo.searchRead<Partner>('res.partner', [], ['name', 'city'], { limit: 10 });

域过滤器

Odoo 域为 三重数组 [field, operator, value]。普通运算符: =, (中文(简体) ), !=, (中文(简体) ), <, (中文(简体) ), >, (中文(简体) ), <=, (中文(简体) ), >=, (中文(简体) ), like, (中文(简体) ), =like, (中文(简体) ), ilike, (中文(简体) ), in, (中文(简体) ), not in, (中文(简体) ), child_of。逻辑运算符: | (或), & (AND,隐含), ! (无).

完整列表 : Odoo ORM 域。 。 。 .

// Single triple
await odoo.search('res.partner', [['name', '=like', 'john%']]);

// Multiple triples (AND is implicit)
await odoo.search('res.partner', [
  ['name', '=like', 'john%'],
  ['sale_order_count', '>', 1],
]);

// OR: email = X OR name ilike Y
await odoo.searchRead('res.partner', [
  '|',
  ['email', '=', '[email protected]'],
  ['name', 'ilike', 'charlie'],
]);

关系场(多为2个/一为2个)

数值传递给奥多 原始内容。使用 Odoo 本地语言 命令调图数字 :

命令含义
[0, 0, {values}]创建新的相关记录并将其链接
[1, id, {values}]更新现有相关记录
[2, id]解开链接 删除 从 DB
[3, id]不删除而解链接
[4, id]链接现有记录
[5]全部取消链接( 无删除)
[6, 0, [ids]]用给定的ID替换链接设置

实例:

// Create a new related category on the fly
await odoo.update('res.partner', 278, {
  category_id: [[0, 0, { name: 'A new category' }]],
});

// Update an existing related category
await odoo.update('res.partner', 278, {
  category_id: [[1, 3, { name: 'Updated category' }]],
});

// Link existing categories (ids 3, 12, 6)
await odoo.update('res.partner', 278, {
  category_id: [[6, 0, [3, 12, 6]]],
});

// Unlink without deleting
await odoo.update('res.partner', 278, {
  category_id: [[3, 5]],
});

// Unlink and delete from DB
await odoo.update('res.partner', 278, {
  category_id: [[2, 5]],
});

外地翻译

更新跨语言的可变字段.

await odoo.updateFieldTranslations('product.template', 1, 'name', {
  de_DE: 'Neuer Name',
  en_GB: 'New name',
});

外部标识符

外部ID( 存储于 ir.model.data),让您通过一个稳定的跨系统的密钥参考记录——用于CSV的导入和第三方同步.

默认模块名称是 __api__因此,一个外部的ID喜欢 'sku-42' 变成 '__api__.sku-42' 在数据库中。 您可以使用 moduleName 参数 createExternalId。当你抬头看时,你会 没有 需要模块前缀.

// Link an external ID to an existing record
await odoo.createExternalId('product.product', 76, 'sku-42');

// Find id by external ID
const id = await odoo.searchByExternalId('sku-42');

// Read fields by external ID
const record = await odoo.readByExternalId<{ name: string; list_price: number }>(
  'sku-42',
  ['name', 'list_price']
);

// Update by external ID
await odoo.updateByExternalId('sku-42', { name: 'space shoe', list_price: 65479.99 });

// Delete by external ID
await odoo.deleteByExternalId('sku-42');

错误

每一次失败都会有 OdooError (扩展 Error:网络出错,超时出错,HTTP出错,回应不是JSON(一个代理的HTML页面),一个被拒绝的登录和由Odoo提出的出错.

import { OdooError } from '@fernandoslim/odoo-jsonrpc';

try {
  await odoo.action('sale.order', 'action_confirm', [42]);
} catch (error) {
  if (error instanceof OdooError) {
    error.message; // Odoo's message, e.g. "You cannot confirm an order without lines"
    error.status; // HTTP status (JSON-2 uses real ones: 401, 403, 404, 422…)
    error.exceptionName; // e.g. 'odoo.exceptions.UserError'
    error.model; // 'sale.order'
    error.method; // 'action_confirm'
  }
}

Odoo 拒绝的登录 connect() (地位401).

处理错误 Try

Try 完成承诺并返回 [result, null] | [null, error] 没有 try/catch 锅炉板.

import OdooJSONRpc, { Try } from '@fernandoslim/odoo-jsonrpc';

const [contacts, error] = await Try(() =>
  odoo.read<{ id: number; name: string }>('res.partner', 54, ['name'])
);
if (error) throw error;
if (!contacts.length) throw new Error('Contact not found');
const [contact] = contacts;

现实世界的例子——创建和确认销售令:

export const createSalesOrder = async (data: SalesOrder) => {
  const [id, createErr] = await Try(() => odoo.create('sale.order', data));
  if (createErr) throw createErr;

  const [, confirmErr] = await Try(() => odoo.action('sale.order', 'action_confirm', [id]));
  if (confirmErr) throw confirmErr;

  return id;
};

断开连接

在服务器上结束会话(证书/会话ID模式)。 拥有 API 密钥没有服务器会话: 它只清除本地状态 .

await odoo.disconnect();

类型脚本

所有出口都完全输入。 关键类型 :

import type {
  OdooConnection,
  OdooProtocol,
  OdooCallParams,
  ConnectionWithCredentials,
  ConnectionWithSession,
  OdooSearchDomain,
  OdooSearchReadOptions,
  OdooAuthenticateWithCredentialsResponse,
  OdooAuthenticateWithApiKeyResponse,
  UserContext,
  UserSettings,
} from '@fernandoslim/odoo-jsonrpc';

read<T> 财务报告和审定财务报表 searchRead<T> 接受行形状的通则,所以可以不进行铸造而输入记录.

基准

合成基准 浩诺 —— 说 hey -n 2000 -c 80数字 :

区/区丑闻
JSON-RPC 软件公司第617.11号122分钟
XML - RPC 相机352.98 (英语)213分钟

JSON-RPC每秒处理的请求增加~75%,运行速度平均为~43%.

更改日志

2.2.0 (中文(简体) )

  • JSON-2(奥杜19+)数字 : protocol: 'json2'无记名 API 键, X-Odoo-Database所有帮助者都致力于两个协议.
  • call(model, method, params) 带有命名参数.
  • OdooError 与 status, (中文(简体) ), model, (中文(简体) ), method 财务报告和审定财务报表 exceptionName 对每一个失败.
  • timeoutMs 选项.
  • port 可选 : baseUrl 也可以是满的 https://… URL (中文(简体) ).
  • 修正: Odoo 拒绝的登录没有丢任何东西(uid: false 计算为连接);它现在投出 connect()。 。 。 .
  • 修正: 错误被返回 /web/session/* 未检测到.
  • 修补 : action() 以若干 ids 作为单独参数发送它们; 它现在发送列表 .
  • 修补 : create() 财务报告和审定财务报表 createExternalId() 返回其类型表示数字的列表(一个数值列表仍然返回列表).
  • 修补 : disconnect() 带有一个 API 键被扔出; 现在它清除了当地的州.
  • 测试(Vites)针对一个能说出两个协议的默默奥杜语,加上对一个真实服务器的可选的只读检查(tests/live.test.ts) (中文(简体) ).

许可证

ISC——版权2024 Fernando Delgado. 全文见资料来源.

版本与发布记录

@fernandoslim/odoo-jsonrpc 共发布 26 个版本,首次发布于 2024/06/04;过去 12 个月发布了 2 个版本。

版本发布日期距今
2.2.0latest2026/10/081 个月前
2.1.02026/04/226 个月前
2.0.22024/09/252 年前
2.0.12024/09/252 年前
2.0.02024/08/262 年前
1.9.42024/08/162 年前

关于 @fernandoslim/odoo-jsonrpc 的常见问题

@fernandoslim/odoo-jsonrpc 是什么?

JSON-RPC和JSON-2 API(Odoo 19+)的轻量级Odoo客户端,零依赖.

如何安装 @fernandoslim/odoo-jsonrpc?

在项目目录运行 npm install @fernandoslim/odoo-jsonrpc,或使用 pnpm add / yarn add / bun add。

@fernandoslim/odoo-jsonrpc 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fernandoslim/odoo-jsonrpc 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@fernandoslim/odoo-jsonrpc 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@fernandoslim/odoo-jsonrpc 可以商用吗?

@fernandoslim/odoo-jsonrpc 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fernandoslim/odoo-jsonrpc 的下载量有多少?

根据 npm 官方下载统计,@fernandoslim/odoo-jsonrpc 在 2026/10/15 – 2026/11/13 这 30 天内被下载 19,812 次,日均约 660 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fernandoslim/odoo-jsonrpc 还在维护吗?

最新版本 2.2.0 发布于 2026/10/08(1 个月前);至今共发布 26 个版本,其中过去 12 个月发布了 2 个;首次发布于 2024/06/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。