@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,42 @@
1
+ import type { HttpClientErrorType } from "../internal/platform.js";
2
+ /** The stable platform failure distinction, without the unsafe platform error. */
3
+ export type TransportErrorKind = HttpClientErrorType["reason"]["_tag"];
4
+ /**
5
+ * Sanitized snapshot of the outbound request, safe to log and to attach to
6
+ * errors. The URL has no userinfo, query, or fragment. Never carries auth
7
+ * headers, cookies, or the request body — those may hold `session_id`, API
8
+ * keys, or passwords.
9
+ */
10
+ export interface RequestInfo {
11
+ readonly method: string;
12
+ readonly url: string;
13
+ }
14
+ declare const OdooTransportError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
15
+ readonly _tag: "OdooTransportError";
16
+ } & Readonly<A>;
17
+ /**
18
+ * The network / HTTP layer failed: DNS, TLS, connection reset, non-decodable
19
+ * response, timeout. Carries only primitive metadata copied from the platform
20
+ * error. It never retains the `HttpClientError`, its request, response, cause,
21
+ * headers, or body.
22
+ *
23
+ * A transport failure proves only that *something* failed at the wire — it
24
+ * says nothing about Odoo's application state. Never infer success/idempotency
25
+ * from it; probe actual state before any resume.
26
+ */
27
+ export declare class OdooTransportError extends OdooTransportError_base<{
28
+ readonly request: RequestInfo;
29
+ readonly kind: TransportErrorKind;
30
+ readonly message: string;
31
+ readonly status?: number;
32
+ }> {
33
+ constructor(fields: {
34
+ readonly request: RequestInfo;
35
+ readonly kind: TransportErrorKind;
36
+ readonly status?: number;
37
+ });
38
+ /** Copy safe diagnostics out of an Effect HTTP error, then discard it. */
39
+ static fromHttpClientError(request: RequestInfo, error: HttpClientErrorType): OdooTransportError;
40
+ }
41
+ export {};
42
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/errors/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,kFAAkF;AAClF,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;;;;AAeD;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,SAAQ,wBAAuC;IAC7E,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;IACA,YAAY,MAAM,EAAE;QAClB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;QAC9B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;QAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,EAYA;IAED,0EAA0E;IAC1E,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,GAAG,kBAAkB,CAO/F;CACF"}
@@ -0,0 +1,49 @@
1
+ import { Data } from "effect";
2
+ const sanitizeUrl = (raw) => {
3
+ try {
4
+ const url = new URL(raw);
5
+ url.username = "";
6
+ url.password = "";
7
+ url.search = "";
8
+ url.hash = "";
9
+ return url.href;
10
+ }
11
+ catch {
12
+ return "<invalid-url>";
13
+ }
14
+ };
15
+ /**
16
+ * The network / HTTP layer failed: DNS, TLS, connection reset, non-decodable
17
+ * response, timeout. Carries only primitive metadata copied from the platform
18
+ * error. It never retains the `HttpClientError`, its request, response, cause,
19
+ * headers, or body.
20
+ *
21
+ * A transport failure proves only that *something* failed at the wire — it
22
+ * says nothing about Odoo's application state. Never infer success/idempotency
23
+ * from it; probe actual state before any resume.
24
+ */
25
+ export class OdooTransportError extends Data.TaggedError("OdooTransportError") {
26
+ constructor(fields) {
27
+ const request = {
28
+ method: fields.request.method,
29
+ url: sanitizeUrl(fields.request.url),
30
+ };
31
+ const suffix = fields.status === undefined ? "" : ` (HTTP ${fields.status})`;
32
+ super({
33
+ request,
34
+ kind: fields.kind,
35
+ message: `${fields.kind} during ${request.method} ${request.url}${suffix}`,
36
+ ...(fields.status !== undefined ? { status: fields.status } : {}),
37
+ });
38
+ }
39
+ /** Copy safe diagnostics out of an Effect HTTP error, then discard it. */
40
+ static fromHttpClientError(request, error) {
41
+ const status = error.response?.status;
42
+ return new OdooTransportError({
43
+ request,
44
+ kind: error.reason._tag,
45
+ ...(status !== undefined ? { status } : {}),
46
+ });
47
+ }
48
+ }
49
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/errors/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAiB9B,MAAM,WAAW,GAAG,CAAC,GAAW,EAAU,EAAE;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;QAClB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;QAClB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,OAAO,kBAAmB,SAAQ,IAAI,CAAC,WAAW,CAAC,oBAAoB,CAK3E;IACA,YAAY,MAIX;QACC,MAAM,OAAO,GAAG;YACd,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;YAC7B,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;SACrC,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,MAAM,GAAG,CAAC;QAC7E,KAAK,CAAC;YACJ,OAAO;YACP,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,WAAW,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,GAAG,MAAM,EAAE;YAC1E,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;IACL,CAAC;IAED,0EAA0E;IAC1E,MAAM,CAAC,mBAAmB,CAAC,OAAoB,EAAE,KAA0B;QACzE,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC;QACtC,OAAO,IAAI,kBAAkB,CAAC;YAC5B,OAAO;YACP,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;YACvB,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @hodeinavarro/odoo-rpc-ts — public surface.
3
+ *
4
+ * A modern, Effect-native, strongly-typed Odoo RPC client: the shared
5
+ * contracts (errors, the `Transport` seam, config, domains, version
6
+ * resolution), the three transports, the session/auth services, and the
7
+ * high-level `OdooClient`. The `FakeTransport` testing double ships from the
8
+ * separate `@hodeinavarro/odoo-rpc-ts/testing` entry point.
9
+ */
10
+ export * from "./errors/index.js";
11
+ export { Transport, type CallKwParams, type TransportCallError } from "./transport.js";
12
+ export { OdooConfig, type OdooCredentials } from "./config.js";
13
+ export { AND, OR, NOT, normalizeDomain, type Domain, type DomainItem, type DomainLeaf, type DomainOperator, } from "./domain.js";
14
+ export { VersionResolver, CommonVersionResponse, ServerVersionInfo, parseVersionInfo, deriveWireCapabilities, type OdooVersion, type WireCapabilities, type ResolvedVersion, } from "./version.js";
15
+ export * as VersionResolverLive from "./version-live.js";
16
+ export { Rpc, GlobalContext, type OdooContext, type GlobalContextProvider } from "./rpc.js";
17
+ export * as RpcLive from "./rpc.js";
18
+ export { OdooClient, type OdooRecord, type FieldsMetadata, type SearchOptions, type SearchReadOptions, type CallOptions, type ReadGroupOptions, type NameSearchOptions, type TypedOptions, type TypedReadOptions, } from "./client.js";
19
+ export * as OdooClientLive from "./client.js";
20
+ export { Many2OneRef, Many2OneRefFromWire, Many2OneRefOrNull, Many2OneRefValue, OdooDate, OdooDateOrNull, OdooDateTime, OdooDateTimeOrNull, collectRefIds, makeRelatedMap, makeTypedRecordSet, refId, type HasId, type Many2OneRefField, type RefOrId, type RelatedMap, type TypedRecordSet, defineRecord, Many2One, One2Many, compileSpecification, hasRelations, EmptyRecordSpecError, RecordSpecCycleError, type FieldInput, type FieldMeta, type Many2OneDecl, type One2ManyDecl, type RecordSpec, type RowEncoded, type RowType, } from "./records/index.js";
21
+ export * as JsonRpcTransport from "./transports/jsonrpc.js";
22
+ export * as Json2Transport from "./transports/json2.js";
23
+ export * as WebTransport from "./transports/web.js";
24
+ export { CookieSession, type CookieSessionService, type CookieLoginError, type ExistingSessionOptions, type OdooSessionInfo, type RawHttpOptions, } from "./session/cookie.js";
25
+ export * as CookieSessionLive from "./session/cookie.js";
26
+ export { retryOnSessionExpired } from "./combinators/retryOnSessionExpired.js";
27
+ export { Command, type CommandTuple, type X2ManyCommands, } from "./commands.js";
28
+ export * as DbService from "./services/db.js";
29
+ export type { CreateOptions, DuplicateOptions, RestoreOptions } from "./services/db.js";
30
+ export { ReportService, type ReportAction, type DownloadOptions } from "./services/report.js";
31
+ export { Profiles, SecretStore, ProfileStorage, InMemorySecretStore, InMemoryProfileStorage, ProfileStoreError, ProfileSecretMissingError, type ProfileData, type CredentialProfileData, type SessionProfileData, type LoadedProfile, type ProfileProtocol, } from "./profiles.js";
32
+ export * as ProfilesLive from "./profiles.js";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @hodeinavarro/odoo-rpc-ts — public surface.
3
+ *
4
+ * A modern, Effect-native, strongly-typed Odoo RPC client: the shared
5
+ * contracts (errors, the `Transport` seam, config, domains, version
6
+ * resolution), the three transports, the session/auth services, and the
7
+ * high-level `OdooClient`. The `FakeTransport` testing double ships from the
8
+ * separate `@hodeinavarro/odoo-rpc-ts/testing` entry point.
9
+ */
10
+ // Error taxonomy + the fault mapper.
11
+ export * from "./errors/index.js";
12
+ // The one transport seam and its call shape.
13
+ export { Transport } from "./transport.js";
14
+ // Configuration.
15
+ export { OdooConfig } from "./config.js";
16
+ // Search-domain types and combinators.
17
+ export { AND, OR, NOT, normalizeDomain, } from "./domain.js";
18
+ // Version resolution and WIRE-capability derivation (the app layer owns
19
+ // product capabilities — see WireCapabilities).
20
+ export { VersionResolver, CommonVersionResponse, ServerVersionInfo, parseVersionInfo, deriveWireCapabilities, } from "./version.js";
21
+ export * as VersionResolverLive from "./version-live.js";
22
+ // The call_kw choke point (context merge) and the high-level client.
23
+ export { Rpc, GlobalContext } from "./rpc.js";
24
+ export * as RpcLive from "./rpc.js";
25
+ export { OdooClient, } from "./client.js";
26
+ export * as OdooClientLive from "./client.js";
27
+ // Typed records — two per-protocol tiers, not competitors. The CLASSIC tier
28
+ // (explicit traversal: Many2OneRef*, TypedRecordSet) speaks the classic
29
+ // `[id, name]`-pair protocol and works on 16+; the SPEC tier (declared
30
+ // prefetch: defineRecord/Many2One/One2Many + the pure specification compiler)
31
+ // speaks the 17+ `specification` protocol. Relation/temporal decode
32
+ // vocabulary is shared.
33
+ export {
34
+ // Relation & temporal decode schemas (classic-tier row declarations).
35
+ Many2OneRef, Many2OneRefFromWire, Many2OneRefOrNull, Many2OneRefValue, OdooDate, OdooDateOrNull, OdooDateTime, OdooDateTimeOrNull,
36
+ // Pure join helpers + the classic-tier explicit-traversal snapshot.
37
+ collectRefIds, makeRelatedMap, makeTypedRecordSet, refId,
38
+ // Spec-tier model declaration + compilation (17+ `specification` protocol).
39
+ defineRecord, Many2One, One2Many, compileSpecification, hasRelations, EmptyRecordSpecError, RecordSpecCycleError, } from "./records/index.js";
40
+ // Transports — one Layer per protocol, all implementing the Transport tag.
41
+ export * as JsonRpcTransport from "./transports/jsonrpc.js";
42
+ export * as Json2Transport from "./transports/json2.js";
43
+ export * as WebTransport from "./transports/web.js";
44
+ // Cookie session + opt-in session-expiry recovery.
45
+ export { CookieSession, } from "./session/cookie.js";
46
+ export * as CookieSessionLive from "./session/cookie.js";
47
+ export { retryOnSessionExpired } from "./combinators/retryOnSessionExpired.js";
48
+ // Typed x2many write commands.
49
+ export { Command, } from "./commands.js";
50
+ // Database administration (master-password gated; Node-oriented buffers).
51
+ export * as DbService from "./services/db.js";
52
+ // Report downloads over a cookie session.
53
+ export { ReportService } from "./services/report.js";
54
+ // Named connection profiles — secrets never serialize; storage is yours.
55
+ export { Profiles, SecretStore, ProfileStorage, InMemorySecretStore, InMemoryProfileStorage, ProfileStoreError, ProfileSecretMissingError, } from "./profiles.js";
56
+ export * as ProfilesLive from "./profiles.js";
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,qCAAqC;AACrC,cAAc,mBAAmB,CAAC;AAElC,6CAA6C;AAC7C,OAAO,EAAE,SAAS,EAA8C,MAAM,gBAAgB,CAAC;AAEvF,iBAAiB;AACjB,OAAO,EAAE,UAAU,EAAwB,MAAM,aAAa,CAAC;AAE/D,uCAAuC;AACvC,OAAO,EACL,GAAG,EACH,EAAE,EACF,GAAG,EACH,eAAe,GAKhB,MAAM,aAAa,CAAC;AAErB,wEAAwE;AACxE,gDAAgD;AAChD,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,GAIvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,mBAAmB,MAAM,mBAAmB,CAAC;AAEzD,qEAAqE;AACrE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAgD,MAAM,UAAU,CAAC;AAC5F,OAAO,KAAK,OAAO,MAAM,UAAU,CAAC;AACpC,OAAO,EACL,UAAU,GAUX,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,cAAc,MAAM,aAAa,CAAC;AAE9C,4EAA4E;AAC5E,wEAAwE;AACxE,uEAAuE;AACvE,8EAA8E;AAC9E,oEAAoE;AACpE,wBAAwB;AACxB,OAAO;AACL,sEAAsE;AACtE,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,kBAAkB;AAClB,oEAAoE;AACpE,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,KAAK;AAML,4EAA4E;AAC5E,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,GAQrB,MAAM,oBAAoB,CAAC;AAE5B,2EAA2E;AAC3E,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AAEpD,mDAAmD;AACnD,OAAO,EACL,aAAa,GAMd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,iBAAiB,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAE/E,+BAA+B;AAC/B,OAAO,EACL,OAAO,GAGR,MAAM,eAAe,CAAC;AAEvB,0EAA0E;AAC1E,OAAO,KAAK,SAAS,MAAM,kBAAkB,CAAC;AAG9C,0CAA0C;AAC1C,OAAO,EAAE,aAAa,EAA2C,MAAM,sBAAsB,CAAC;AAE9F,yEAAyE;AACzE,OAAO,EACL,QAAQ,EACR,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,yBAAyB,GAM1B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,YAAY,MAAM,eAAe,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The single funnel for everything we consume from the platform HTTP layer.
3
+ *
4
+ * Every other module in this package imports platform values and types
5
+ * from HERE — never directly from `effect/unstable/http`. This kept the
6
+ * blast radius of the platform v4 move (v3 `@effect/platform` →
7
+ * `effect/unstable/http`) to exactly this file, and documents the exact
8
+ * platform surface the client depends on.
9
+ */
10
+ export { Cookies, FetchHttpClient, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
11
+ export type { HttpClientError as HttpClientErrorType } from "effect/unstable/http/HttpClientError";
12
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/internal/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,OAAO,EACP,eAAe,EACf,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAI9B,YAAY,EAAE,eAAe,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The single funnel for everything we consume from the platform HTTP layer.
3
+ *
4
+ * Every other module in this package imports platform values and types
5
+ * from HERE — never directly from `effect/unstable/http`. This kept the
6
+ * blast radius of the platform v4 move (v3 `@effect/platform` →
7
+ * `effect/unstable/http`) to exactly this file, and documents the exact
8
+ * platform surface the client depends on.
9
+ */
10
+ export { Cookies, FetchHttpClient, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
11
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/internal/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,OAAO,EACP,eAAe,EACf,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { Effect, Option } from "effect";
2
+ /** A memoized `get` whose underlying `acquire` runs at most once on success. */
3
+ export interface SingleFlight<A, E, R> {
4
+ /**
5
+ * Return the cached value, or acquire it. Concurrent callers race a single
6
+ * permit so `acquire` runs once; a failure caches nothing, so the next call
7
+ * retries. This is the success-only single-flight AGENTS.md mandates in place
8
+ * of `Effect.cached` (which would also memoize failures).
9
+ */
10
+ readonly get: Effect.Effect<A, E, R>;
11
+ /**
12
+ * Drop the cached value so the next `get` re-acquires.
13
+ *
14
+ * GOTCHA: an acquire already in flight when `invalidate` runs will complete
15
+ * and be returned to ITS caller, but is NOT cached — a generation counter
16
+ * guards the write, so a stale result can never resurrect the cache.
17
+ */
18
+ readonly invalidate: Effect.Effect<void>;
19
+ /** The current cached value, without triggering an acquire. */
20
+ readonly peek: Effect.Effect<Option.Option<A>>;
21
+ }
22
+ /**
23
+ * Build a success-only single-flight cache around `acquire`.
24
+ *
25
+ * Invariant (AGENTS.md): only successful results are cached. A failed `acquire`
26
+ * leaves the cache empty so callers can retry — never `Effect.cached`.
27
+ */
28
+ export declare const make: <A, E, R>(acquire: Effect.Effect<A, E, R>) => Effect.Effect<SingleFlight<A, E, R>, never, never>;
29
+ //# sourceMappingURL=singleFlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleFlight.d.ts","sourceRoot":"","sources":["../../src/internal/singleFlight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAExD,gFAAgF;AAChF,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;CAChD;AAOD;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,WACjB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC9B,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CA2ChD,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { Effect, Option, Ref, Semaphore } from "effect";
2
+ /**
3
+ * Build a success-only single-flight cache around `acquire`.
4
+ *
5
+ * Invariant (AGENTS.md): only successful results are cached. A failed `acquire`
6
+ * leaves the cache empty so callers can retry — never `Effect.cached`.
7
+ */
8
+ export const make = (acquire) => Effect.gen(function* () {
9
+ const ref = yield* Ref.make({ generation: 0, value: Option.none() });
10
+ const semaphore = yield* Semaphore.make(1);
11
+ const get = Effect.gen(function* () {
12
+ // Fast path: already cached, no lock needed.
13
+ const cached = yield* Ref.get(ref);
14
+ if (Option.isSome(cached.value)) {
15
+ return cached.value.value;
16
+ }
17
+ // Slow path: serialize acquisition behind the single permit.
18
+ return yield* semaphore.withPermits(1)(Effect.gen(function* () {
19
+ // Double-check: a racing caller may have filled the cache while we
20
+ // waited for the permit.
21
+ const state = yield* Ref.get(ref);
22
+ if (Option.isSome(state.value)) {
23
+ return state.value.value;
24
+ }
25
+ const startGeneration = state.generation;
26
+ const value = yield* acquire;
27
+ // Cache only if no invalidate happened while we were acquiring.
28
+ yield* Ref.update(ref, (current) => current.generation === startGeneration
29
+ ? { generation: current.generation, value: Option.some(value) }
30
+ : current);
31
+ return value;
32
+ }));
33
+ });
34
+ const invalidate = Ref.update(ref, (current) => ({
35
+ generation: current.generation + 1,
36
+ value: Option.none(),
37
+ }));
38
+ const peek = Ref.get(ref).pipe(Effect.map((state) => state.value));
39
+ return { get, invalidate, peek };
40
+ });
41
+ //# sourceMappingURL=singleFlight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleFlight.js","sourceRoot":"","sources":["../../src/internal/singleFlight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AA4BxD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,OAA+B,EACqB,EAAE,CACtD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAW,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE3C,MAAM,GAAG,GAA2B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACtD,6CAA6C;QAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;QAC5B,CAAC;QAED,6DAA6D;QAC7D,OAAO,KAAK,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CACpC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,mEAAmE;YACnE,yBAAyB;YACzB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;YAC3B,CAAC;YAED,MAAM,eAAe,GAAG,KAAK,CAAC,UAAU,CAAC;YACzC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC;YAC7B,gEAAgE;YAChE,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CACjC,OAAO,CAAC,UAAU,KAAK,eAAe;gBACpC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBAC/D,CAAC,CAAC,OAAO,CACZ,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC/C,UAAU,EAAE,OAAO,CAAC,UAAU,GAAG,CAAC;QAClC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAK;KACxB,CAAC,CAAC,CAAC;IAEJ,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAEnE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACnC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,179 @@
1
+ import { Context, Effect, Layer, Option, type Redacted } from "effect";
2
+ import type { OdooConfig } from "./config.js";
3
+ /**
4
+ * Which transport a saved profile targets. Mirrors the three seams behind the
5
+ * `Transport` tag (`web`, `/jsonrpc`, JSON-2); persisted as a stable string so a
6
+ * stored profile survives refactors of the internal dialect enum.
7
+ */
8
+ export type ProfileProtocol = "json-rpc" | "json-2" | "web";
9
+ /**
10
+ * The serializable, **secret-free** metadata of a credential-backed preset.
11
+ * `credentialKind` records which arm of {@link OdooCredentials} to
12
+ * reconstruct, so the secret can be re-homed without guessing.
13
+ */
14
+ export interface CredentialProfileData {
15
+ readonly url: string;
16
+ readonly db: string;
17
+ readonly username: string;
18
+ readonly credentialKind: "api-key" | "password";
19
+ readonly protocol: ProfileProtocol;
20
+ }
21
+ /**
22
+ * The serializable, **secret-free** metadata of a harvested-session preset:
23
+ * the stored secret is the `session_id` cookie value itself, not a credential
24
+ * (the flow exists precisely because there ARE no credentials — TOTP/SSO
25
+ * accounts cannot password-auth). No `db`/`username`: the server binds both
26
+ * to the cookie, and `get_session_info` reports them live. A session rides
27
+ * the cookie-authenticated `/web` routes, so `protocol` is always `"web"`.
28
+ */
29
+ export interface SessionProfileData {
30
+ readonly url: string;
31
+ readonly credentialKind: "session";
32
+ readonly protocol: "web";
33
+ }
34
+ /**
35
+ * The serializable, **secret-free** part of a connection preset. This is the
36
+ * ONLY shape {@link ProfileStorage} ever sees — the secret (credential or
37
+ * session cookie) lives in {@link SecretStore} and is never a field here.
38
+ * Discriminated on `credentialKind`.
39
+ */
40
+ export type ProfileData = CredentialProfileData | SessionProfileData;
41
+ declare const ProfileStoreError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
42
+ readonly _tag: "ProfileStoreError";
43
+ } & Readonly<A>;
44
+ /**
45
+ * A profile's storage operation failed at the consumer-supplied backend (keyring
46
+ * denied, IndexedDB transaction aborted, file unwritable, …). Wraps the opaque
47
+ * platform `cause`; upper layers branch on `_tag`, never on the message. A store
48
+ * failure proves only that *that* operation failed — infer nothing about the
49
+ * other backend's state (see the removal ordering in {@link make}).
50
+ */
51
+ export declare class ProfileStoreError extends ProfileStoreError_base<{
52
+ /** The logical operation that failed, e.g. `"secret.set"` / `"storage.load"`. */
53
+ readonly operation: string;
54
+ readonly cause: unknown;
55
+ }> {
56
+ }
57
+ declare const ProfileSecretMissingError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
58
+ readonly _tag: "ProfileSecretMissingError";
59
+ } & Readonly<A>;
60
+ /**
61
+ * A profile's metadata was found but its credential secret is not reachable
62
+ * through {@link SecretStore} (the secret was never stored, was removed out of
63
+ * band, or the named profile does not exist at all). Distinct from
64
+ * {@link ProfileStoreError}: the backend answered fine — the secret is simply
65
+ * absent, so the {@link OdooConfig} cannot be reconstructed.
66
+ */
67
+ export declare class ProfileSecretMissingError extends ProfileSecretMissingError_base<{
68
+ readonly name: string;
69
+ /** The keyring account that was probed: `${name}:${credentialKind}`. */
70
+ readonly account: string;
71
+ }> {
72
+ }
73
+ declare const SecretStore_base: Context.ServiceClass<SecretStore, "odoo-rpc-ts/SecretStore", {
74
+ readonly get: (account: string) => Effect.Effect<Option.Option<Redacted.Redacted<string>>, ProfileStoreError>;
75
+ readonly set: (account: string, secret: Redacted.Redacted<string>) => Effect.Effect<void, ProfileStoreError>;
76
+ readonly remove: (account: string) => Effect.Effect<void, ProfileStoreError>;
77
+ }>;
78
+ /**
79
+ * Consumer-implemented secret vault, keyed by an opaque `account` string. The
80
+ * platform-portable seam: an OS keyring in Node, IndexedDB + WebCrypto in a
81
+ * browser, a `Ref`-backed map in tests. Secrets cross this boundary only as
82
+ * `Redacted` — never a bare string — so they cannot leak into logs or JSON.
83
+ */
84
+ export declare class SecretStore extends SecretStore_base {
85
+ }
86
+ declare const ProfileStorage_base: Context.ServiceClass<ProfileStorage, "odoo-rpc-ts/ProfileStorage", {
87
+ readonly load: () => Effect.Effect<Record<string, ProfileData>, ProfileStoreError>;
88
+ readonly save: (profiles: Record<string, ProfileData>) => Effect.Effect<void, ProfileStoreError>;
89
+ }>;
90
+ /**
91
+ * Consumer-implemented store for the secret-free {@link ProfileData} map, keyed
92
+ * by profile name. A JSON file on disk, `localStorage`, a config table — anything
93
+ * that round-trips a `Record<string, ProfileData>`. Never carries secrets.
94
+ */
95
+ export declare class ProfileStorage extends ProfileStorage_base {
96
+ }
97
+ /**
98
+ * What {@link Profiles.loadProfile} hands back — tagged so the consumer can
99
+ * tell a credential preset from a harvested-session preset WITHOUT this
100
+ * service ever fabricating an {@link OdooConfig} around a fake credential:
101
+ *
102
+ * - `"credentials"` — a real config; feed it to `EphemeralAuth` /
103
+ * `CookieSessionLive.layerConfig` as usual.
104
+ * - `"session"` — no credentials exist; feed `url` + `sessionId` to
105
+ * `CookieSessionLive.fromExisting` (its {@link ExistingSessionOptions}
106
+ * shape) over the web transport.
107
+ */
108
+ export type LoadedProfile = {
109
+ readonly _tag: "credentials";
110
+ readonly config: OdooConfig;
111
+ readonly protocol: ProfileProtocol;
112
+ } | {
113
+ readonly _tag: "session";
114
+ readonly url: URL;
115
+ readonly sessionId: Redacted.Redacted<string>;
116
+ readonly protocol: "web";
117
+ };
118
+ declare const Profiles_base: Context.ServiceClass<Profiles, "odoo-rpc-ts/Profiles", {
119
+ /**
120
+ * Persist a preset: writes the secret to {@link SecretStore} first, then the
121
+ * secret-free {@link ProfileData} to {@link ProfileStorage} (secret-before-
122
+ * metadata so stored metadata never predates its secret). Overwrites any
123
+ * existing profile of the same name.
124
+ */
125
+ readonly saveProfile: (name: string, config: OdooConfig, protocol: ProfileProtocol) => Effect.Effect<void, ProfileStoreError>;
126
+ /**
127
+ * Persist a harvested-session preset: the `session_id` cookie value is
128
+ * the stored secret (same secret-before-metadata ordering as
129
+ * {@link saveProfile}). No credentials are involved — this is the flow
130
+ * for TOTP/SSO accounts whose cookie was minted by a real `/web/login`
131
+ * (e.g. an embedded login window). Overwrites any existing profile of
132
+ * the same name.
133
+ */
134
+ readonly saveSessionProfile: (name: string, url: URL, sessionId: Redacted.Redacted<string>) => Effect.Effect<void, ProfileStoreError>;
135
+ /**
136
+ * Reconstruct a preset: reads metadata, then resolves the secret from
137
+ * {@link SecretStore}. A credential preset rebuilds the {@link OdooConfig};
138
+ * a session preset returns the harvested cookie — see {@link LoadedProfile}
139
+ * for how to consume each arm. Fails with {@link ProfileSecretMissingError}
140
+ * when the profile is unknown or its secret is absent — never returns a
141
+ * config or session with a placeholder secret.
142
+ */
143
+ readonly loadProfile: (name: string) => Effect.Effect<LoadedProfile, ProfileStoreError | ProfileSecretMissingError>;
144
+ /** All stored presets by name — secret-free by construction. */
145
+ readonly listProfiles: () => Effect.Effect<Record<string, ProfileData>, ProfileStoreError>;
146
+ /**
147
+ * Delete a preset. Removes the secret FIRST, then the metadata (see the
148
+ * ordering rationale in {@link make}). A no-op for an unknown name.
149
+ */
150
+ readonly removeProfile: (name: string) => Effect.Effect<void, ProfileStoreError>;
151
+ }>;
152
+ /**
153
+ * Named connection profiles over a {@link SecretStore} + {@link ProfileStorage}
154
+ * seam. The metadata (URL, db, username, protocol) is serializable and travels
155
+ * through `ProfileStorage`; the credential secret travels *only* through
156
+ * `SecretStore` as `Redacted`. Serializing what this service persists can never
157
+ * expose secret material.
158
+ */
159
+ export declare class Profiles extends Profiles_base {
160
+ }
161
+ /** Hand-written {@link Profiles} layer over the two storage seams. */
162
+ export declare const layer: Layer.Layer<Profiles, never, SecretStore | ProfileStorage>;
163
+ /**
164
+ * Ready-made, `Ref`-backed {@link SecretStore} for tests and single-process
165
+ * consumers. Each `.layer` build gets its own isolated map — no shared global
166
+ * state between tests.
167
+ */
168
+ export declare const InMemorySecretStore: {
169
+ readonly layer: Layer.Layer<SecretStore, never, never>;
170
+ };
171
+ /**
172
+ * Ready-made, `Ref`-backed {@link ProfileStorage} for tests and single-process
173
+ * consumers. Isolated per `.layer` build, like {@link InMemorySecretStore}.
174
+ */
175
+ export declare const InMemoryProfileStorage: {
176
+ readonly layer: Layer.Layer<ProfileStorage, never, never>;
177
+ };
178
+ export {};
179
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"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"}