@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,101 @@
1
+ /**
2
+ * Declared record models — the SPEC tier of the records design ("declared
3
+ * prefetch" over the 17+ `specification` protocol; the classic tier in
4
+ * `typed.ts`/`related.ts` speaks the 16+ `[id, name]`-pair protocol instead).
5
+ * A `RecordSpec`
6
+ * declares a model's field graph with `effect/Schema`: scalars decode directly,
7
+ * relations declared with {@link Many2One}/{@link One2Many} carry a nested child
8
+ * `RecordSpec`. From one declaration we derive BOTH:
9
+ *
10
+ * 1. a row {@link RecordSpec.schema} that strict-decodes the NESTED wire shape
11
+ * the 17+ `web_search_read`/`web_read` `specification` protocol returns
12
+ * (a many2one as a `{ id, ...declared }` dict or `false`; an x2many as a
13
+ * nested list), and
14
+ * 2. the field {@link RecordSpec.fields} metadata that {@link ./spec.ts} compiles
15
+ * into that `specification` payload.
16
+ *
17
+ * No I/O lives here — this is pure declaration. The single round trip that turns
18
+ * a spec into rows is in `OdooClient.searchTyped`/`readTyped`/`saveTyped`.
19
+ *
20
+ * MANY2ONE VALUE SHAPE (decision, records design 2026-07-10). A declared
21
+ * many2one decodes to a FLAT struct `{ id, ...declaredChildFields } | null`, not
22
+ * a `{ id, value }` wrapper. In TS, `| null` already carries "is it set?", and a
23
+ * flat struct exposes `.id` and every declared child field as directly-typed
24
+ * properties (`company.name`) with no `__getattr__`-style proxy and no extra
25
+ * `.value` hop — strictly better inference and ergonomics than a wrapper. This
26
+ * deliberately diverges from the Python design's `.value` recommendation, which
27
+ * existed to model presence separately from the record; TS does not need it.
28
+ */
29
+ import { Schema, SchemaTransformation } from "effect";
30
+ /**
31
+ * Declare a many2one field: `company_id: Many2One(Company)`. Decodes the nested
32
+ * `{ id, ...Company fields }` wire dict to `Company | null` (Odoo's `false`
33
+ * empty → `null`). Note it does NOT accept the classic `[id, name]` pair — under
34
+ * a `specification` the server returns a dict, so a leaked pair is drift, not a
35
+ * silent decode.
36
+ */
37
+ export const Many2One = (spec) => ({
38
+ _decl: "many2one",
39
+ spec,
40
+ });
41
+ /**
42
+ * Declare an x2many field: `child_ids: One2Many(Contact)` or
43
+ * `One2Many(Contact, { limit: 20 })`. Decodes the nested list to
44
+ * `ReadonlyArray<Contact>`; `limit`, when given, rides the compiled
45
+ * `specification` so the server bounds the co-recordset.
46
+ */
47
+ export const One2Many = (spec, options) => ({ _decl: "one2many", spec, limit: options?.limit });
48
+ const isRelationDecl = (value) => typeof value === "object" && value !== null && "_decl" in value;
49
+ /**
50
+ * The nullable-many2one nested schema: the child dict, or Odoo's `false` empty
51
+ * → `null`. Strict: a classic `[id, name]` pair (or any non-dict) fails decode,
52
+ * surfacing as {@link SchemaDriftError} at the client boundary rather than being
53
+ * cast past.
54
+ */
55
+ // oxlint-disable-next-line no-explicit-any -- Schema invariance; see FieldInput.
56
+ const many2oneFieldSchema = (child) => Schema.Union([child.schema, Schema.Literal(false)]).pipe(Schema.decodeTo(Schema.NullOr(Schema.toType(child.schema)), SchemaTransformation.transform({
57
+ decode: (wire) => (wire === false ? null : wire),
58
+ encode: (value) => (value === null ? false : value),
59
+ })));
60
+ /**
61
+ * Build a {@link RecordSpec} from a model name and a field declaration. `id`
62
+ * (`Schema.Number`) is added automatically and always compiled into the
63
+ * `specification`, so every declared record and every relation subtree carries
64
+ * its id — the key downstream joins and re-reads rely on.
65
+ *
66
+ * ```ts
67
+ * const Company = defineRecord("res.company", { name: Schema.String });
68
+ * const Partner = defineRecord("res.partner", {
69
+ * name: Schema.String,
70
+ * company_id: Many2One(Company),
71
+ * child_ids: One2Many(Partner_contact, { limit: 20 }),
72
+ * });
73
+ * ```
74
+ */
75
+ export const defineRecord = (model, fields) => {
76
+ const structFields = { id: Schema.Number };
77
+ const meta = { id: { kind: "scalar" } };
78
+ for (const [name, decl] of Object.entries(fields)) {
79
+ if (isRelationDecl(decl)) {
80
+ if (decl._decl === "many2one") {
81
+ structFields[name] = many2oneFieldSchema(decl.spec);
82
+ meta[name] = { kind: "many2one", child: decl.spec };
83
+ }
84
+ else {
85
+ structFields[name] = Schema.Array(decl.spec.schema);
86
+ meta[name] = { kind: "one2many", child: decl.spec, limit: decl.limit };
87
+ }
88
+ }
89
+ else {
90
+ structFields[name] = decl;
91
+ meta[name] = { kind: "scalar" };
92
+ }
93
+ }
94
+ // The dynamic Struct build cannot preserve the precise mapped type; we recover
95
+ // it via the RowType/RowEncoded projection, which is exactly what the loop
96
+ // constructs field-for-field.
97
+ const schema = Schema.Struct(structFields);
98
+ const hasRelations = Object.values(meta).some((m) => m.kind !== "scalar");
99
+ return { model, schema, fields: meta, hasRelations };
100
+ };
101
+ //# sourceMappingURL=recordModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordModel.js","sourceRoot":"","sources":["../../src/records/recordModel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Pure join helpers for explicit relation traversal (the classic tier). No I/O: given
3
+ * already-decoded rows and a field accessor, collect the distinct related ids to
4
+ * fetch, and key decoded related rows by id for O(1) joins. The batched `read`
5
+ * itself lives in {@link ./typed.ts}; everything here is inert data shaping.
6
+ */
7
+ import type { Many2OneRefValue } from "./relations.js";
8
+ /** A many2one field's decoded value: a ref, a bare id, or empty. */
9
+ export type RefOrId = Many2OneRefValue | number | null | undefined;
10
+ /**
11
+ * The related id a {@link RefOrId} points at, or `null` when empty. A ref value
12
+ * carries `.id`; a bare number is itself the id. Anything else is out of
13
+ * contract and yields `null` (the caller's runtime guard rejects true misuse
14
+ * before this is reached — see {@link ./typed.ts}).
15
+ */
16
+ export declare const refId: (ref: RefOrId) => number | null;
17
+ /**
18
+ * The distinct, non-null related ids reachable through `accessor` across all
19
+ * `rows`, in first-seen order. This is the exact id set a single batched `read`
20
+ * must cover — an empty result means ZERO round trips are needed.
21
+ */
22
+ export declare const collectRefIds: <Row>(rows: ReadonlyArray<Row>, accessor: (row: Row) => RefOrId) => ReadonlyArray<number>;
23
+ /**
24
+ * A snapshot of decoded related rows keyed by id. Inert: it holds data and never
25
+ * fetches. `get` accepts a many2one ref, a bare id, or `null`/`undefined` and
26
+ * returns the joined row or `null` — a pure lookup, never a round trip.
27
+ */
28
+ export interface RelatedMap<A> {
29
+ /** Join a many2one value (ref/id/empty) to its decoded related row, or `null`. */
30
+ readonly get: (ref: RefOrId) => A | null;
31
+ /** The number of distinct related rows held. */
32
+ readonly size: number;
33
+ }
34
+ /**
35
+ * Build a {@link RelatedMap} from decoded related rows, keyed by each row's
36
+ * `id`. The rows come straight from a single batched `read`, so a duplicate id
37
+ * cannot occur; last-write-wins is a harmless invariant, not a policy.
38
+ */
39
+ export declare const makeRelatedMap: <A extends {
40
+ readonly id: number;
41
+ }>(rows: ReadonlyArray<A>) => RelatedMap<A>;
42
+ //# sourceMappingURL=related.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related.d.ts","sourceRoot":"","sources":["../../src/records/related.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,oEAAoE;AACpE,MAAM,MAAM,OAAO,GAAG,gBAAgB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,KAAK,QAAS,OAAO,KAAG,MAAM,GAAG,IAQ7C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,GAAG,QACzB,aAAa,CAAC,GAAG,CAAC,YACd,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,KAC9B,aAAa,CAAC,MAAM,CAWtB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC;IACzC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,QACxD,aAAa,CAAC,CAAC,CAAC,KACrB,UAAU,CAAC,CAAC,CAYd,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The related id a {@link RefOrId} points at, or `null` when empty. A ref value
3
+ * carries `.id`; a bare number is itself the id. Anything else is out of
4
+ * contract and yields `null` (the caller's runtime guard rejects true misuse
5
+ * before this is reached — see {@link ./typed.ts}).
6
+ */
7
+ export const refId = (ref) => {
8
+ if (ref === null || ref === undefined) {
9
+ return null;
10
+ }
11
+ if (typeof ref === "number") {
12
+ return ref;
13
+ }
14
+ return typeof ref === "object" && typeof ref.id === "number" ? ref.id : null;
15
+ };
16
+ /**
17
+ * The distinct, non-null related ids reachable through `accessor` across all
18
+ * `rows`, in first-seen order. This is the exact id set a single batched `read`
19
+ * must cover — an empty result means ZERO round trips are needed.
20
+ */
21
+ export const collectRefIds = (rows, accessor) => {
22
+ const seen = new Set();
23
+ const out = [];
24
+ for (const row of rows) {
25
+ const id = refId(accessor(row));
26
+ if (id !== null && !seen.has(id)) {
27
+ seen.add(id);
28
+ out.push(id);
29
+ }
30
+ }
31
+ return out;
32
+ };
33
+ /**
34
+ * Build a {@link RelatedMap} from decoded related rows, keyed by each row's
35
+ * `id`. The rows come straight from a single batched `read`, so a duplicate id
36
+ * cannot occur; last-write-wins is a harmless invariant, not a policy.
37
+ */
38
+ export const makeRelatedMap = (rows) => {
39
+ const byId = new Map();
40
+ for (const row of rows) {
41
+ byId.set(row.id, row);
42
+ }
43
+ return {
44
+ get: (ref) => {
45
+ const id = refId(ref);
46
+ return id === null ? null : (byId.get(id) ?? null);
47
+ },
48
+ size: byId.size,
49
+ };
50
+ };
51
+ //# sourceMappingURL=related.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related.js","sourceRoot":"","sources":["../../src/records/related.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAiB,EAAE;IACnD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,IAAwB,EACxB,QAA+B,EACR,EAAE;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACb,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAcF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAsB,EACP,EAAE;IACjB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAa,CAAC;IAClC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;YACX,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure relation & temporal value schemas — the decode vocabulary for typed
3
+ * records. No I/O: these are `effect/Schema` definitions a caller composes into
4
+ * a row schema. They transform Odoo's on-the-wire shapes (classic `[id, name]`
5
+ * many2one pairs, `false` empties, naive datetime strings) into ergonomic,
6
+ * fully-typed domain values, failing loudly as schema drift on anything else.
7
+ */
8
+ import { Schema } from "effect";
9
+ /**
10
+ * A decoded many2one reference: the related record's id and its display label,
11
+ * as Odoo carries them together in a classic `search_read`/`read` payload. The
12
+ * label is already present, so the common "show the name" case needs ZERO extra
13
+ * round trips — see {@link ../typed.ts | TypedRecordSet.fetchRelated} for when
14
+ * you need more than the label.
15
+ */
16
+ export interface Many2OneRefValue {
17
+ readonly id: number;
18
+ readonly name: string;
19
+ }
20
+ /** The decoded-value schema for a {@link Many2OneRefValue}. */
21
+ export declare const Many2OneRefValue: Schema.Codec<Many2OneRefValue>;
22
+ /**
23
+ * Decode a present many2one from its wire `[id, name]` pair to a
24
+ * {@link Many2OneRefValue}. Use {@link Many2OneRefOrNull} for a field that can
25
+ * be empty (Odoo sends `false`, not `null`, for an unset many2one).
26
+ *
27
+ * WIRE FACT: the `[id, name]` pair requires the classic `_classic_read` load
28
+ * (the `search_read`/`read` default). A context that changes the load (e.g. a
29
+ * future `load=None`) shifts the shape; strict decode then surfaces it as
30
+ * {@link SchemaDriftError} rather than casting past it.
31
+ */
32
+ export declare const Many2OneRefFromWire: Schema.Codec<Many2OneRefValue, readonly [number, string]>;
33
+ /** Ergonomic alias for {@link Many2OneRefFromWire} (the present-ref schema). */
34
+ export declare const Many2OneRef: Schema.Codec<Many2OneRefValue, readonly [number, string], never, never>;
35
+ /**
36
+ * Decode a nullable many2one: the wire `[id, name]` pair → {@link Many2OneRefValue},
37
+ * or Odoo's `false` empty → `null`. This is the schema a row declares for a
38
+ * many2one that can be unset, e.g. `company_id: Many2OneRefOrNull`.
39
+ */
40
+ export declare const Many2OneRefOrNull: Schema.Codec<Many2OneRefValue | null, readonly [number, string] | false>;
41
+ /**
42
+ * Odoo `Date` field: `"YYYY-MM-DD"` → a `Date` at UTC midnight of that day.
43
+ *
44
+ * A `Date` is an instant, and Odoo's `Date` carries no time or zone. We anchor
45
+ * it at UTC midnight and NEVER localize — reading back `getUTCFullYear()`/
46
+ * `getUTCMonth()`/`getUTCDate()` reproduces the exact wire day on any host,
47
+ * whereas the local accessors could roll a day depending on the runner's tz.
48
+ * Decision (records design, 2026-07-10): decode as UTC, documented, never
49
+ * silently localized.
50
+ */
51
+ export declare const OdooDate: Schema.Codec<Date, string>;
52
+ /**
53
+ * Odoo `Datetime` field: `"YYYY-MM-DD HH:MM:SS"` → a `Date` parsed as UTC.
54
+ *
55
+ * Odoo stores and transmits datetimes as naive strings that are UTC by
56
+ * convention. A JS `Date` is a single instant, so parsing the wire string AS
57
+ * UTC is the faithful, non-localizing choice: it names the same instant Odoo
58
+ * meant, with no host-tz shift. We never apply the session tz here — that is a
59
+ * presentation concern the caller owns. Decision (records design, 2026-07-10).
60
+ */
61
+ export declare const OdooDateTime: Schema.Codec<Date, string>;
62
+ /** {@link OdooDate} with Odoo's `false` empty decoding to `null`. */
63
+ export declare const OdooDateOrNull: Schema.Codec<Date | null, string | false>;
64
+ /** {@link OdooDateTime} with Odoo's `false` empty decoding to `null`. */
65
+ export declare const OdooDateTimeOrNull: Schema.Codec<Date | null, string | false>;
66
+ //# sourceMappingURL=relations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../src/records/relations.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAkB,MAAM,EAAmD,MAAM,QAAQ,CAAC;AAIjG;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAG1D,CAAC;AAKH;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CASvF,CAAC;AAEJ,gFAAgF;AAChF,eAAO,MAAM,WAAW,yEAAsB,CAAC;AAK/C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,KAAK,CAC1C,gBAAgB,GAAG,IAAI,EACvB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CASlC,CAAC;AAuBF;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAmB/C,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAmBnD,CAAC;AA6BF,qEAAqE;AACrE,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,GAAG,KAAK,CAAyB,CAAC;AAE/F,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,GAAG,KAAK,CAC9C,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Pure relation & temporal value schemas — the decode vocabulary for typed
3
+ * records. No I/O: these are `effect/Schema` definitions a caller composes into
4
+ * a row schema. They transform Odoo's on-the-wire shapes (classic `[id, name]`
5
+ * many2one pairs, `false` empties, naive datetime strings) into ergonomic,
6
+ * fully-typed domain values, failing loudly as schema drift on anything else.
7
+ */
8
+ import { Effect, Option, Schema, SchemaIssue, SchemaParser, SchemaTransformation } from "effect";
9
+ /** The decoded-value schema for a {@link Many2OneRefValue}. */
10
+ export const Many2OneRefValue = Schema.Struct({
11
+ id: Schema.Number,
12
+ name: Schema.String,
13
+ });
14
+ /** Odoo's on-the-wire many2one shape: the classic `[id, name]` pair. */
15
+ const Many2OneWirePair = Schema.Tuple([Schema.Number, Schema.String]);
16
+ /**
17
+ * Decode a present many2one from its wire `[id, name]` pair to a
18
+ * {@link Many2OneRefValue}. Use {@link Many2OneRefOrNull} for a field that can
19
+ * be empty (Odoo sends `false`, not `null`, for an unset many2one).
20
+ *
21
+ * WIRE FACT: the `[id, name]` pair requires the classic `_classic_read` load
22
+ * (the `search_read`/`read` default). A context that changes the load (e.g. a
23
+ * future `load=None`) shifts the shape; strict decode then surfaces it as
24
+ * {@link SchemaDriftError} rather than casting past it.
25
+ */
26
+ export const Many2OneRefFromWire = Many2OneWirePair.pipe(Schema.decodeTo(Many2OneRefValue, SchemaTransformation.transform({
27
+ decode: ([id, name]) => ({ id, name }),
28
+ encode: ({ id, name }) => [id, name],
29
+ })));
30
+ /** Ergonomic alias for {@link Many2OneRefFromWire} (the present-ref schema). */
31
+ export const Many2OneRef = Many2OneRefFromWire;
32
+ /** The wire shape of a nullable many2one: the `[id, name]` pair or `false`. */
33
+ const Many2OneWireOrFalse = Schema.Union([Many2OneWirePair, Schema.Literal(false)]);
34
+ /**
35
+ * Decode a nullable many2one: the wire `[id, name]` pair → {@link Many2OneRefValue},
36
+ * or Odoo's `false` empty → `null`. This is the schema a row declares for a
37
+ * many2one that can be unset, e.g. `company_id: Many2OneRefOrNull`.
38
+ */
39
+ export const Many2OneRefOrNull = Many2OneWireOrFalse.pipe(Schema.decodeTo(Schema.NullOr(Many2OneRefValue), SchemaTransformation.transform({
40
+ decode: (wire) => (wire === false ? null : { id: wire[0], name: wire[1] }),
41
+ encode: (value) => (value === null ? false : [value.id, value.name]),
42
+ })));
43
+ // --- dates & datetimes ------------------------------------------------------
44
+ // Odoo Date is `"YYYY-MM-DD"`; Datetime is `"YYYY-MM-DD HH:MM:SS"`, both naive.
45
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
46
+ const DATETIME_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
47
+ const pad = (n, width) => n.toString().padStart(width, "0");
48
+ const formatUtcDate = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
49
+ const formatUtcDateTime = (date) => `${formatUtcDate(date)} ${pad(date.getUTCHours(), 2)}:${pad(date.getUTCMinutes(), 2)}:${pad(date.getUTCSeconds(), 2)}`;
50
+ /** A decode/encode failure as a v4 schema issue on the offending value. */
51
+ const invalid = (value, message) => new SchemaIssue.InvalidValue(Option.some(value), { message });
52
+ /**
53
+ * Odoo `Date` field: `"YYYY-MM-DD"` → a `Date` at UTC midnight of that day.
54
+ *
55
+ * A `Date` is an instant, and Odoo's `Date` carries no time or zone. We anchor
56
+ * it at UTC midnight and NEVER localize — reading back `getUTCFullYear()`/
57
+ * `getUTCMonth()`/`getUTCDate()` reproduces the exact wire day on any host,
58
+ * whereas the local accessors could roll a day depending on the runner's tz.
59
+ * Decision (records design, 2026-07-10): decode as UTC, documented, never
60
+ * silently localized.
61
+ */
62
+ export const OdooDate = Schema.String.pipe(Schema.decodeTo(Schema.Date, SchemaTransformation.transformOrFail({
63
+ decode: (input) => {
64
+ if (!DATE_RE.test(input)) {
65
+ return Effect.fail(invalid(input, "expected YYYY-MM-DD"));
66
+ }
67
+ const ms = Date.parse(`${input}T00:00:00Z`);
68
+ return Number.isNaN(ms)
69
+ ? Effect.fail(invalid(input, "not a valid calendar date"))
70
+ : Effect.succeed(new Date(ms));
71
+ },
72
+ encode: (date) => Number.isNaN(date.getTime())
73
+ ? Effect.fail(invalid(date, "invalid Date"))
74
+ : Effect.succeed(formatUtcDate(date)),
75
+ })));
76
+ /**
77
+ * Odoo `Datetime` field: `"YYYY-MM-DD HH:MM:SS"` → a `Date` parsed as UTC.
78
+ *
79
+ * Odoo stores and transmits datetimes as naive strings that are UTC by
80
+ * convention. A JS `Date` is a single instant, so parsing the wire string AS
81
+ * UTC is the faithful, non-localizing choice: it names the same instant Odoo
82
+ * meant, with no host-tz shift. We never apply the session tz here — that is a
83
+ * presentation concern the caller owns. Decision (records design, 2026-07-10).
84
+ */
85
+ export const OdooDateTime = Schema.String.pipe(Schema.decodeTo(Schema.Date, SchemaTransformation.transformOrFail({
86
+ decode: (input) => {
87
+ if (!DATETIME_RE.test(input)) {
88
+ return Effect.fail(invalid(input, "expected YYYY-MM-DD HH:MM:SS"));
89
+ }
90
+ const ms = Date.parse(`${input.replace(" ", "T")}Z`);
91
+ return Number.isNaN(ms)
92
+ ? Effect.fail(invalid(input, "not a valid calendar datetime"))
93
+ : Effect.succeed(new Date(ms));
94
+ },
95
+ encode: (date) => Number.isNaN(date.getTime())
96
+ ? Effect.fail(invalid(date, "invalid Date"))
97
+ : Effect.succeed(formatUtcDateTime(date)),
98
+ })));
99
+ /**
100
+ * Lift a `Codec<Date, string>` into one that also accepts Odoo's `false`
101
+ * empty, decoding it to `null`. Shared by the date and datetime nullable
102
+ * variants so the false↔null seam lives in exactly one place. Delegation to
103
+ * the base codec runs through `SchemaParser.*Effect`, which fails with a
104
+ * `SchemaIssue.Issue` directly — the v4 analogue of v3's
105
+ * `ParseResult.decodeUnknown` delegation.
106
+ */
107
+ const orFalseNull = (base) => Schema.Union([Schema.String, Schema.Literal(false)]).pipe(Schema.decodeTo(Schema.NullOr(Schema.Date), SchemaTransformation.transformOrFail({
108
+ decode: (wire, options) => wire === false
109
+ ? Effect.succeed(null)
110
+ : SchemaParser.decodeUnknownEffect(base)(wire, options),
111
+ encode: (value, options) => value === null
112
+ ? Effect.succeed(false)
113
+ : SchemaParser.encodeUnknownEffect(base)(value, options),
114
+ })));
115
+ /** {@link OdooDate} with Odoo's `false` empty decoding to `null`. */
116
+ export const OdooDateOrNull = orFalseNull(OdooDate);
117
+ /** {@link OdooDateTime} with Odoo's `false` empty decoding to `null`. */
118
+ export const OdooDateTimeOrNull = orFalseNull(OdooDateTime);
119
+ //# sourceMappingURL=relations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.js","sourceRoot":"","sources":["../../src/records/relations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pure compilation of a declared {@link RecordSpec} into the Odoo `web_read`/
3
+ * `web_search_read` `specification` payload (17+). No I/O: given the field graph,
4
+ * emit `{ field: field_spec }` where a scalar is `{}`, a many2one is
5
+ * `{ fields: <nested> }`, and an x2many is `{ fields: <nested>, limit? }`. The
6
+ * server resolves each relation once over the whole co-recordset, so a compiled
7
+ * spec is exactly ONE batched round trip — no N+1.
8
+ *
9
+ * CYCLES. Eager child specs cannot form an object cycle in a strict language
10
+ * (you cannot reference a `RecordSpec` inside its own `defineRecord` call), so a
11
+ * cyclic declaration is inexpressible by construction and the normal path is
12
+ * total. We still guard defensively by RecordSpec IDENTITY along the ancestor
13
+ * chain — a spec forced into a cycle (via mutation / `any`) would otherwise
14
+ * recurse forever; instead it throws {@link RecordSpecCycleError}, which the
15
+ * client surfaces as a defect (a declaration bug, never a runtime condition). A
16
+ * finite DAG that references the same MODEL through two distinct specs is fine —
17
+ * only a repeated spec object is a cycle.
18
+ */
19
+ import type { RecordSpec } from "./recordModel.js";
20
+ import type { HasId } from "./typed.js";
21
+ /** A declared record graph that references itself — a declaration bug, not a
22
+ * runtime fault. Thrown by {@link compileSpecification}; the client turns it into
23
+ * an `Effect` defect. */
24
+ export declare class RecordSpecCycleError extends Error {
25
+ readonly path: ReadonlyArray<string>;
26
+ constructor(path: ReadonlyArray<string>);
27
+ }
28
+ /** A declared record with no compilable fields — an empty `specification` would
29
+ * ask the server for nothing. Thrown by {@link compileSpecification}. */
30
+ export declare class EmptyRecordSpecError extends Error {
31
+ readonly model: string;
32
+ constructor(model: string);
33
+ }
34
+ /** `true` iff the spec declares at least one relation field (gate key). */
35
+ export declare const hasRelations: <A extends HasId, I>(spec: RecordSpec<A, I>) => boolean;
36
+ /**
37
+ * Compile a declared {@link RecordSpec} into its nested `specification` payload.
38
+ * Pure and total for the by-construction-acyclic normal case; throws
39
+ * {@link RecordSpecCycleError} on a forced cycle and {@link EmptyRecordSpecError}
40
+ * on an empty declaration. The client runs it under `Effect.sync`, so either
41
+ * throw becomes a precise defect.
42
+ */
43
+ export declare const compileSpecification: <A extends HasId, I>(spec: RecordSpec<A, I>) => Record<string, unknown>;
44
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../src/records/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;yBAEyB;AACzB,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,YAAY,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAQtC;CACF;AAED;yEACyE;AACzE,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,YAAY,KAAK,EAAE,MAAM,EAOxB;CACF;AAED,2EAA2E;AAC3E,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,KAAK,EAAE,CAAC,QAAQ,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,OACb,CAAC;AAiC9D;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,KAAK,EAAE,CAAC,QAC/C,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAsB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /** A declared record graph that references itself — a declaration bug, not a
2
+ * runtime fault. Thrown by {@link compileSpecification}; the client turns it into
3
+ * an `Effect` defect. */
4
+ export class RecordSpecCycleError extends Error {
5
+ path;
6
+ constructor(path) {
7
+ super(`RecordSpec declaration cycle: ${path.join(" -> ")} -> ${path[path.length - 1]}. ` +
8
+ `A relation subtree references a record spec already on its ancestor chain; ` +
9
+ `the specification would be infinite. Declared record graphs must be finite.`);
10
+ this.name = "RecordSpecCycleError";
11
+ this.path = path;
12
+ }
13
+ }
14
+ /** A declared record with no compilable fields — an empty `specification` would
15
+ * ask the server for nothing. Thrown by {@link compileSpecification}. */
16
+ export class EmptyRecordSpecError extends Error {
17
+ model;
18
+ constructor(model) {
19
+ super(`RecordSpec for "${model}" compiles to an empty specification (no fields). ` +
20
+ `A declared record must request at least its id.`);
21
+ this.name = "EmptyRecordSpecError";
22
+ this.model = model;
23
+ }
24
+ }
25
+ /** `true` iff the spec declares at least one relation field (gate key). */
26
+ export const hasRelations = (spec) => Object.values(spec.fields).some((m) => m.kind !== "scalar");
27
+ const compile = (
28
+ // oxlint-disable-next-line no-explicit-any -- Schema is invariant; the compiler
29
+ // walks the erased metadata graph structurally (see FieldMeta).
30
+ spec, ancestors) => {
31
+ if (ancestors.includes(spec)) {
32
+ throw new RecordSpecCycleError([...ancestors.map((a) => a.model), spec.model]);
33
+ }
34
+ const nextAncestors = [...ancestors, spec];
35
+ const out = {};
36
+ for (const [name, meta] of Object.entries(spec.fields)) {
37
+ if (meta.kind === "scalar") {
38
+ out[name] = {};
39
+ }
40
+ else if (meta.kind === "many2one") {
41
+ out[name] = { fields: compile(meta.child, nextAncestors) };
42
+ }
43
+ else {
44
+ out[name] = {
45
+ fields: compile(meta.child, nextAncestors),
46
+ ...(meta.limit !== undefined ? { limit: meta.limit } : {}),
47
+ };
48
+ }
49
+ }
50
+ if (Object.keys(out).length === 0) {
51
+ throw new EmptyRecordSpecError(spec.model);
52
+ }
53
+ return out;
54
+ };
55
+ /**
56
+ * Compile a declared {@link RecordSpec} into its nested `specification` payload.
57
+ * Pure and total for the by-construction-acyclic normal case; throws
58
+ * {@link RecordSpecCycleError} on a forced cycle and {@link EmptyRecordSpecError}
59
+ * on an empty declaration. The client runs it under `Effect.sync`, so either
60
+ * throw becomes a precise defect.
61
+ */
62
+ export const compileSpecification = (spec) => compile(spec, []);
63
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/records/spec.ts"],"names":[],"mappings":"AAqBA;;yBAEyB;AACzB,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,IAAI,CAAwB;IACrC,YAAY,IAA2B;QACrC,KAAK,CACH,iCAAiC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI;YAChF,6EAA6E;YAC7E,6EAA6E,CAChF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;yEACyE;AACzE,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,KAAK,CAAS;IACvB,YAAY,KAAa;QACvB,KAAK,CACH,mBAAmB,KAAK,oDAAoD;YAC1E,iDAAiD,CACpD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAqB,IAAsB,EAAW,EAAE,CAClF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AAE9D,MAAM,OAAO,GAAG;AACd,gFAAgF;AAChF,gEAAgE;AAChE,IAA0B,EAC1B,SAA8C,EACrB,EAAE;IAC3B,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,oBAAoB,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,aAAa,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,GAAG,GAA4B,EAAE,CAAC;IAExC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACjB,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACpC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC,EAAE,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,GAAG;gBACV,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,aAAa,CAAC;gBAC1C,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,IAAsB,EACG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `TypedRecordSet` — an immutable, decoded snapshot of rows bound to its
3
+ * `(rpc, model)` origin, with explicit, batched relation traversal — the
4
+ * CLASSIC tier of the records design (the classic `[id, name]`-pair protocol,
5
+ * works on 16+; the spec tier in `recordModel.ts` needs 17+). Traversal is always ONE `read` per relation per
6
+ * recordset — never per row — so the N+1 storm is structurally impossible: there
7
+ * is no per-record fetch API to misuse. The snapshot is never mutated; a fetch
8
+ * produces a fresh {@link RelatedMap}, and the caller joins explicitly.
9
+ */
10
+ import { Effect, Schema } from "effect";
11
+ import type { Rpc } from "../rpc.js";
12
+ import type { TransportCallError } from "../transport.js";
13
+ import { type RelatedMap } from "./related.js";
14
+ import type { Many2OneRefValue } from "./relations.js";
15
+ /** A decoded row must carry its `id` — every traversal and join keys on it. */
16
+ export type HasId = {
17
+ readonly id: number;
18
+ };
19
+ /**
20
+ * The field names of `Row` whose decoded value is a many2one reference
21
+ * (`Many2OneRefValue | null`) — the only fields a relation can be traversed
22
+ * through. A compile-time constraint: passing any other key to
23
+ * {@link TypedRecordSet.fetchRelated}/`joinRelated` is a type error, not a
24
+ * runtime surprise.
25
+ */
26
+ export type Many2OneRefField<Row> = {
27
+ readonly [K in keyof Row]: Row[K] extends Many2OneRefValue | null ? K : never;
28
+ }[keyof Row];
29
+ /**
30
+ * An immutable snapshot of typed rows with explicit relation traversal. Obtain
31
+ * one from `OdooClient.searchRecordsTyped`; it is bound to the `Rpc` seam and
32
+ * model it was read from so `fetchRelated` can batch a co-model `read`.
33
+ */
34
+ export interface TypedRecordSet<Row extends HasId> extends Iterable<Row> {
35
+ /** The model these rows were read from. */
36
+ readonly model: string;
37
+ /** The decoded rows, in server order. Immutable. */
38
+ readonly rows: ReadonlyArray<Row>;
39
+ /** Each row's `id`, in row order. */
40
+ readonly ids: ReadonlyArray<number>;
41
+ /** The number of rows. */
42
+ readonly length: number;
43
+ /**
44
+ * Fetch the related records behind a many2one `field` in ONE batched `read`
45
+ * over the distinct, non-null ids across the whole snapshot, decoded through
46
+ * `schema`. Returns a {@link RelatedMap} keyed by related id — a fresh
47
+ * snapshot; the recordset is never back-filled.
48
+ *
49
+ * ZERO round trips when no row references a related record (all empty). The
50
+ * batched-single-read guarantee is the entire point of this API and is
51
+ * asserted in the tests, not merely documented.
52
+ */
53
+ readonly fetchRelated: <K extends Many2OneRefField<Row>, A extends HasId, I = A>(field: K, relatedModel: string, schema: Schema.Codec<A, I>, fields?: ReadonlyArray<string>) => Effect.Effect<RelatedMap<A>, TransportCallError>;
54
+ /**
55
+ * Like {@link fetchRelated}, but returns each row paired with its joined
56
+ * related record (`null` when the many2one is empty or unresolved). One
57
+ * explicit, batched call — named to keep "this is a fetch" legible at the call
58
+ * site.
59
+ */
60
+ readonly joinRelated: <K extends Many2OneRefField<Row>, A extends HasId, I = A>(field: K, relatedModel: string, schema: Schema.Codec<A, I>, fields?: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<readonly [Row, A | null]>, TransportCallError>;
61
+ }
62
+ /** The subset of the {@link Rpc} service {@link TypedRecordSet} depends on. */
63
+ type RpcSeam = Pick<typeof Rpc.Service, "callKw">;
64
+ /**
65
+ * Construct a {@link TypedRecordSet} over already-decoded rows. Internal: the
66
+ * public entry point is `OdooClient.searchRecordsTyped`, which owns the initial
67
+ * `search_read` decode and passes the resulting rows here.
68
+ */
69
+ export declare const make: <Row extends HasId>(rpc: RpcSeam, model: string, rows: ReadonlyArray<Row>) => TypedRecordSet<Row>;
70
+ export {};
71
+ //# sourceMappingURL=typed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed.d.ts","sourceRoot":"","sources":["../../src/records/typed.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,+EAA+E;AAC/E,MAAM,MAAM,KAAK,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,CAAC,GAAG,IAAI;IAClC,QAAQ,EAAE,CAAC,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,gBAAgB,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK;CAC9E,CAAC,MAAM,GAAG,CAAC,CAAC;AAEb;;;;GAIG;AACH,MAAM,WAAW,cAAc,CAAC,GAAG,SAAS,KAAK,CAAE,SAAQ,QAAQ,CAAC,GAAG,CAAC;IACtE,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,0BAA0B;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,SAAS,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,CAAC,EAC7E,KAAK,EAAE,CAAC,EACR,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAEtD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,CAAC,EAC5E,KAAK,EAAE,CAAC,EACR,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;CACjF;AAED,+EAA+E;AAC/E,KAAK,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAoClD;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAI,GAAG,SAAS,KAAK,OAC/B,OAAO,SACL,MAAM,QACP,aAAa,CAAC,GAAG,CAAC,KACvB,cAAc,CAAC,GAAG,CAyDpB,CAAC"}