奥多·杰森-RPC
JSON- RPC 和新的轻量级 Odoo 客户端 JSON-2 API(奥多19+)零依赖关系。 类型脚本第一. 在节点18+, Bun, Deno,和Cloudflare工人上工作.
依据 奥杜阿等 (XML-RPC). 感谢 维特洛法。 。 。 .
- 特征
- 安装
- 快速启动
- 认证
- JSON-2(奥杜19+)
- 选项
- API 参考
- 拥挤
- 搜索读取( S)
- 域过滤器
- 关系场(多为2个/一为2个)
- 外地翻译
- 外部标识符
- 错误
- 处理错误
Try - 断开连接
- 类型脚本
- 基准
- 更改日志
- 许可证
特征
- 零运行时间依赖关系
- 双重无害环境管理+共同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 参考
| 方法 | 签署 | 回返 |
|---|---|---|
connect | connect(config?) | 认证回复 |
disconnect | disconnect() | boolean |
call | call(model, method, params?) | any (名称参数) |
call_kw | call_kw(model, method, args, kwargs?) | any (职位) |
create | create(model, values) | number (编) |
read<T> | read(model, id | ids, fields) | T[] |
update | update(model, id, values) | boolean |
delete | delete(model, id) | boolean |
search | search(model, domain) | number[] |
searchRead<T> | searchRead(model, domain, fields, opts?) | T[] |
getFields | getFields(model) | 字段映射 |
action | action(model, action, ids) | boolean |
updateFieldTranslations | updateFieldTranslations(model, id, field, translations) | boolean |
createExternalId | createExternalId(model, recordId, externalId, moduleName?) | number |
searchByExternalId | searchByExternalId(externalId) | number |
readByExternalId<T> | readByExternalId(externalId, fields?) | T |
updateByExternalId | updateByExternalId(externalId, params) | boolean |
deleteByExternalId | deleteByExternalId(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. 全文见资料来源.