@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.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +401 -0
  3. package/dist/client.d.ts +184 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +277 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/combinators/retryOnSessionExpired.d.ts +13 -0
  8. package/dist/combinators/retryOnSessionExpired.d.ts.map +1 -0
  9. package/dist/combinators/retryOnSessionExpired.js +15 -0
  10. package/dist/combinators/retryOnSessionExpired.js.map +1 -0
  11. package/dist/commands.d.ts +91 -0
  12. package/dist/commands.d.ts.map +1 -0
  13. package/dist/commands.js +69 -0
  14. package/dist/commands.js.map +1 -0
  15. package/dist/config.d.ts +32 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +60 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/domain.d.ts +30 -0
  20. package/dist/domain.d.ts.map +1 -0
  21. package/dist/domain.js +70 -0
  22. package/dist/domain.js.map +1 -0
  23. package/dist/errors/auth.d.ts +27 -0
  24. package/dist/errors/auth.d.ts.map +1 -0
  25. package/dist/errors/auth.js +8 -0
  26. package/dist/errors/auth.js.map +1 -0
  27. package/dist/errors/index.d.ts +30 -0
  28. package/dist/errors/index.d.ts.map +1 -0
  29. package/dist/errors/index.js +13 -0
  30. package/dist/errors/index.js.map +1 -0
  31. package/dist/errors/mapFault.d.ts +31 -0
  32. package/dist/errors/mapFault.d.ts.map +1 -0
  33. package/dist/errors/mapFault.js +48 -0
  34. package/dist/errors/mapFault.js.map +1 -0
  35. package/dist/errors/protocol.d.ts +22 -0
  36. package/dist/errors/protocol.d.ts.map +1 -0
  37. package/dist/errors/protocol.js +9 -0
  38. package/dist/errors/protocol.js.map +1 -0
  39. package/dist/errors/schema.d.ts +22 -0
  40. package/dist/errors/schema.d.ts.map +1 -0
  41. package/dist/errors/schema.js +14 -0
  42. package/dist/errors/schema.js.map +1 -0
  43. package/dist/errors/server.d.ts +109 -0
  44. package/dist/errors/server.d.ts.map +1 -0
  45. package/dist/errors/server.js +49 -0
  46. package/dist/errors/server.js.map +1 -0
  47. package/dist/errors/session.d.ts +16 -0
  48. package/dist/errors/session.d.ts.map +1 -0
  49. package/dist/errors/session.js +11 -0
  50. package/dist/errors/session.js.map +1 -0
  51. package/dist/errors/transport.d.ts +42 -0
  52. package/dist/errors/transport.d.ts.map +1 -0
  53. package/dist/errors/transport.js +49 -0
  54. package/dist/errors/transport.js.map +1 -0
  55. package/dist/index.d.ts +33 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +57 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal/platform.d.ts +12 -0
  60. package/dist/internal/platform.d.ts.map +1 -0
  61. package/dist/internal/platform.js +11 -0
  62. package/dist/internal/platform.js.map +1 -0
  63. package/dist/internal/singleFlight.d.ts +29 -0
  64. package/dist/internal/singleFlight.d.ts.map +1 -0
  65. package/dist/internal/singleFlight.js +41 -0
  66. package/dist/internal/singleFlight.js.map +1 -0
  67. package/dist/profiles.d.ts +179 -0
  68. package/dist/profiles.d.ts.map +1 -0
  69. package/dist/profiles.js +191 -0
  70. package/dist/profiles.js.map +1 -0
  71. package/dist/protocol/jsonrpc.d.ts +127 -0
  72. package/dist/protocol/jsonrpc.d.ts.map +1 -0
  73. package/dist/protocol/jsonrpc.js +117 -0
  74. package/dist/protocol/jsonrpc.js.map +1 -0
  75. package/dist/records/index.d.ts +14 -0
  76. package/dist/records/index.d.ts.map +1 -0
  77. package/dist/records/index.js +14 -0
  78. package/dist/records/index.js.map +1 -0
  79. package/dist/records/recordModel.d.ts +131 -0
  80. package/dist/records/recordModel.d.ts.map +1 -0
  81. package/dist/records/recordModel.js +101 -0
  82. package/dist/records/recordModel.js.map +1 -0
  83. package/dist/records/related.d.ts +42 -0
  84. package/dist/records/related.d.ts.map +1 -0
  85. package/dist/records/related.js +51 -0
  86. package/dist/records/related.js.map +1 -0
  87. package/dist/records/relations.d.ts +66 -0
  88. package/dist/records/relations.d.ts.map +1 -0
  89. package/dist/records/relations.js +119 -0
  90. package/dist/records/relations.js.map +1 -0
  91. package/dist/records/spec.d.ts +44 -0
  92. package/dist/records/spec.d.ts.map +1 -0
  93. package/dist/records/spec.js +63 -0
  94. package/dist/records/spec.js.map +1 -0
  95. package/dist/records/typed.d.ts +71 -0
  96. package/dist/records/typed.d.ts.map +1 -0
  97. package/dist/records/typed.js +65 -0
  98. package/dist/records/typed.js.map +1 -0
  99. package/dist/rpc.d.ts +90 -0
  100. package/dist/rpc.d.ts.map +1 -0
  101. package/dist/rpc.js +113 -0
  102. package/dist/rpc.js.map +1 -0
  103. package/dist/services/db.d.ts +128 -0
  104. package/dist/services/db.d.ts.map +1 -0
  105. package/dist/services/db.js +132 -0
  106. package/dist/services/db.js.map +1 -0
  107. package/dist/services/report.d.ts +48 -0
  108. package/dist/services/report.d.ts.map +1 -0
  109. package/dist/services/report.js +103 -0
  110. package/dist/services/report.js.map +1 -0
  111. package/dist/session/cookie.d.ts +114 -0
  112. package/dist/session/cookie.d.ts.map +1 -0
  113. package/dist/session/cookie.js +231 -0
  114. package/dist/session/cookie.js.map +1 -0
  115. package/dist/testing/fakeTransport.d.ts +40 -0
  116. package/dist/testing/fakeTransport.d.ts.map +1 -0
  117. package/dist/testing/fakeTransport.js +36 -0
  118. package/dist/testing/fakeTransport.js.map +1 -0
  119. package/dist/testing/index.d.ts +9 -0
  120. package/dist/testing/index.d.ts.map +1 -0
  121. package/dist/testing/index.js +9 -0
  122. package/dist/testing/index.js.map +1 -0
  123. package/dist/transport.d.ts +62 -0
  124. package/dist/transport.d.ts.map +1 -0
  125. package/dist/transport.js +9 -0
  126. package/dist/transport.js.map +1 -0
  127. package/dist/transports/json2.d.ts +41 -0
  128. package/dist/transports/json2.d.ts.map +1 -0
  129. package/dist/transports/json2.js +196 -0
  130. package/dist/transports/json2.js.map +1 -0
  131. package/dist/transports/jsonrpc.d.ts +41 -0
  132. package/dist/transports/jsonrpc.d.ts.map +1 -0
  133. package/dist/transports/jsonrpc.js +119 -0
  134. package/dist/transports/jsonrpc.js.map +1 -0
  135. package/dist/transports/web.d.ts +15 -0
  136. package/dist/transports/web.d.ts.map +1 -0
  137. package/dist/transports/web.js +62 -0
  138. package/dist/transports/web.js.map +1 -0
  139. package/dist/version-live.d.ts +41 -0
  140. package/dist/version-live.d.ts.map +1 -0
  141. package/dist/version-live.js +26 -0
  142. package/dist/version-live.js.map +1 -0
  143. package/dist/version.d.ts +78 -0
  144. package/dist/version.d.ts.map +1 -0
  145. package/dist/version.js +46 -0
  146. package/dist/version.js.map +1 -0
  147. package/docs/protocol-verification.md +43 -0
  148. package/package.json +71 -0
  149. package/src/client.ts +731 -0
  150. package/src/combinators/retryOnSessionExpired.ts +25 -0
  151. package/src/commands.ts +112 -0
  152. package/src/config.ts +110 -0
  153. package/src/domain.ts +91 -0
  154. package/src/errors/auth.ts +28 -0
  155. package/src/errors/index.ts +50 -0
  156. package/src/errors/mapFault.ts +92 -0
  157. package/src/errors/protocol.ts +19 -0
  158. package/src/errors/schema.ts +18 -0
  159. package/src/errors/server.ts +79 -0
  160. package/src/errors/session.ts +12 -0
  161. package/src/errors/transport.ts +74 -0
  162. package/src/index.ts +152 -0
  163. package/src/internal/platform.ts +22 -0
  164. package/src/internal/singleFlight.ts +80 -0
  165. package/src/profiles.ts +386 -0
  166. package/src/protocol/jsonrpc.ts +162 -0
  167. package/src/records/index.ts +49 -0
  168. package/src/records/recordModel.ts +211 -0
  169. package/src/records/related.ts +80 -0
  170. package/src/records/relations.ts +192 -0
  171. package/src/records/spec.ts +96 -0
  172. package/src/records/typed.ts +180 -0
  173. package/src/rpc.ts +219 -0
  174. package/src/services/db.ts +282 -0
  175. package/src/services/report.ts +165 -0
  176. package/src/session/cookie.ts +461 -0
  177. package/src/testing/fakeTransport.ts +84 -0
  178. package/src/testing/index.ts +9 -0
  179. package/src/transport.ts +72 -0
  180. package/src/transports/json2.ts +303 -0
  181. package/src/transports/jsonrpc.ts +218 -0
  182. package/src/transports/web.ts +96 -0
  183. package/src/version-live.ts +67 -0
  184. package/src/version.ts +102 -0
@@ -0,0 +1,62 @@
1
+ import { Context, type Effect } from "effect";
2
+ import type { OdooAuthenticationError } from "./errors/auth.js";
3
+ import type { ProtocolUnsupportedError } from "./errors/protocol.js";
4
+ import type { SchemaDriftError } from "./errors/schema.js";
5
+ import type { OdooServerFault } from "./errors/server.js";
6
+ import type { SessionExpiredError } from "./errors/session.js";
7
+ import type { OdooTransportError } from "./errors/transport.js";
8
+ /**
9
+ * Parameters for a single `call_kw`-shaped round trip. Deliberately mirrors
10
+ * Odoo's `execute_kw(model, method, args, kwargs)`.
11
+ */
12
+ export interface CallKwParams {
13
+ readonly model: string;
14
+ readonly method: string;
15
+ readonly args: ReadonlyArray<unknown>;
16
+ readonly kwargs: Record<string, unknown>;
17
+ /**
18
+ * The browse target — the record ids the method operates on.
19
+ *
20
+ * GOTCHA: this is protocol-agnostic on purpose. JSON-2 sends it as its special
21
+ * `ids` body key; the execute_kw-family transports (web, `/jsonrpc`) prepend it
22
+ * as the first positional argument. Callers set `ids` instead of hand-placing
23
+ * it in `args`, so the same call works over every seam.
24
+ */
25
+ readonly ids?: ReadonlyArray<number>;
26
+ }
27
+ /**
28
+ * The complete typed failure channel of a transport round trip. Every seam
29
+ * (web, JSON-RPC, JSON-2) maps its wire faults into exactly this union at its
30
+ * choke point, so application code sees one shape regardless of protocol.
31
+ */
32
+ export type TransportCallError = OdooTransportError | SessionExpiredError | SchemaDriftError | OdooAuthenticationError | ProtocolUnsupportedError | OdooServerFault;
33
+ /**
34
+ * How a transport encodes calls on the wire. High-level ops that know a
35
+ * method's Python semantics branch on this — a transport itself never reorders
36
+ * args. The one live case: `create` vals MUST be positional over the
37
+ * execute_kw family (Odoo's `call_kw` reads `args[0]` unconditionally, 16–19)
38
+ * but keyword-only (`vals_list`) over JSON-2.
39
+ */
40
+ export type TransportDialect = "execute-kw" | "json2";
41
+ declare const Transport_base: Context.ServiceClass<Transport, "odoo-rpc-ts/Transport", {
42
+ /** The wire dialect — see {@link TransportDialect}. */
43
+ readonly dialect: TransportDialect;
44
+ /**
45
+ * Execute one `call_kw`-shaped round trip and return the raw, still-undecoded
46
+ * result (callers decode through `effect/Schema` at their boundary).
47
+ *
48
+ * GOTCHA: the JSON-2 API (`/json/2/<model>/<method>`) is keyword-only — it
49
+ * cannot take positional `args`. A `Json2Transport` will reject any call
50
+ * where `args` is non-empty; put everything in `kwargs` when targeting it.
51
+ */
52
+ readonly callKw: (params: CallKwParams) => Effect.Effect<unknown, TransportCallError>;
53
+ }>;
54
+ /**
55
+ * The one seam every protocol implements. `JsonRpcTransport`, `Json2Transport`,
56
+ * and the cookie-session `WebTransport` all provide this tag; application code
57
+ * depends only on it and stays protocol-agnostic.
58
+ */
59
+ export declare class Transport extends Transport_base {
60
+ }
61
+ export {};
62
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B,kBAAkB,GAClB,mBAAmB,GACnB,gBAAgB,GAChB,uBAAuB,GACvB,wBAAwB,GACxB,eAAe,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,OAAO,CAAC;;IAUlD,uDAAuD;sBACrC,gBAAgB;IAElC;;;;;;;OAOG;qBACc,CAAC,MAAM,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,kBAAkB,CAAC;;AAnBzF;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAgBH;CAAG"}
@@ -0,0 +1,9 @@
1
+ import { Context } from "effect";
2
+ /**
3
+ * The one seam every protocol implements. `JsonRpcTransport`, `Json2Transport`,
4
+ * and the cookie-session `WebTransport` all provide this tag; application code
5
+ * depends only on it and stays protocol-agnostic.
6
+ */
7
+ export class Transport extends Context.Service()("odoo-rpc-ts/Transport") {
8
+ }
9
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,QAAQ,CAAC;AAkD9C;;;;GAIG;AACH,MAAM,OAAO,SAAU,SAAQ,OAAO,CAAC,OAAO,EAgB3C,CAAC,uBAAuB,CAAC;CAAG"}
@@ -0,0 +1,41 @@
1
+ import { type Config, Effect, Layer, Option, Schema } from "effect";
2
+ import type { OdooConfig as OdooConfigType } from "../config.js";
3
+ import { OdooAuthenticationError } from "../errors/auth.js";
4
+ import { SchemaDriftError } from "../errors/schema.js";
5
+ import { OdooTransportError } from "../errors/transport.js";
6
+ import { HttpClient } from "../internal/platform.js";
7
+ import { Transport } from "../transport.js";
8
+ /**
9
+ * Build a {@link Transport} bound to the JSON-2 route for a given config.
10
+ *
11
+ * GOTCHA: JSON-2 is bearer-only. If the config carries `Password` credentials
12
+ * there is no API key to send, so construction FAILS in the typed channel with
13
+ * {@link OdooAuthenticationError} rather than silently degrading — this is a
14
+ * `Layer` build failure, surfaced eagerly so a misconfigured client never issues
15
+ * an unauthenticated request.
16
+ */
17
+ export declare const make: (config: OdooConfigType) => Effect.Effect<typeof Transport.Service, OdooAuthenticationError, HttpClient.HttpClient>;
18
+ /** Wire a JSON-2 {@link Transport} from an explicit config. */
19
+ export declare const layer: (config: OdooConfigType) => Layer.Layer<Transport, OdooAuthenticationError, HttpClient.HttpClient>;
20
+ /** Wire a JSON-2 {@link Transport}, reading {@link OdooConfig} from the environment. */
21
+ export declare const layerConfig: Layer.Layer<Transport, OdooAuthenticationError | Config.ConfigError, HttpClient.HttpClient>;
22
+ /**
23
+ * The `/json/version` probe result: the server's version string and its raw
24
+ * `version_info` tuple. Decoded at the boundary; drift raises
25
+ * {@link SchemaDriftError}.
26
+ */
27
+ export declare const Json2Version: Schema.Struct<{
28
+ readonly version: Schema.String;
29
+ readonly version_info: Schema.$Array<Schema.Union<readonly [Schema.Number, Schema.String]>>;
30
+ }>;
31
+ export type Json2Version = typeof Json2Version.Type;
32
+ /**
33
+ * Probe `GET {url}/json/version` to detect JSON-2 support.
34
+ *
35
+ * Returns `Option.some(version)` on a 200, and `Option.none()` on a 404 — a 404
36
+ * is the documented "this server predates Odoo 19 (no JSON-2)" signal, not an
37
+ * error. Any other non-2xx is a real {@link OdooTransportError}; a 200 body that
38
+ * fails to decode is a {@link SchemaDriftError}.
39
+ */
40
+ export declare const probeJson2Version: (config: OdooConfigType) => Effect.Effect<Option.Option<Json2Version>, OdooTransportError | SchemaDriftError, HttpClient.HttpClient>;
41
+ //# sourceMappingURL=json2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json2.d.ts","sourceRoot":"","sources":["../../src/transports/json2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAY,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9E,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AASvD,OAAO,EAAE,kBAAkB,EAAoB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EACL,UAAU,EAIX,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAqB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAyH/D;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,WACP,cAAc,KACrB,MAAM,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,OAAO,EAAE,uBAAuB,EAAE,UAAU,CAAC,UAAU,CAyErF,CAAC;AASL,+DAA+D;AAC/D,eAAO,MAAM,KAAK,WACR,cAAc,KACrB,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,uBAAuB,EAAE,UAAU,CAAC,UAAU,CACjC,CAAC;AAExC,wFAAwF;AACxF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,KAAK,CACnC,SAAS,EACT,uBAAuB,GAAG,MAAM,CAAC,WAAW,EAC5C,UAAU,CAAC,UAAU,CACsC,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;EAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,WACpB,cAAc,KACrB,MAAM,CAAC,MAAM,CACd,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAC3B,kBAAkB,GAAG,gBAAgB,EACrC,UAAU,CAAC,UAAU,CA6BnB,CAAC"}
@@ -0,0 +1,196 @@
1
+ import { Effect, Layer, Option, Redacted, Schema } from "effect";
2
+ import { OdooConfig } from "../config.js";
3
+ import { OdooAuthenticationError } from "../errors/auth.js";
4
+ import { mapServerFault } from "../errors/mapFault.js";
5
+ import { ProtocolUnsupportedError } from "../errors/protocol.js";
6
+ import { SchemaDriftError } from "../errors/schema.js";
7
+ import { OdooAccessError, OdooLockError, OdooMissingError, OdooServerError, OdooUserError, } from "../errors/server.js";
8
+ import { OdooTransportError } from "../errors/transport.js";
9
+ import { HttpClient, HttpClientRequest, HttpClientResponse, } from "../internal/platform.js";
10
+ import { Transport } from "../transport.js";
11
+ import { ServerVersionInfo } from "../version.js";
12
+ /**
13
+ * Odoo's `/json/2/<model>/<method>` API (Odoo 19+): a keyword-only, bearer-auth
14
+ * JSON route that returns the raw method result with no JSON-RPC envelope. This
15
+ * module is one of the three seams behind the {@link Transport} tag.
16
+ */
17
+ /** Strip any trailing slashes so path segments join cleanly onto a base URL. */
18
+ const baseUrl = (url) => url.href.replace(/\/+$/u, "");
19
+ /** Sanitized request snapshot — never carries the bearer header or body. */
20
+ const requestInfo = (url, method) => ({ method, url });
21
+ const toTransportError = (request) => (cause) => OdooTransportError.fromHttpClientError(request, cause);
22
+ /**
23
+ * The `serialize_exception` payload Odoo returns in an error body. Everything is
24
+ * optional: a bare HTTP failure (proxy 502, gateway timeout) may have no JSON
25
+ * body at all, in which case we synthesize a fault from the status alone.
26
+ */
27
+ const SerializeException = Schema.Struct({
28
+ name: Schema.optional(Schema.String),
29
+ message: Schema.optional(Schema.String),
30
+ arguments: Schema.optional(Schema.Array(Schema.Unknown)),
31
+ context: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
32
+ debug: Schema.optional(Schema.String),
33
+ });
34
+ const emptyException = {};
35
+ /**
36
+ * Build a {@link RawServerFault} from a decoded exception body and the call
37
+ * site, honouring `exactOptionalPropertyTypes` (optional keys are attached only
38
+ * when present, never as explicit `undefined`).
39
+ */
40
+ const rawFault = (exc, status, site) => ({
41
+ name: exc.name ?? `http.${status}`,
42
+ message: exc.message ?? "",
43
+ arguments: exc.arguments ?? [],
44
+ context: exc.context ?? {},
45
+ ...(exc.debug !== undefined ? { debug: exc.debug } : {}),
46
+ ...(site.model !== undefined ? { model: site.model } : {}),
47
+ ...(site.method !== undefined ? { method: site.method } : {}),
48
+ });
49
+ /** Status → concrete server-fault constructor, for the no-`name` fallback path. */
50
+ const byStatus = {
51
+ 403: OdooAccessError,
52
+ 404: OdooMissingError,
53
+ 409: OdooLockError,
54
+ 422: OdooUserError,
55
+ };
56
+ /**
57
+ * The single error-mapping choke point for JSON-2. A non-2xx response becomes
58
+ * exactly one tagged error:
59
+ *
60
+ * - `400` / `415` are transport-shaped (malformed request / wrong media type),
61
+ * so they map to {@link OdooTransportError} with sanitized response-error
62
+ * metadata.
63
+ * - Any body with a Python `name` goes through {@link mapServerFault} (shared
64
+ * with the other transports), so `AccessDenied` correctly becomes an auth
65
+ * error and known exception names get their subtype.
66
+ * - Otherwise we fall back on the HTTP status, synthesizing a fault named
67
+ * `http.<status>` so nothing is silently dropped.
68
+ */
69
+ const mapJson2Fault = (response, request, site) => Effect.gen(function* () {
70
+ const status = response.status;
71
+ if (status === 400 || status === 415) {
72
+ // `filterStatusOk` manufactures a response error from the non-ok status
73
+ // without reading the body; the public error retains only safe metadata.
74
+ const cause = yield* Effect.flip(HttpClientResponse.filterStatusOk(response)).pipe(Effect.orDie);
75
+ return yield* Effect.fail(OdooTransportError.fromHttpClientError(request, cause));
76
+ }
77
+ // A non-JSON or shape-drifted error body falls back to an empty exception,
78
+ // which routes to status-based mapping below.
79
+ const exc = yield* response.json.pipe(Effect.flatMap(Schema.decodeUnknownEffect(SerializeException)), Effect.orElseSucceed(() => emptyException));
80
+ const raw = rawFault(exc, status, site);
81
+ // GOTCHA: 401 is checked BEFORE the body name. Odoo 19 serializes a bad
82
+ // bearer key as werkzeug.exceptions.Unauthorized — an unmapped name that
83
+ // would otherwise fall through to OdooServerError instead of auth.
84
+ if (status === 401) {
85
+ return yield* Effect.fail(new OdooAuthenticationError({
86
+ reason: "api-key-expired-or-invalid",
87
+ message: raw.message || "JSON-2 rejected the API key (HTTP 401).",
88
+ }));
89
+ }
90
+ if (exc.name !== undefined) {
91
+ return yield* Effect.fail(mapServerFault(raw, site));
92
+ }
93
+ const Ctor = byStatus[status] ?? OdooServerError;
94
+ return yield* Effect.fail(new Ctor(raw));
95
+ });
96
+ /**
97
+ * Build a {@link Transport} bound to the JSON-2 route for a given config.
98
+ *
99
+ * GOTCHA: JSON-2 is bearer-only. If the config carries `Password` credentials
100
+ * there is no API key to send, so construction FAILS in the typed channel with
101
+ * {@link OdooAuthenticationError} rather than silently degrading — this is a
102
+ * `Layer` build failure, surfaced eagerly so a misconfigured client never issues
103
+ * an unauthenticated request.
104
+ */
105
+ export const make = (config) => Effect.gen(function* () {
106
+ if (config.credentials._tag !== "ApiKey") {
107
+ return yield* Effect.fail(new OdooAuthenticationError({
108
+ reason: "invalid-credentials",
109
+ message: "JSON-2 transport is bearer-only and requires ApiKey credentials; " +
110
+ "got Password. Provide ODOO_API_KEY.",
111
+ }));
112
+ }
113
+ const apiKey = config.credentials.apiKey;
114
+ const client = yield* HttpClient.HttpClient;
115
+ const base = baseUrl(config.url);
116
+ const callKw = (params) => {
117
+ const site = { model: params.model, method: params.method };
118
+ const run = Effect.gen(function* () {
119
+ // JSON-2 cannot carry positional args; reordering silently would corrupt
120
+ // the call, so reject loudly and name the offending method.
121
+ if (params.args.length > 0) {
122
+ return yield* Effect.fail(new ProtocolUnsupportedError({
123
+ protocol: "json-2",
124
+ serverVersion: "19+",
125
+ message: `JSON-2 is keyword-only: "${params.model}.${params.method}" was called with ` +
126
+ `${params.args.length} positional argument(s); move them into kwargs.`,
127
+ }));
128
+ }
129
+ const url = `${base}/json/2/${params.model}/${params.method}`;
130
+ const info = requestInfo(url, "POST");
131
+ // Keyword-only route: the browse target rides as the dedicated `ids` body
132
+ // key (see CallKwParams.ids), never as positional args.
133
+ const body = params.ids ? { ...params.kwargs, ids: params.ids } : params.kwargs;
134
+ // Redacted is unwrapped ONLY here, at the wire boundary.
135
+ const request = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders({
136
+ Authorization: `Bearer ${Redacted.value(apiKey)}`,
137
+ "X-Odoo-Database": config.db,
138
+ "Content-Type": "application/json",
139
+ }), HttpClientRequest.bodyJsonUnsafe(body));
140
+ const response = yield* client
141
+ .execute(request)
142
+ .pipe(Effect.mapError(toTransportError(info)));
143
+ if (response.status >= 200 && response.status < 300) {
144
+ // Raw method result, no envelope; callers decode at their own boundary.
145
+ return yield* response.json.pipe(Effect.mapError(toTransportError(info)));
146
+ }
147
+ return yield* mapJson2Fault(response, info, site);
148
+ });
149
+ // Span carries only the coordinates — never args, kwargs, or the API key.
150
+ return run.pipe(Effect.withSpan("odoo.json2", {
151
+ attributes: { model: params.model, method: params.method },
152
+ }));
153
+ };
154
+ return { dialect: "json2", callKw };
155
+ });
156
+ /** Wire a JSON-2 {@link Transport} from an explicit config. */
157
+ export const layer = (config) => Layer.effect(Transport, make(config));
158
+ /** Wire a JSON-2 {@link Transport}, reading {@link OdooConfig} from the environment. */
159
+ export const layerConfig = Layer.effect(Transport, Effect.flatMap(OdooConfig, make));
160
+ /**
161
+ * The `/json/version` probe result: the server's version string and its raw
162
+ * `version_info` tuple. Decoded at the boundary; drift raises
163
+ * {@link SchemaDriftError}.
164
+ */
165
+ export const Json2Version = Schema.Struct({
166
+ version: Schema.String,
167
+ version_info: ServerVersionInfo,
168
+ });
169
+ /**
170
+ * Probe `GET {url}/json/version` to detect JSON-2 support.
171
+ *
172
+ * Returns `Option.some(version)` on a 200, and `Option.none()` on a 404 — a 404
173
+ * is the documented "this server predates Odoo 19 (no JSON-2)" signal, not an
174
+ * error. Any other non-2xx is a real {@link OdooTransportError}; a 200 body that
175
+ * fails to decode is a {@link SchemaDriftError}.
176
+ */
177
+ export const probeJson2Version = (config) => Effect.gen(function* () {
178
+ const client = yield* HttpClient.HttpClient;
179
+ const url = `${baseUrl(config.url)}/json/version`;
180
+ const info = requestInfo(url, "GET");
181
+ const response = yield* client
182
+ .execute(HttpClientRequest.get(url))
183
+ .pipe(Effect.mapError(toTransportError(info)));
184
+ const status = response.status;
185
+ if (status === 404) {
186
+ return Option.none();
187
+ }
188
+ if (status < 200 || status >= 300) {
189
+ const cause = yield* Effect.flip(HttpClientResponse.filterStatusOk(response)).pipe(Effect.orDie);
190
+ return yield* Effect.fail(OdooTransportError.fromHttpClientError(info, cause));
191
+ }
192
+ const payload = yield* response.json.pipe(Effect.mapError(toTransportError(info)));
193
+ const decoded = yield* Schema.decodeUnknownEffect(Json2Version)(payload).pipe(Effect.mapError((cause) => new SchemaDriftError({ context: "odoo.json2.version", payload, cause })));
194
+ return Option.some(decoded);
195
+ });
196
+ //# sourceMappingURL=json2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json2.js","sourceRoot":"","sources":["../../src/transports/json2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAsB,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EAEf,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAoB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EACL,UAAU,EAEV,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAqB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD;;;;GAIG;AAEH,gFAAgF;AAChF,MAAM,OAAO,GAAG,CAAC,GAAQ,EAAU,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAEpE,4EAA4E;AAC5E,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,MAAc,EAAe,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AAEpF,MAAM,gBAAgB,GACpB,CAAC,OAAoB,EAAE,EAAE,CACzB,CAAC,KAA0B,EAAsB,EAAE,CACjD,kBAAkB,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACtE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;CACtC,CAAC,CAAC;AAGH,MAAM,cAAc,GAAuB,EAAE,CAAC;AAE9C;;;;GAIG;AACH,MAAM,QAAQ,GAAG,CACf,GAAuB,EACvB,MAAc,EACd,IAAmB,EACa,EAAE,CAAC,CAAC;IACpC,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,QAAQ,MAAM,EAAE;IAClC,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;IAC1B,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE;IAC9B,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;IAC1B,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CAC9D,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,QAAQ,GAAoF;IAChG,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,aAAa;CACnB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,aAAa,GAAG,CACpB,QAA+C,EAC/C,OAAoB,EACpB,IAAmB,EACmE,EAAE,CACxF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAE/B,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,wEAAwE;QACxE,yEAAyE;QACzE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAChF,MAAM,CAAC,KAAK,CACb,CAAC;QACF,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,2EAA2E;IAC3E,8CAA8C;IAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CACnC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,CAAC,EAC9D,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,CAC3C,CAAC;IACF,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAExC,wEAAwE;IACxE,yEAAyE;IACzE,mEAAmE;IACnE,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,IAAI,uBAAuB,CAAC;YAC1B,MAAM,EAAE,4BAA4B;YACpC,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,yCAAyC;SAClE,CAAC,CACH,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,eAAe,CAAC;IACjD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEL;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,MAAsB,EACmE,EAAE,CAC3F,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,IAAI,uBAAuB,CAAC;YAC1B,MAAM,EAAE,qBAAqB;YAC7B,OAAO,EACL,mEAAmE;gBACnE,qCAAqC;SACxC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;IACzC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEjC,MAAM,MAAM,GAAG,CAAC,MAAoB,EAAmD,EAAE;QACvF,MAAM,IAAI,GAAkB,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAE3E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC9B,yEAAyE;YACzE,4DAA4D;YAC5D,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,IAAI,wBAAwB,CAAC;oBAC3B,QAAQ,EAAE,QAAQ;oBAClB,aAAa,EAAE,KAAK;oBACpB,OAAO,EACL,4BAA4B,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,oBAAoB;wBAC7E,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,iDAAiD;iBACzE,CAAC,CACH,CAAC;YACJ,CAAC;YAED,MAAM,GAAG,GAAG,GAAG,IAAI,WAAW,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAEtC,0EAA0E;YAC1E,wDAAwD;YACxD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;YAEhF,yDAAyD;YACzD,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAC9C,iBAAiB,CAAC,UAAU,CAAC;gBAC3B,aAAa,EAAE,UAAU,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;gBACjD,iBAAiB,EAAE,MAAM,CAAC,EAAE;gBAC5B,cAAc,EAAE,kBAAkB;aACnC,CAAC,EACF,iBAAiB,CAAC,cAAc,CAAC,IAAI,CAAC,CACvC,CAAC;YAEF,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM;iBAC3B,OAAO,CAAC,OAAO,CAAC;iBAChB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEjD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACpD,wEAAwE;gBACxE,OAAO,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5E,CAAC;YAED,OAAO,KAAK,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,0EAA0E;QAC1E,OAAO,GAAG,CAAC,IAAI,CACb,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE;YAC5B,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;SAC3D,CAAC,CACH,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,EAAE,OAAO,EAAE,OAAgB,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC,CAAC,CAAC;AASL,+DAA+D;AAC/D,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAsB,EACkD,EAAE,CAC1E,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAExC,wFAAwF;AACxF,MAAM,CAAC,MAAM,WAAW,GAIpB,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,YAAY,EAAE,iBAAiB;CAChC,CAAC,CAAC;AAGH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAsB,EAKtB,EAAE,CACF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAC5C,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;IAClD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM;SAC3B,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;SACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC/B,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IACD,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAChF,MAAM,CAAC,KAAK,CACb,CAAC;QACF,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAC3E,MAAM,CAAC,QAAQ,CACb,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,gBAAgB,CAAC,EAAE,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CACnF,CACF,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { Config, Effect, Layer } from "effect";
2
+ import { OdooConfig } from "../config.js";
3
+ import { OdooAuthenticationError } from "../errors/auth.js";
4
+ import { SchemaDriftError } from "../errors/schema.js";
5
+ import type { OdooServerFault } from "../errors/server.js";
6
+ import type { SessionExpiredError } from "../errors/session.js";
7
+ import { OdooTransportError } from "../errors/transport.js";
8
+ import { HttpClient } from "../internal/platform.js";
9
+ import { Transport } from "../transport.js";
10
+ import { CommonVersionResponse } from "../version.js";
11
+ /** The failure channel a single round trip can produce (a subset of `TransportCallError`). */
12
+ type RoundTripError = OdooTransportError | SchemaDriftError | SessionExpiredError | OdooServerFault | OdooAuthenticationError;
13
+ /**
14
+ * Build a `JsonRpcTransport` service value against a fully-resolved config.
15
+ *
16
+ * `uid` is resolved lazily on the first `callKw` via `common.authenticate`, then
17
+ * success-only single-flight cached (a failed authenticate caches nothing, so
18
+ * the next call retries — never `Effect.cached`).
19
+ *
20
+ * Requires an `HttpClient` in context; the caller provides the platform layer.
21
+ */
22
+ export declare const make: (config: OdooConfig) => Effect.Effect<typeof Transport.Service, never, HttpClient.HttpClient>;
23
+ /**
24
+ * `JsonRpcTransport` as a `Layer`, wired against an explicit config. Never
25
+ * fails to build (`E = never`); the config is already resolved. Requires an
26
+ * `HttpClient` from the platform layer the consumer provides.
27
+ */
28
+ export declare const layer: (config: OdooConfig) => Layer.Layer<Transport, never, HttpClient.HttpClient>;
29
+ /**
30
+ * `JsonRpcTransport` as a `Layer` that reads {@link OdooConfig} from the ambient
31
+ * `ConfigProvider`. Fails with `ConfigError` if required values are missing.
32
+ */
33
+ export declare const layerConfig: Layer.Layer<Transport, Config.ConfigError, HttpClient.HttpClient>;
34
+ /**
35
+ * The `common.version` probe, decoded into {@link CommonVersionResponse}. Shared
36
+ * with the `VersionResolver` implementation so version resolution and the
37
+ * transport agree on one wire path. Requires an `HttpClient` in context.
38
+ */
39
+ export declare const makeVersion: (config: OdooConfig) => Effect.Effect<CommonVersionResponse, RoundTripError, HttpClient.HttpClient>;
40
+ export {};
41
+ //# sourceMappingURL=jsonrpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../../src/transports/jsonrpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAyB,MAAM,QAAQ,CAAC;AACtE,OAAO,EAAE,UAAU,EAAwB,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAqB,MAAM,yBAAyB,CAAC;AAGxE,OAAO,EAAqB,SAAS,EAA2B,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAStD,8FAA8F;AAC9F,KAAK,cAAc,GACf,kBAAkB,GAClB,gBAAgB,GAChB,mBAAmB,GACnB,eAAe,GACf,uBAAuB,CAAC;AA0D5B;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,WACP,UAAU,KACjB,MAAM,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CA+EnE,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,KAAK,WAAY,UAAU,KAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CACvD,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,UAAU,CAC/B,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,WAAW,WACd,UAAU,KACjB,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,cAAc,EAAE,UAAU,CAAC,UAAU,CAkBzE,CAAC"}
@@ -0,0 +1,119 @@
1
+ import { Config, Effect, Layer, Redacted, Ref, Schema } from "effect";
2
+ import { OdooConfig } from "../config.js";
3
+ import { OdooAuthenticationError } from "../errors/auth.js";
4
+ import { SchemaDriftError } from "../errors/schema.js";
5
+ import { OdooTransportError } from "../errors/transport.js";
6
+ import { HttpClient, HttpClientRequest } from "../internal/platform.js";
7
+ import * as SingleFlight from "../internal/singleFlight.js";
8
+ import { buildRequest, JsonRpcResponse, mapJsonRpcError } from "../protocol/jsonrpc.js";
9
+ import { Transport } from "../transport.js";
10
+ import { CommonVersionResponse } from "../version.js";
11
+ /** Join the configured base URL with the `/jsonrpc` path, tolerating a trailing slash. */
12
+ const jsonRpcEndpoint = (url) => `${url.href.replace(/\/+$/, "")}/jsonrpc`;
13
+ const extractCredentials = (credentials) => credentials._tag === "ApiKey"
14
+ ? { username: credentials.username, secret: credentials.apiKey, isPassword: false }
15
+ : { username: credentials.username, secret: credentials.password, isPassword: true };
16
+ /**
17
+ * One JSON-RPC round trip: build the envelope, POST it, decode the response, and
18
+ * either return the raw `result` or map the fault into our union.
19
+ *
20
+ * GOTCHA: never put `args`/`kwargs`/the secret into the span attributes — only
21
+ * the model and method (or service/method) are safe to record.
22
+ */
23
+ const roundTrip = (client, endpoint, id, params, attributes, site) => Effect.gen(function* () {
24
+ // v4 rename: bodyUnsafeJson → bodyJsonUnsafe.
25
+ const request = HttpClientRequest.post(endpoint).pipe(HttpClientRequest.setHeader("Content-Type", "application/json"), HttpClientRequest.bodyJsonUnsafe(buildRequest(params, id)));
26
+ // HttpClientError covers both connection failures and a non-JSON body.
27
+ const payload = yield* client.execute(request).pipe(Effect.flatMap((response) => response.json), Effect.mapError((cause) => OdooTransportError.fromHttpClientError({ method: "POST", url: endpoint }, cause)));
28
+ const decoded = yield* Schema.decodeUnknownEffect(JsonRpcResponse)(payload).pipe(Effect.mapError((cause) => new SchemaDriftError({ context: "odoo.jsonrpc response", payload, cause })));
29
+ if ("error" in decoded) {
30
+ return yield* Effect.fail(mapJsonRpcError(decoded.error, site));
31
+ }
32
+ return decoded.result;
33
+ }).pipe(Effect.withSpan("odoo.jsonrpc", { attributes }));
34
+ /**
35
+ * Build a `JsonRpcTransport` service value against a fully-resolved config.
36
+ *
37
+ * `uid` is resolved lazily on the first `callKw` via `common.authenticate`, then
38
+ * success-only single-flight cached (a failed authenticate caches nothing, so
39
+ * the next call retries — never `Effect.cached`).
40
+ *
41
+ * Requires an `HttpClient` in context; the caller provides the platform layer.
42
+ */
43
+ export const make = (config) => Effect.gen(function* () {
44
+ const client = yield* HttpClient.HttpClient;
45
+ const idRef = yield* Ref.make(0);
46
+ const endpoint = jsonRpcEndpoint(config.url);
47
+ const { username, secret, isPassword } = extractCredentials(config.credentials);
48
+ const db = config.db;
49
+ const nextId = Ref.updateAndGet(idRef, (n) => n + 1);
50
+ const authenticate = Effect.gen(function* () {
51
+ const id = yield* nextId;
52
+ const result = yield* roundTrip(client, endpoint, id, {
53
+ service: "common",
54
+ method: "authenticate",
55
+ args: [db, username, Redacted.value(secret), {}],
56
+ }, {}, {});
57
+ if (typeof result === "number" && result > 0) {
58
+ return result;
59
+ }
60
+ // authenticate returned `false` (or a null uid): credentials rejected.
61
+ return yield* Effect.fail(new OdooAuthenticationError({
62
+ reason: "invalid-credentials",
63
+ message: isPassword
64
+ ? "Odoo rejected the login. If this account has TOTP/2FA enabled, its password cannot be used over RPC — supply an API key as the secret instead."
65
+ : "Odoo rejected the provided credentials.",
66
+ }));
67
+ });
68
+ const uid = yield* SingleFlight.make(authenticate);
69
+ const callKw = (params) => Effect.gen(function* () {
70
+ const resolvedUid = yield* uid.get;
71
+ const id = yield* nextId;
72
+ // execute_kw-family route: an explicit `ids` browse target rides as the
73
+ // first positional argument (see CallKwParams.ids).
74
+ const args = params.ids ? [params.ids, ...params.args] : params.args;
75
+ return yield* roundTrip(client, endpoint, id, {
76
+ service: "object",
77
+ method: "execute_kw",
78
+ args: [
79
+ db,
80
+ resolvedUid,
81
+ Redacted.value(secret),
82
+ params.model,
83
+ params.method,
84
+ args,
85
+ params.kwargs,
86
+ ],
87
+ }, { model: params.model, method: params.method }, { model: params.model, method: params.method });
88
+ }).pipe(
89
+ // Self-healing: an auth fault means the cached uid/credential pairing is
90
+ // no longer valid server-side (key revoked-then-restored, credentials
91
+ // out of sync, ...). Drop the cache so the next call re-authenticates
92
+ // instead of staying wedged until the layer is rebuilt — symmetric with
93
+ // CookieSession.invalidate.
94
+ Effect.tapError((error) => error._tag === "OdooAuthenticationError" ? uid.invalidate : Effect.void));
95
+ return { dialect: "execute-kw", callKw };
96
+ });
97
+ /**
98
+ * `JsonRpcTransport` as a `Layer`, wired against an explicit config. Never
99
+ * fails to build (`E = never`); the config is already resolved. Requires an
100
+ * `HttpClient` from the platform layer the consumer provides.
101
+ */
102
+ export const layer = (config) => Layer.effect(Transport, make(config));
103
+ /**
104
+ * `JsonRpcTransport` as a `Layer` that reads {@link OdooConfig} from the ambient
105
+ * `ConfigProvider`. Fails with `ConfigError` if required values are missing.
106
+ */
107
+ export const layerConfig = Layer.effect(Transport, Effect.flatMap(OdooConfig, make));
108
+ /**
109
+ * The `common.version` probe, decoded into {@link CommonVersionResponse}. Shared
110
+ * with the `VersionResolver` implementation so version resolution and the
111
+ * transport agree on one wire path. Requires an `HttpClient` in context.
112
+ */
113
+ export const makeVersion = (config) => Effect.gen(function* () {
114
+ const client = yield* HttpClient.HttpClient;
115
+ const endpoint = jsonRpcEndpoint(config.url);
116
+ const raw = yield* roundTrip(client, endpoint, 1, { service: "common", method: "version", args: [] }, {}, {});
117
+ return yield* Schema.decodeUnknownEffect(CommonVersionResponse)(raw).pipe(Effect.mapError((cause) => new SchemaDriftError({ context: "odoo.jsonrpc common.version", payload: raw, cause })));
118
+ });
119
+ //# sourceMappingURL=jsonrpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonrpc.js","sourceRoot":"","sources":["../../src/transports/jsonrpc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type Config, Effect, Layer } from "effect";
2
+ import { OdooConfig } from "../config.js";
3
+ import { CookieSession } from "../session/cookie.js";
4
+ import { Transport } from "../transport.js";
5
+ /**
6
+ * Build the `WebTransport` implementation. Every `call_kw` first ensures a
7
+ * cookie session exists (via {@link CookieSession}) and then rides the shared,
8
+ * cookie-bound HttpClient so `session_id` — and any rotation — is always sent.
9
+ */
10
+ export declare const make: (config: Pick<OdooConfig, "url">) => Effect.Effect<typeof Transport.Service, never, CookieSession>;
11
+ /** Provide the web `Transport` from an already-resolved config. Requires `CookieSession`. */
12
+ export declare const layer: (config: Pick<OdooConfig, "url">) => Layer.Layer<Transport, never, CookieSession>;
13
+ /** Provide the web `Transport`, resolving `OdooConfig` from the environment. */
14
+ export declare const layerConfig: Layer.Layer<Transport, Config.ConfigError, CookieSession>;
15
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/transports/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAQrD,OAAO,EAAqB,SAAS,EAA2B,MAAM,iBAAiB,CAAC;AAExF;;;;GAIG;AACH,eAAO,MAAM,IAAI,WAIP,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,KAC9B,MAAM,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CA6D3D,CAAC;AAEL,6FAA6F;AAC7F,eAAO,MAAM,KAAK,WACR,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,KAC9B,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,aAAa,CAA0C,CAAC;AAEzF,gFAAgF;AAChF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE,aAAa,CACvB,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { Effect, Layer, Schema } from "effect";
2
+ import { OdooConfig } from "../config.js";
3
+ import { SchemaDriftError } from "../errors/schema.js";
4
+ import { OdooTransportError } from "../errors/transport.js";
5
+ import { HttpClientRequest } from "../internal/platform.js";
6
+ import { CookieSession } from "../session/cookie.js";
7
+ import { joinPath } from "../session/cookie.js";
8
+ import { buildRequest, JsonRpcResponse, mapJsonRpcError, nextRequestId, } from "../protocol/jsonrpc.js";
9
+ import { Transport } from "../transport.js";
10
+ /**
11
+ * Build the `WebTransport` implementation. Every `call_kw` first ensures a
12
+ * cookie session exists (via {@link CookieSession}) and then rides the shared,
13
+ * cookie-bound HttpClient so `session_id` — and any rotation — is always sent.
14
+ */
15
+ export const make = (
16
+ // Only `url` is read: the transport never touches credentials, so an
17
+ // injected-cookie consumer (CookieSessionLive.fromExisting) can drive it
18
+ // without fabricating an OdooConfig. A full OdooConfig still satisfies this.
19
+ config) => Effect.gen(function* () {
20
+ const session = yield* CookieSession;
21
+ const callUrl = joinPath(config.url, "web/dataset/call_kw");
22
+ const request = { method: "POST", url: callUrl };
23
+ const callKw = (params) => Effect.gen(function* () {
24
+ // Choke point: ensure the session before the call. A SessionExpiredError
25
+ // from here does NOT auto-relogin (see retryOnSessionExpired).
26
+ yield* session.login;
27
+ // execute_kw-family route: an explicit `ids` browse target rides as the
28
+ // first positional argument (see CallKwParams.ids).
29
+ const args = params.ids ? [params.ids, ...params.args] : params.args;
30
+ const envelope = buildRequest({
31
+ model: params.model,
32
+ method: params.method,
33
+ args,
34
+ kwargs: params.kwargs,
35
+ }, nextRequestId());
36
+ const response = yield* session.client
37
+ .execute(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.post(callUrl), envelope))
38
+ .pipe(Effect.mapError((cause) => OdooTransportError.fromHttpClientError(request, cause)));
39
+ const body = yield* response.json.pipe(Effect.mapError((cause) => OdooTransportError.fromHttpClientError(request, cause)));
40
+ const decoded = yield* Schema.decodeUnknownEffect(JsonRpcResponse)(body).pipe(Effect.mapError((cause) => new SchemaDriftError({
41
+ context: "web/dataset/call_kw envelope",
42
+ payload: body,
43
+ cause,
44
+ })));
45
+ if ("error" in decoded) {
46
+ // Single mapping choke point: code 100 → SessionExpiredError, else
47
+ // mapServerFault, with the call site threaded through.
48
+ return yield* Effect.fail(mapJsonRpcError(decoded.error, { model: params.model, method: params.method }));
49
+ }
50
+ return decoded.result;
51
+ }).pipe(
52
+ // No args/secrets in span attributes — model + method only.
53
+ Effect.withSpan("odoo.web.call_kw", {
54
+ attributes: { model: params.model, method: params.method },
55
+ }));
56
+ return { dialect: "execute-kw", callKw };
57
+ });
58
+ /** Provide the web `Transport` from an already-resolved config. Requires `CookieSession`. */
59
+ export const layer = (config) => Layer.effect(Transport, make(config));
60
+ /** Provide the web `Transport`, resolving `OdooConfig` from the environment. */
61
+ export const layerConfig = Layer.effect(Transport, Effect.flatMap(OdooConfig, make));
62
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/transports/web.ts"],"names":[],"mappings":"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"}