@hodeinavarro/odoo-rpc-ts 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/client.d.ts +184 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +277 -0
- package/dist/client.js.map +1 -0
- package/dist/combinators/retryOnSessionExpired.d.ts +13 -0
- package/dist/combinators/retryOnSessionExpired.d.ts.map +1 -0
- package/dist/combinators/retryOnSessionExpired.js +15 -0
- package/dist/combinators/retryOnSessionExpired.js.map +1 -0
- package/dist/commands.d.ts +91 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +69 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +60 -0
- package/dist/config.js.map +1 -0
- package/dist/domain.d.ts +30 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +70 -0
- package/dist/domain.js.map +1 -0
- package/dist/errors/auth.d.ts +27 -0
- package/dist/errors/auth.d.ts.map +1 -0
- package/dist/errors/auth.js +8 -0
- package/dist/errors/auth.js.map +1 -0
- package/dist/errors/index.d.ts +30 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +13 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/mapFault.d.ts +31 -0
- package/dist/errors/mapFault.d.ts.map +1 -0
- package/dist/errors/mapFault.js +48 -0
- package/dist/errors/mapFault.js.map +1 -0
- package/dist/errors/protocol.d.ts +22 -0
- package/dist/errors/protocol.d.ts.map +1 -0
- package/dist/errors/protocol.js +9 -0
- package/dist/errors/protocol.js.map +1 -0
- package/dist/errors/schema.d.ts +22 -0
- package/dist/errors/schema.d.ts.map +1 -0
- package/dist/errors/schema.js +14 -0
- package/dist/errors/schema.js.map +1 -0
- package/dist/errors/server.d.ts +109 -0
- package/dist/errors/server.d.ts.map +1 -0
- package/dist/errors/server.js +49 -0
- package/dist/errors/server.js.map +1 -0
- package/dist/errors/session.d.ts +16 -0
- package/dist/errors/session.d.ts.map +1 -0
- package/dist/errors/session.js +11 -0
- package/dist/errors/session.js.map +1 -0
- package/dist/errors/transport.d.ts +42 -0
- package/dist/errors/transport.d.ts.map +1 -0
- package/dist/errors/transport.js +49 -0
- package/dist/errors/transport.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/platform.d.ts +12 -0
- package/dist/internal/platform.d.ts.map +1 -0
- package/dist/internal/platform.js +11 -0
- package/dist/internal/platform.js.map +1 -0
- package/dist/internal/singleFlight.d.ts +29 -0
- package/dist/internal/singleFlight.d.ts.map +1 -0
- package/dist/internal/singleFlight.js +41 -0
- package/dist/internal/singleFlight.js.map +1 -0
- package/dist/profiles.d.ts +179 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +191 -0
- package/dist/profiles.js.map +1 -0
- package/dist/protocol/jsonrpc.d.ts +127 -0
- package/dist/protocol/jsonrpc.d.ts.map +1 -0
- package/dist/protocol/jsonrpc.js +117 -0
- package/dist/protocol/jsonrpc.js.map +1 -0
- package/dist/records/index.d.ts +14 -0
- package/dist/records/index.d.ts.map +1 -0
- package/dist/records/index.js +14 -0
- package/dist/records/index.js.map +1 -0
- package/dist/records/recordModel.d.ts +131 -0
- package/dist/records/recordModel.d.ts.map +1 -0
- package/dist/records/recordModel.js +101 -0
- package/dist/records/recordModel.js.map +1 -0
- package/dist/records/related.d.ts +42 -0
- package/dist/records/related.d.ts.map +1 -0
- package/dist/records/related.js +51 -0
- package/dist/records/related.js.map +1 -0
- package/dist/records/relations.d.ts +66 -0
- package/dist/records/relations.d.ts.map +1 -0
- package/dist/records/relations.js +119 -0
- package/dist/records/relations.js.map +1 -0
- package/dist/records/spec.d.ts +44 -0
- package/dist/records/spec.d.ts.map +1 -0
- package/dist/records/spec.js +63 -0
- package/dist/records/spec.js.map +1 -0
- package/dist/records/typed.d.ts +71 -0
- package/dist/records/typed.d.ts.map +1 -0
- package/dist/records/typed.js +65 -0
- package/dist/records/typed.js.map +1 -0
- package/dist/rpc.d.ts +90 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +113 -0
- package/dist/rpc.js.map +1 -0
- package/dist/services/db.d.ts +128 -0
- package/dist/services/db.d.ts.map +1 -0
- package/dist/services/db.js +132 -0
- package/dist/services/db.js.map +1 -0
- package/dist/services/report.d.ts +48 -0
- package/dist/services/report.d.ts.map +1 -0
- package/dist/services/report.js +103 -0
- package/dist/services/report.js.map +1 -0
- package/dist/session/cookie.d.ts +114 -0
- package/dist/session/cookie.d.ts.map +1 -0
- package/dist/session/cookie.js +231 -0
- package/dist/session/cookie.js.map +1 -0
- package/dist/testing/fakeTransport.d.ts +40 -0
- package/dist/testing/fakeTransport.d.ts.map +1 -0
- package/dist/testing/fakeTransport.js +36 -0
- package/dist/testing/fakeTransport.js.map +1 -0
- package/dist/testing/index.d.ts +9 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +9 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/transport.d.ts +62 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +9 -0
- package/dist/transport.js.map +1 -0
- package/dist/transports/json2.d.ts +41 -0
- package/dist/transports/json2.d.ts.map +1 -0
- package/dist/transports/json2.js +196 -0
- package/dist/transports/json2.js.map +1 -0
- package/dist/transports/jsonrpc.d.ts +41 -0
- package/dist/transports/jsonrpc.d.ts.map +1 -0
- package/dist/transports/jsonrpc.js +119 -0
- package/dist/transports/jsonrpc.js.map +1 -0
- package/dist/transports/web.d.ts +15 -0
- package/dist/transports/web.d.ts.map +1 -0
- package/dist/transports/web.js +62 -0
- package/dist/transports/web.js.map +1 -0
- package/dist/version-live.d.ts +41 -0
- package/dist/version-live.d.ts.map +1 -0
- package/dist/version-live.js +26 -0
- package/dist/version-live.js.map +1 -0
- package/dist/version.d.ts +78 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +46 -0
- package/dist/version.js.map +1 -0
- package/docs/protocol-verification.md +43 -0
- package/package.json +71 -0
- package/src/client.ts +731 -0
- package/src/combinators/retryOnSessionExpired.ts +25 -0
- package/src/commands.ts +112 -0
- package/src/config.ts +110 -0
- package/src/domain.ts +91 -0
- package/src/errors/auth.ts +28 -0
- package/src/errors/index.ts +50 -0
- package/src/errors/mapFault.ts +92 -0
- package/src/errors/protocol.ts +19 -0
- package/src/errors/schema.ts +18 -0
- package/src/errors/server.ts +79 -0
- package/src/errors/session.ts +12 -0
- package/src/errors/transport.ts +74 -0
- package/src/index.ts +152 -0
- package/src/internal/platform.ts +22 -0
- package/src/internal/singleFlight.ts +80 -0
- package/src/profiles.ts +386 -0
- package/src/protocol/jsonrpc.ts +162 -0
- package/src/records/index.ts +49 -0
- package/src/records/recordModel.ts +211 -0
- package/src/records/related.ts +80 -0
- package/src/records/relations.ts +192 -0
- package/src/records/spec.ts +96 -0
- package/src/records/typed.ts +180 -0
- package/src/rpc.ts +219 -0
- package/src/services/db.ts +282 -0
- package/src/services/report.ts +165 -0
- package/src/session/cookie.ts +461 -0
- package/src/testing/fakeTransport.ts +84 -0
- package/src/testing/index.ts +9 -0
- package/src/transport.ts +72 -0
- package/src/transports/json2.ts +303 -0
- package/src/transports/jsonrpc.ts +218 -0
- package/src/transports/web.ts +96 -0
- package/src/version-live.ts +67 -0
- package/src/version.ts +102 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hodei Navarro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# @hodeinavarro/odoo-rpc-ts
|
|
2
|
+
|
|
3
|
+
Effect-native, strongly-typed Odoo RPC client for TypeScript. **Odoo 16–19**,
|
|
4
|
+
three wire protocols behind one seam — JSON-RPC, the new JSON-2 API (19+),
|
|
5
|
+
and the cookie-session web route — with a tagged-error taxonomy you can
|
|
6
|
+
`catchTag` instead of parsing messages.
|
|
7
|
+
|
|
8
|
+
> **Status:** public and pre-1.0. Releases are published to the public npm
|
|
9
|
+
> registry with provenance.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
This package owns generic Odoo transport, authentication/session, schema
|
|
14
|
+
decoding, typed records, connection profiles, and a small set of reusable
|
|
15
|
+
services. It does not own application-specific models, UI, persistence,
|
|
16
|
+
workflow policy, or automatic protocol selection. XML-RPC and generated
|
|
17
|
+
per-model clients are deliberately out of scope.
|
|
18
|
+
|
|
19
|
+
Install it from the public npm registry with:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
pnpm add @hodeinavarro/odoo-rpc-ts effect@4.0.0-beta.93
|
|
23
|
+
pnpm add @effect/platform-node@4.0.0-beta.93 # or run in the browser with FetchHttpClient
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Effect 4 is in beta and moves between betas — pin it **exact** (no caret) and
|
|
27
|
+
upgrade deliberately.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Set `ODOO_URL`, `ODOO_DB`, `ODOO_USERNAME`, and `ODOO_API_KEY` (or
|
|
32
|
+
`ODOO_PASSWORD`), then:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Effect, Layer, Schema } from "effect";
|
|
36
|
+
import { NodeHttpClient } from "@effect/platform-node";
|
|
37
|
+
import { JsonRpcTransport, OdooClient, OdooClientLive, RpcLive } from "@hodeinavarro/odoo-rpc-ts";
|
|
38
|
+
|
|
39
|
+
// One layer graph: client → rpc → transport → your HTTP runtime.
|
|
40
|
+
const OdooLive = OdooClientLive.layer.pipe(
|
|
41
|
+
Layer.provideMerge(RpcLive.layer),
|
|
42
|
+
Layer.provide(JsonRpcTransport.layerConfig),
|
|
43
|
+
Layer.provide(NodeHttpClient.layerUndici),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Rows decode through a schema — drift fails loudly, never a silent cast.
|
|
47
|
+
const Partner = Schema.Struct({
|
|
48
|
+
id: Schema.Number,
|
|
49
|
+
name: Schema.String,
|
|
50
|
+
email: Schema.Union([Schema.String, Schema.Literal(false)]), // Odoo sends false, not null
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const companies = Effect.gen(function* () {
|
|
54
|
+
const odoo = yield* OdooClient;
|
|
55
|
+
return yield* odoo.searchRead(
|
|
56
|
+
"res.partner",
|
|
57
|
+
{ domain: [["is_company", "=", true]], fields: ["id", "name", "email"], limit: 10 },
|
|
58
|
+
Partner,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await companies.pipe(Effect.provide(OdooLive), Effect.runPromise);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`OdooClient` gives you `searchRead`, `search`, `read`, `create`, `write`,
|
|
66
|
+
`unlink`, `fieldsGet`, `searchCount`, `readGroup`, `nameSearch`, `ref`
|
|
67
|
+
(resolve an XML id to `[model, id]`), and a generic `call` escape hatch —
|
|
68
|
+
every op works over every transport. `Rpc.callKw` sits underneath for
|
|
69
|
+
anything else. `RpcLive.layer` seeds the user's server-side context
|
|
70
|
+
(`lang`, `tz` — via `res.users.context_get`, lazily on the first call) as
|
|
71
|
+
the base tier of every call, so translated fields come back in the user's
|
|
72
|
+
language; use `RpcLive.layerWith()` to opt out of seeding.
|
|
73
|
+
|
|
74
|
+
## Everyday workflows
|
|
75
|
+
|
|
76
|
+
**Find or create:**
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const findOrCreatePartner = (email: string, name: string) =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const odoo = yield* OdooClient;
|
|
82
|
+
const existing = yield* odoo.search("res.partner", {
|
|
83
|
+
domain: [["email", "=", email]],
|
|
84
|
+
limit: 1,
|
|
85
|
+
});
|
|
86
|
+
if (existing.length > 0) return existing[0]!;
|
|
87
|
+
const [id] = yield* odoo.create("res.partner", { name, email });
|
|
88
|
+
return id!;
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Bulk update with domain combinators** (`AND` / `OR` / `NOT`):
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { AND, OR } from "@hodeinavarro/odoo-rpc-ts";
|
|
96
|
+
|
|
97
|
+
const archiveStaleLeads = Effect.gen(function* () {
|
|
98
|
+
const odoo = yield* OdooClient;
|
|
99
|
+
const stale = yield* odoo.search("crm.lead", {
|
|
100
|
+
domain: AND(
|
|
101
|
+
[["active", "=", true]],
|
|
102
|
+
OR([["probability", "=", 0]], [["write_date", "<", "2025-01-01"]]),
|
|
103
|
+
),
|
|
104
|
+
});
|
|
105
|
+
if (stale.length === 0) return 0;
|
|
106
|
+
yield* odoo.write("crm.lead", stale, { active: false });
|
|
107
|
+
return stale.length;
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Paginate a big model:**
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const allPartnerIds = Effect.gen(function* () {
|
|
115
|
+
const odoo = yield* OdooClient;
|
|
116
|
+
const out: number[] = [];
|
|
117
|
+
for (let offset = 0; ; offset += 500) {
|
|
118
|
+
const page = yield* odoo.search("res.partner", { limit: 500, offset });
|
|
119
|
+
out.push(...page);
|
|
120
|
+
if (page.length < 500) return out;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Typed records and relations
|
|
126
|
+
|
|
127
|
+
Two per-protocol tiers — dialects of the same design, not competitors. Both
|
|
128
|
+
make N+1 storms structurally impossible, and every RPC stays visible. The
|
|
129
|
+
**spec tier** speaks the `specification` protocol Odoo grew in 17; the
|
|
130
|
+
**classic tier** speaks the classic `[id, name]`-pair protocol every supported
|
|
131
|
+
version (16+) understands. Pick by the oldest server you must reach.
|
|
132
|
+
|
|
133
|
+
**Spec tier — declared prefetch** (Odoo 17+ `specification` protocol) —
|
|
134
|
+
declare the graph, get ONE `web_search_read`:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { defineRecord, Many2One, OdooClient, OdooDateTime } from "@hodeinavarro/odoo-rpc-ts"
|
|
138
|
+
import { Schema } from "effect"
|
|
139
|
+
|
|
140
|
+
const Company = defineRecord("res.company", { name: Schema.String })
|
|
141
|
+
const Partner = defineRecord("res.partner", {
|
|
142
|
+
name: Schema.String,
|
|
143
|
+
create_date: OdooDateTime, // parsed as UTC, never localized
|
|
144
|
+
company_id: Many2One(Company), // row.company_id?.name — already fetched
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const rows = Effect.gen(function* () {
|
|
148
|
+
const odoo = yield* OdooClient
|
|
149
|
+
return yield* odoo.searchTyped(Partner, { domain: [["is_company", "=", true]], limit: 10 })
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The version gate is explicit: the spec tier's relations REQUIRE Odoo 17+.
|
|
154
|
+
Provide a `VersionResolver` layer and the gate holds automatically — a
|
|
155
|
+
declared relation on Odoo 16 fails with `ProtocolUnsupportedError` before any
|
|
156
|
+
round trip, while relation-free records degrade to plain `search_read`.
|
|
157
|
+
`saveTyped` (17+) writes and returns the fresh nested snapshot in one call.
|
|
158
|
+
For anything that must also run on 16, use the classic tier below.
|
|
159
|
+
|
|
160
|
+
**Classic tier — explicit traversal** (all versions, 16+) — rows keep the
|
|
161
|
+
classic `[id, name]` refs; batch the hop when you need it, exactly one deduped
|
|
162
|
+
`read`:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { Many2OneRefOrNull, OdooClient } from "@hodeinavarro/odoo-rpc-ts"
|
|
166
|
+
|
|
167
|
+
const PartnerRow = Schema.Struct({
|
|
168
|
+
id: Schema.Number,
|
|
169
|
+
name: Schema.String,
|
|
170
|
+
company_id: Many2OneRefOrNull, // Odoo's [id, name] pair; false -> null
|
|
171
|
+
})
|
|
172
|
+
const CompanyRow = Schema.Struct({ id: Schema.Number, name: Schema.String })
|
|
173
|
+
|
|
174
|
+
const pairs = Effect.gen(function* () {
|
|
175
|
+
const odoo = yield* OdooClient
|
|
176
|
+
const rows = yield* odoo.searchRecordsTyped("res.partner", {}, PartnerRow)
|
|
177
|
+
return yield* rows.joinRelated("company_id", "res.company", CompanyRow) // ONE read
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## x2many writes without magic tuples
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { Command } from "@hodeinavarro/odoo-rpc-ts"
|
|
185
|
+
|
|
186
|
+
yield* odoo.write("res.partner", [companyId], {
|
|
187
|
+
child_ids: [Command.create({ name: "New contact" }), Command.link(existingId)],
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Services: databases, reports, profiles
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { DbService, ReportService } from "@hodeinavarro/odoo-rpc-ts"
|
|
195
|
+
|
|
196
|
+
// database administration (master-password gated; Node-oriented)
|
|
197
|
+
const names = yield* DbService.listDatabases(url)
|
|
198
|
+
yield* DbService.duplicate(url, master, "prod", "staging")
|
|
199
|
+
|
|
200
|
+
// report downloads over the cookie session — works on ALL of 16-19
|
|
201
|
+
const pdf = yield* ReportService.download(session, {
|
|
202
|
+
reportName: "base.report_irmodeloverview",
|
|
203
|
+
ids: [modelId],
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Connection profiles never serialize secrets: `ProfileData` holds the metadata,
|
|
208
|
+
secrets cross only as `Redacted` through a `SecretStore` you implement (OS
|
|
209
|
+
keyring in Node, IndexedDB in the browser); in-memory layers ship for tests.
|
|
210
|
+
Besides api-key/password presets, `saveSessionProfile(name, url, sessionId)`
|
|
211
|
+
stores a harvested `session_id` cookie (the TOTP/SSO flow — no credentials
|
|
212
|
+
exist); `loadProfile` returns a tagged union, and its `"session"` arm feeds
|
|
213
|
+
`CookieSessionLive.fromExisting` directly instead of fabricating a config.
|
|
214
|
+
|
|
215
|
+
## Bring your own HTTP layer
|
|
216
|
+
|
|
217
|
+
The library depends only on the abstract `HttpClient` tag — timeouts, proxies,
|
|
218
|
+
custom CAs, and retry policy are yours:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { FetchHttpClient, HttpClient } from "effect/unstable/http"
|
|
222
|
+
import { Layer } from "effect"
|
|
223
|
+
|
|
224
|
+
const TunedHttp = Layer.effect(
|
|
225
|
+
HttpClient.HttpClient,
|
|
226
|
+
Effect.map(HttpClient.HttpClient, (client) => client.pipe(HttpClient.retryTransient({ times: 3 }))),
|
|
227
|
+
).pipe(Layer.provide(FetchHttpClient.layer))
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Errors are a typed API
|
|
231
|
+
|
|
232
|
+
Every failure is a tagged error carrying the raw fault, the Python exception
|
|
233
|
+
name, and the call site — normalized identically across all three protocols:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const created = Effect.gen(function* () {
|
|
237
|
+
const odoo = yield* OdooClient;
|
|
238
|
+
return yield* odoo.create("res.partner", { name: "Ada" });
|
|
239
|
+
}).pipe(
|
|
240
|
+
Effect.catchTags({
|
|
241
|
+
OdooValidationError: (e) => Effect.die(`rejected by a constraint: ${e.message}`),
|
|
242
|
+
OdooAccessError: () => Effect.succeed([]), // degrade gracefully
|
|
243
|
+
OdooAuthenticationError: (e) => Effect.die(e.reason), // "invalid-credentials" | "mfa-pending" | …
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The full union: `OdooTransportError` · `OdooAuthenticationError` ·
|
|
249
|
+
`SessionExpiredError` · `SchemaDriftError` · `ProtocolUnsupportedError` ·
|
|
250
|
+
`OdooServerError` (+ `OdooAccessError`, `OdooValidationError`,
|
|
251
|
+
`OdooMissingError`, `OdooUserError`, `OdooLockError`). Unknown server faults
|
|
252
|
+
are preserved on `OdooServerError` with the raw name — never swallowed.
|
|
253
|
+
`OdooTransportError` retains only a sanitized `{ method, url }` request,
|
|
254
|
+
failure `kind`, safe message, and optional HTTP status; it never exposes the
|
|
255
|
+
platform request, response, headers, body, or cause.
|
|
256
|
+
|
|
257
|
+
## Pick your transport
|
|
258
|
+
|
|
259
|
+
Same application code, different layer — always your explicit choice:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
// Server-to-server, stateless: /jsonrpc + execute_kw (Odoo 16–19)
|
|
263
|
+
Layer.provide(JsonRpcTransport.layerConfig);
|
|
264
|
+
|
|
265
|
+
// Odoo 19+: /json/2, bearer API key, real HTTP status codes
|
|
266
|
+
Layer.provide(Json2Transport.layerConfig);
|
|
267
|
+
|
|
268
|
+
// Browser/long-lived: cookie session over /web/dataset/call_kw
|
|
269
|
+
Layer.provide(WebTransport.layerConfig).pipe(Layer.provideMerge(CookieSessionLive.layerConfig));
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Cookie sessions expire; recovery is opt-in, never magic:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { CookieSession, retryOnSessionExpired } from "@hodeinavarro/odoo-rpc-ts";
|
|
276
|
+
|
|
277
|
+
const resilientCount = Effect.gen(function* () {
|
|
278
|
+
const session = yield* CookieSession;
|
|
279
|
+
const odoo = yield* OdooClient;
|
|
280
|
+
return yield* retryOnSessionExpired(odoo.searchCount("res.partner", []), session);
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Already hold a `session_id` minted elsewhere — e.g. harvested from an
|
|
285
|
+
embedded browser window after the user completed the real `/web/login` page
|
|
286
|
+
(the only stock flow for TOTP/SSO accounts)? Adopt it directly; the client
|
|
287
|
+
never holds credentials:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { Redacted } from "effect";
|
|
291
|
+
import { CookieSessionLive, WebTransport } from "@hodeinavarro/odoo-rpc-ts";
|
|
292
|
+
|
|
293
|
+
const url = new URL("https://erp.example.com");
|
|
294
|
+
|
|
295
|
+
const HarvestedOdoo = OdooClientLive.layer.pipe(
|
|
296
|
+
Layer.provideMerge(RpcLive.layer),
|
|
297
|
+
Layer.provide(WebTransport.layer({ url })), // no credentials needed
|
|
298
|
+
Layer.provide(
|
|
299
|
+
CookieSessionLive.layerFromExisting({
|
|
300
|
+
url,
|
|
301
|
+
sessionId: Redacted.make(harvestedCookieValue),
|
|
302
|
+
// Optional renew hook: mint a fresh cookie after an invalidate (e.g.
|
|
303
|
+
// reopen the login window). Without it the session cannot recover —
|
|
304
|
+
// once the server kills it, calls (and the one relogin attempted by
|
|
305
|
+
// retryOnSessionExpired) fail with SessionExpiredError, and your shell
|
|
306
|
+
// constructs a new session from a fresh cookie.
|
|
307
|
+
// renew: driveLoginWindow,
|
|
308
|
+
}),
|
|
309
|
+
),
|
|
310
|
+
Layer.provide(NodeHttpClient.layerUndici),
|
|
311
|
+
);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The injected session hydrates honestly on first use via
|
|
315
|
+
`POST /web/session/get_session_info`, so `login` still yields the real
|
|
316
|
+
`uid`/context/version — and a dead cookie surfaces immediately as
|
|
317
|
+
`SessionExpiredError`.
|
|
318
|
+
|
|
319
|
+
Notes: API keys work as the password on every route (and are _required_ for
|
|
320
|
+
accounts with 2FA). JSON-2 is bearer-only and keyword-only — the client
|
|
321
|
+
handles the encoding differences for you via the transport's `dialect`.
|
|
322
|
+
|
|
323
|
+
## Testing without a server
|
|
324
|
+
|
|
325
|
+
`@hodeinavarro/odoo-rpc-ts/testing` ships a deterministic `FakeTransport` with
|
|
326
|
+
a call log:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import * as FakeTransport from "@hodeinavarro/odoo-rpc-ts/testing";
|
|
330
|
+
|
|
331
|
+
const fake = FakeTransport.make({
|
|
332
|
+
"res.partner": {
|
|
333
|
+
search_read: () => [{ id: 1, name: "Alice", email: "alice@example.com" }],
|
|
334
|
+
create: (params) => (params.args[0] as unknown[]).map((_, i) => 100 + i),
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const TestLayer = OdooClientLive.layer.pipe(
|
|
339
|
+
Layer.provideMerge(RpcLive.layer),
|
|
340
|
+
Layer.provide(fake.layer),
|
|
341
|
+
);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Config is `effect/Config`, so tests can inject values without touching env:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import { ConfigProvider } from "effect";
|
|
348
|
+
|
|
349
|
+
const TestConfig = ConfigProvider.layer(
|
|
350
|
+
ConfigProvider.fromEnv({
|
|
351
|
+
env: {
|
|
352
|
+
ODOO_URL: "https://mycompany.odoo.com",
|
|
353
|
+
ODOO_DB: "mycompany",
|
|
354
|
+
ODOO_USERNAME: "integration@mycompany.com",
|
|
355
|
+
ODOO_API_KEY: "…",
|
|
356
|
+
},
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Want the real thing? `pnpm harness up 16.0` (or 17/18/19) boots a disposable
|
|
362
|
+
seeded Odoo in Docker and `pnpm test:integration` runs the live suite against
|
|
363
|
+
it — see [harness/README.md](harness/README.md).
|
|
364
|
+
|
|
365
|
+
## Support matrix
|
|
366
|
+
|
|
367
|
+
| | 16.0 | 17.0 | 18.0 | 19.0 |
|
|
368
|
+
| -------------------------- | ---- | ---- | ---- | ----------------------- |
|
|
369
|
+
| JSON-RPC `/jsonrpc` | ✓ | ✓ | ✓ | ✓ (deprecated upstream) |
|
|
370
|
+
| Web session + `call_kw` | ✓ | ✓ | ✓ | ✓ |
|
|
371
|
+
| JSON-2 `/json/2` (bearer) | – | – | – | ✓ |
|
|
372
|
+
| API key auth (as password) | ✓ | ✓ | ✓ | ✓ |
|
|
373
|
+
|
|
374
|
+
XML-RPC is deliberately out of scope — JSON-RPC reaches the same services on
|
|
375
|
+
every supported version with strictly richer errors.
|
|
376
|
+
|
|
377
|
+
## Design, in one paragraph
|
|
378
|
+
|
|
379
|
+
Everything returns `Effect<A, OdooError, R>` — nothing throws. Services are
|
|
380
|
+
`Context.Service`s wired with `Layer`s; the HTTP runtime is yours (`effect` —
|
|
381
|
+
v4, incl. `effect/unstable/http` — is the only peer dependency, so it runs
|
|
382
|
+
wherever Effect does, browser included). Responses are schema-decoded at the boundary
|
|
383
|
+
(`SchemaDriftError` on drift), secrets stay `Redacted`, TLS is never
|
|
384
|
+
disabled, and no URL/db/credential is ever assumed. The wire behavior was
|
|
385
|
+
mapped from pinned Odoo 16–19 source snapshots and exercised against disposable
|
|
386
|
+
instances. See the reproducible
|
|
387
|
+
[protocol verification record](docs/protocol-verification.md) for the exact
|
|
388
|
+
upstream revisions and source paths; architectural decisions live in
|
|
389
|
+
[AGENTS.md](AGENTS.md).
|
|
390
|
+
|
|
391
|
+
Every example in this README is compile-checked in CI
|
|
392
|
+
([test/readme-snippets.test-d.ts](test/readme-snippets.test-d.ts)).
|
|
393
|
+
|
|
394
|
+
## Status
|
|
395
|
+
|
|
396
|
+
🚧 Pre-1.0. Core is implemented, the unit suite covers the shared API, and CI
|
|
397
|
+
runs the integration suite against Odoo 16, 17, 18, and 19. The API may still
|
|
398
|
+
move while Effect 4 remains in beta.
|
|
399
|
+
|
|
400
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the local gates and
|
|
401
|
+
[SECURITY.md](SECURITY.md) for private vulnerability reporting.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Schema } from "effect";
|
|
2
|
+
import type { Domain } from "./domain.js";
|
|
3
|
+
import { type HasId, type RecordSpec, type TypedRecordSet } from "./records/index.js";
|
|
4
|
+
import { type OdooContext, Rpc } from "./rpc.js";
|
|
5
|
+
import type { TransportCallError } from "./transport.js";
|
|
6
|
+
/** A decoded Odoo record with unmodeled field values. */
|
|
7
|
+
export type OdooRecord = {
|
|
8
|
+
readonly [field: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
/** `fields_get` metadata: field name → its attribute dict. */
|
|
11
|
+
export type FieldsMetadata = {
|
|
12
|
+
readonly [field: string]: OdooRecord;
|
|
13
|
+
};
|
|
14
|
+
/** Options for {@link OdooClient.call} — the undecoded model-method escape hatch. */
|
|
15
|
+
export interface CallOptions {
|
|
16
|
+
readonly ids?: ReadonlyArray<number>;
|
|
17
|
+
readonly context?: OdooContext;
|
|
18
|
+
readonly kwargs?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/** Arguments for `read_group` (all Python-stable parameter names, 16–19). */
|
|
21
|
+
export interface ReadGroupOptions {
|
|
22
|
+
readonly domain: Domain;
|
|
23
|
+
readonly fields: ReadonlyArray<string>;
|
|
24
|
+
readonly groupby: ReadonlyArray<string>;
|
|
25
|
+
readonly limit?: number;
|
|
26
|
+
readonly offset?: number;
|
|
27
|
+
readonly orderby?: string;
|
|
28
|
+
readonly lazy?: boolean;
|
|
29
|
+
readonly context?: OdooContext;
|
|
30
|
+
}
|
|
31
|
+
/** Arguments for `name_search`. `args` is the optional search domain. */
|
|
32
|
+
export interface NameSearchOptions {
|
|
33
|
+
readonly name?: string;
|
|
34
|
+
readonly args?: Domain;
|
|
35
|
+
readonly operator?: string;
|
|
36
|
+
readonly limit?: number;
|
|
37
|
+
readonly context?: OdooContext;
|
|
38
|
+
}
|
|
39
|
+
/** Options shared by the record-listing ops. `context` merges via {@link Rpc}. */
|
|
40
|
+
export interface SearchOptions {
|
|
41
|
+
readonly domain?: Domain;
|
|
42
|
+
readonly limit?: number;
|
|
43
|
+
readonly offset?: number;
|
|
44
|
+
readonly order?: string;
|
|
45
|
+
readonly context?: OdooContext;
|
|
46
|
+
}
|
|
47
|
+
export interface SearchReadOptions extends SearchOptions {
|
|
48
|
+
readonly fields?: ReadonlyArray<string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Options for the declared-prefetch typed reads ({@link OdooClient.searchTyped}).
|
|
52
|
+
* `serverMajor` is the explicit version-gate override used when NO
|
|
53
|
+
* {@link VersionResolver} is in scope: it decides whether the `specification`
|
|
54
|
+
* path is taken (>= 17) or, for a relation-free spec, degrades to `search_read`
|
|
55
|
+
* (< 17). When a `VersionResolver` IS provided it always wins and `serverMajor`
|
|
56
|
+
* is ignored.
|
|
57
|
+
*/
|
|
58
|
+
export interface TypedOptions extends SearchOptions {
|
|
59
|
+
readonly serverMajor?: number;
|
|
60
|
+
}
|
|
61
|
+
/** Options for {@link OdooClient.readTyped}/`saveTyped` — context + the same
|
|
62
|
+
* explicit `serverMajor` gate override as {@link TypedOptions}. */
|
|
63
|
+
export interface TypedReadOptions {
|
|
64
|
+
readonly context?: OdooContext;
|
|
65
|
+
readonly serverMajor?: number;
|
|
66
|
+
}
|
|
67
|
+
declare const OdooClient_base: Context.ServiceClass<OdooClient, "odoo-rpc-ts/OdooClient", {
|
|
68
|
+
/**
|
|
69
|
+
* `search_read(domain, fields, offset, limit, order)` — search and read in
|
|
70
|
+
* one round trip. Rows decode through `schema` (default: unknown record).
|
|
71
|
+
*/
|
|
72
|
+
readonly searchRead: <A = OdooRecord, I = A>(model: string, options?: SearchReadOptions, schema?: Schema.Codec<A, I>) => Effect.Effect<ReadonlyArray<A>, TransportCallError>;
|
|
73
|
+
/**
|
|
74
|
+
* `search_read`, decoding rows through `schema`, into a {@link TypedRecordSet}
|
|
75
|
+
* — an immutable snapshot bound to `(rpc, model)` with explicit, batched
|
|
76
|
+
* relation traversal (`fetchRelated`/`joinRelated`). The schema MUST decode
|
|
77
|
+
* an `id: number` on every row (`A extends { id: number }`); that id keys all
|
|
78
|
+
* downstream joins. This is the classic-tier ("explicit traversal",
|
|
79
|
+
* `[id, name]`-pair protocol, works on 16+) entry
|
|
80
|
+
* point — one `search_read` here, then one `read` per relation you traverse.
|
|
81
|
+
*/
|
|
82
|
+
readonly searchRecordsTyped: <A extends HasId, I = A>(model: string, options: SearchReadOptions | undefined, schema: Schema.Codec<A, I>) => Effect.Effect<TypedRecordSet<A>, TransportCallError>;
|
|
83
|
+
/**
|
|
84
|
+
* Spec-tier typed search (declared prefetch, 17+ `specification`
|
|
85
|
+
* protocol). Compiles `record`'s field
|
|
86
|
+
* graph into ONE `web_search_read` `specification` call and strict-decodes
|
|
87
|
+
* the nested payload — a declared many2one comes back as a `{ id, ... }` dict
|
|
88
|
+
* (or `null`), an x2many as a nested list — with NO per-relation round trip.
|
|
89
|
+
*
|
|
90
|
+
* VERSION GATE (before any round trip). If a {@link VersionResolver} is in
|
|
91
|
+
* scope it decides; else `options.serverMajor` does; else the spec path is
|
|
92
|
+
* assumed (17+). A declared relation on a < 17 server fails with
|
|
93
|
+
* {@link ProtocolUnsupportedError}; a relation-free declared model degrades to
|
|
94
|
+
* a plain `search_read` (one trip, identical decode).
|
|
95
|
+
*/
|
|
96
|
+
readonly searchTyped: <A extends HasId, I = A>(record: RecordSpec<A, I>, options?: TypedOptions) => Effect.Effect<ReadonlyArray<A>, TransportCallError>;
|
|
97
|
+
/**
|
|
98
|
+
* Declared-prefetch typed read by ids via `web_read` (17+). Same nested
|
|
99
|
+
* decode and version gate as {@link searchTyped}; degrades to plain `read`
|
|
100
|
+
* for a relation-free spec on < 17.
|
|
101
|
+
*/
|
|
102
|
+
readonly readTyped: <A extends HasId, I = A>(record: RecordSpec<A, I>, ids: ReadonlyArray<number>, options?: TypedReadOptions) => Effect.Effect<ReadonlyArray<A>, TransportCallError>;
|
|
103
|
+
/**
|
|
104
|
+
* Write `values` to `ids` and return the FRESH nested snapshot via
|
|
105
|
+
* `web_save` (17+ ONLY — no degrade, even for a relation-free spec). One
|
|
106
|
+
* explicit call: it writes, then re-reads through `record`'s compiled
|
|
107
|
+
* `specification`. On < 17 fails with {@link ProtocolUnsupportedError}.
|
|
108
|
+
*/
|
|
109
|
+
readonly saveTyped: <A extends HasId, I = A>(record: RecordSpec<A, I>, ids: ReadonlyArray<number>, values: OdooRecord, options?: TypedReadOptions) => Effect.Effect<ReadonlyArray<A>, TransportCallError>;
|
|
110
|
+
/** `search(domain, offset, limit, order)` — matching record ids. */
|
|
111
|
+
readonly search: (model: string, options?: SearchOptions) => Effect.Effect<ReadonlyArray<number>, TransportCallError>;
|
|
112
|
+
/** `read(ids, fields)` — read fields of the given records. */
|
|
113
|
+
readonly read: (model: string, ids: ReadonlyArray<number>, fields?: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<OdooRecord>, TransportCallError>;
|
|
114
|
+
/**
|
|
115
|
+
* `create(vals_list)` — create one or many records, always returning the new
|
|
116
|
+
* ids as an array (even for a single dict, which is normalized to a
|
|
117
|
+
* one-element list). Odoo 16–19 `model_create_multi` returns a list of ids
|
|
118
|
+
* for a list input, so the return contract is uniformly {@link ReadonlyArray}.
|
|
119
|
+
*/
|
|
120
|
+
readonly create: (model: string, values: OdooRecord | ReadonlyArray<OdooRecord>) => Effect.Effect<ReadonlyArray<number>, TransportCallError>;
|
|
121
|
+
/** `write(ids, values)` — update records; Odoo returns `true`. */
|
|
122
|
+
readonly write: (model: string, ids: ReadonlyArray<number>, values: OdooRecord) => Effect.Effect<true, TransportCallError>;
|
|
123
|
+
/** `unlink(ids)` — delete records; Odoo returns `true`. */
|
|
124
|
+
readonly unlink: (model: string, ids: ReadonlyArray<number>) => Effect.Effect<true, TransportCallError>;
|
|
125
|
+
/** `fields_get(attributes=...)` — field metadata keyed by field name. */
|
|
126
|
+
readonly fieldsGet: (model: string, options?: {
|
|
127
|
+
readonly attributes?: ReadonlyArray<string>;
|
|
128
|
+
}) => Effect.Effect<FieldsMetadata, TransportCallError>;
|
|
129
|
+
/** `search_count(domain)` — number of records matching the domain. */
|
|
130
|
+
readonly searchCount: (model: string, domain: Domain) => Effect.Effect<number, TransportCallError>;
|
|
131
|
+
/**
|
|
132
|
+
* Resolve an external identifier (`module.name`) to its `(model, id)` pair,
|
|
133
|
+
* via `ir.model.data.check_object_reference`.
|
|
134
|
+
*
|
|
135
|
+
* We always use `check_object_reference` (present since Odoo 15) rather than
|
|
136
|
+
* branching to the legacy `xmlid_to_res_model_res_id` — this library
|
|
137
|
+
* supports 16–19 only, so the legacy branch is dead weight. A missing xml_id
|
|
138
|
+
* (the server raises a `ValueError`, mapped to `OdooServerError`) or an
|
|
139
|
+
* xml_id that resolves but is not visible to the caller (`[model, false]`)
|
|
140
|
+
* both fail with {@link OdooMissingError} naming the xml_id.
|
|
141
|
+
*/
|
|
142
|
+
readonly ref: (xmlId: string) => Effect.Effect<readonly [model: string, id: number], TransportCallError>;
|
|
143
|
+
/**
|
|
144
|
+
* Escape hatch: call any `model.method` and return its raw, UNDECODED result.
|
|
145
|
+
* The browse `ids`, `context`, and `kwargs` ride the {@link Rpc} seam; no
|
|
146
|
+
* schema is applied, so the caller owns validating the shape. Prefer the
|
|
147
|
+
* typed ops above where one exists.
|
|
148
|
+
*/
|
|
149
|
+
readonly call: (model: string, method: string, options?: CallOptions) => Effect.Effect<unknown, TransportCallError>;
|
|
150
|
+
/**
|
|
151
|
+
* `read_group(domain, fields, groupby, offset, limit, orderby, lazy)` —
|
|
152
|
+
* aggregate records into groups. Rows decode as unknown records (group keys,
|
|
153
|
+
* aggregates, and `__count`/`__domain` are model-dependent).
|
|
154
|
+
*/
|
|
155
|
+
readonly readGroup: (model: string, options: ReadGroupOptions) => Effect.Effect<ReadonlyArray<OdooRecord>, TransportCallError>;
|
|
156
|
+
/**
|
|
157
|
+
* `name_search(name, domain, operator, limit)` → `(id, display_name)` pairs.
|
|
158
|
+
*
|
|
159
|
+
* WIRE FACT: the second parameter was renamed `args` → `domain` in Odoo 19.
|
|
160
|
+
* Over the execute_kw family we pass all four POSITIONALLY (position is
|
|
161
|
+
* stable across 16–19); over JSON-2 (19+) we pass them by MODERN name
|
|
162
|
+
* (`name`, `domain`, `operator`, `limit`).
|
|
163
|
+
*/
|
|
164
|
+
readonly nameSearch: (model: string, options?: NameSearchOptions) => Effect.Effect<ReadonlyArray<readonly [number, string]>, TransportCallError>;
|
|
165
|
+
/**
|
|
166
|
+
* `name_get()` → `(id, display_name)` pairs for the given ids.
|
|
167
|
+
*
|
|
168
|
+
* GOTCHA: `name_get` was REMOVED server-side in Odoo 17+. We call it as-is
|
|
169
|
+
* with no polyfill; on 17+ it fails as a server fault. Use {@link nameSearch}
|
|
170
|
+
* or read `display_name` instead on modern servers.
|
|
171
|
+
*/
|
|
172
|
+
readonly nameGet: (model: string, ids: ReadonlyArray<number>) => Effect.Effect<ReadonlyArray<readonly [number, string]>, TransportCallError>;
|
|
173
|
+
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Hand-typed, boundary-decoded high-level Odoo operations over {@link Rpc}.
|
|
176
|
+
* Every result is decoded through `effect/Schema`; a wire-shape mismatch fails
|
|
177
|
+
* with {@link SchemaDriftError} carrying the raw payload, never a silent cast.
|
|
178
|
+
*/
|
|
179
|
+
export declare class OdooClient extends OdooClient_base {
|
|
180
|
+
}
|
|
181
|
+
/** The {@link OdooClient} layer over an {@link Rpc} service. */
|
|
182
|
+
export declare const layer: Layer.Layer<OdooClient, never, Rpc>;
|
|
183
|
+
export {};
|
|
184
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAK1C,OAAO,EAEL,KAAK,KAAK,EAEV,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,WAAW,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,yDAAyD;AACzD,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE/D,8DAA8D;AAC9D,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,CAAC;AAiBtE,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAChC;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAChC;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;CAChC;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;mEACmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;;IAqCG;;;OAGG;yBACkB,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,CAAC,EACzC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,EAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAExD;;;;;;;;OAQG;iCAC0B,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,CAAC,EAClD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,GAAG,SAAS,EACtC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KACvB,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAEzD;;;;;;;;;;;;OAYG;0BACmB,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,CAAC,EAC3C,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACxB,OAAO,CAAC,EAAE,YAAY,KACnB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAExD;;;;OAIG;wBACiB,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,CAAC,EACzC,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACxB,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,OAAO,CAAC,EAAE,gBAAgB,KACvB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAExD;;;;;OAKG;wBACiB,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,CAAC,EACzC,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACxB,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,gBAAgB,KACvB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAExD,oEAAoE;qBACnD,CACf,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,aAAa,KACpB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAE7D,8DAA8D;mBAC/C,CACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAEjE;;;;;OAKG;qBACc,CACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,KAC3C,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAE7D,kEAAkE;oBAClD,CACd,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAC1B,MAAM,EAAE,UAAU,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAE5C,2DAA2D;qBAC1C,CACf,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,KACvB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAE5C,yEAAyE;wBACrD,CAClB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;KAAE,KACtD,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAAC;IAEtD,sEAAsE;0BAChD,CACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC;IAE9C;;;;;;;;;;OAUG;kBACW,CACZ,KAAK,EAAE,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAE5E;;;;;OAKG;mBACY,CACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,WAAW,KAClB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAE/C;;;;OAIG;wBACiB,CAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,gBAAgB,KACtB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAEjE;;;;;;;OAOG;yBACkB,CACnB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,KACxB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAEhF;;;;;;OAMG;sBACe,CAChB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,KACvB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,kBAAkB,CAAC;;AAxLpF;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,eAqLH;CAAG;AAEhC,gEAAgE;AAChE,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAgarD,CAAC"}
|