@rippling/rippling-sdk 0.2.0-alpha.33 → 0.2.0-alpha.35

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 (284) hide show
  1. package/client.d.mts +8 -2
  2. package/client.d.mts.map +1 -1
  3. package/client.d.ts +8 -2
  4. package/client.d.ts.map +1 -1
  5. package/client.js +17 -0
  6. package/client.js.map +1 -1
  7. package/client.mjs +17 -0
  8. package/client.mjs.map +1 -1
  9. package/internal/types.d.mts +6 -6
  10. package/internal/types.d.mts.map +1 -1
  11. package/internal/types.d.ts +6 -6
  12. package/internal/types.d.ts.map +1 -1
  13. package/lib/manifest/_helpers.d.mts +91 -0
  14. package/lib/manifest/_helpers.d.mts.map +1 -0
  15. package/lib/manifest/_helpers.d.ts +91 -0
  16. package/lib/manifest/_helpers.d.ts.map +1 -0
  17. package/lib/manifest/_helpers.js +65 -0
  18. package/lib/manifest/_helpers.js.map +1 -0
  19. package/lib/manifest/_helpers.mjs +61 -0
  20. package/lib/manifest/_helpers.mjs.map +1 -0
  21. package/lib/manifest/app.d.mts +113 -0
  22. package/lib/manifest/app.d.mts.map +1 -0
  23. package/lib/manifest/app.d.ts +113 -0
  24. package/lib/manifest/app.d.ts.map +1 -0
  25. package/lib/manifest/app.js +95 -0
  26. package/lib/manifest/app.js.map +1 -0
  27. package/lib/manifest/app.mjs +91 -0
  28. package/lib/manifest/app.mjs.map +1 -0
  29. package/lib/manifest/category.d.mts +79 -0
  30. package/lib/manifest/category.d.mts.map +1 -0
  31. package/lib/manifest/category.d.ts +79 -0
  32. package/lib/manifest/category.d.ts.map +1 -0
  33. package/lib/manifest/category.js +72 -0
  34. package/lib/manifest/category.js.map +1 -0
  35. package/lib/manifest/category.mjs +68 -0
  36. package/lib/manifest/category.mjs.map +1 -0
  37. package/lib/manifest/custom-object-field-section.d.mts +71 -0
  38. package/lib/manifest/custom-object-field-section.d.mts.map +1 -0
  39. package/lib/manifest/custom-object-field-section.d.ts +71 -0
  40. package/lib/manifest/custom-object-field-section.d.ts.map +1 -0
  41. package/lib/manifest/custom-object-field-section.js +69 -0
  42. package/lib/manifest/custom-object-field-section.js.map +1 -0
  43. package/lib/manifest/custom-object-field-section.mjs +65 -0
  44. package/lib/manifest/custom-object-field-section.mjs.map +1 -0
  45. package/lib/manifest/custom-object-page-layout.d.mts +318 -0
  46. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -0
  47. package/lib/manifest/custom-object-page-layout.d.ts +318 -0
  48. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -0
  49. package/lib/manifest/custom-object-page-layout.js +158 -0
  50. package/lib/manifest/custom-object-page-layout.js.map +1 -0
  51. package/lib/manifest/custom-object-page-layout.mjs +154 -0
  52. package/lib/manifest/custom-object-page-layout.mjs.map +1 -0
  53. package/lib/manifest/custom-object.d.mts +165 -0
  54. package/lib/manifest/custom-object.d.mts.map +1 -0
  55. package/lib/manifest/custom-object.d.ts +165 -0
  56. package/lib/manifest/custom-object.d.ts.map +1 -0
  57. package/lib/manifest/custom-object.js +254 -0
  58. package/lib/manifest/custom-object.js.map +1 -0
  59. package/lib/manifest/custom-object.mjs +250 -0
  60. package/lib/manifest/custom-object.mjs.map +1 -0
  61. package/lib/manifest/field.d.mts +487 -0
  62. package/lib/manifest/field.d.mts.map +1 -0
  63. package/lib/manifest/field.d.ts +487 -0
  64. package/lib/manifest/field.d.ts.map +1 -0
  65. package/lib/manifest/field.js +461 -0
  66. package/lib/manifest/field.js.map +1 -0
  67. package/lib/manifest/field.mjs +438 -0
  68. package/lib/manifest/field.mjs.map +1 -0
  69. package/lib/manifest/index.d.mts +15 -0
  70. package/lib/manifest/index.d.mts.map +1 -0
  71. package/lib/manifest/index.d.ts +15 -0
  72. package/lib/manifest/index.d.ts.map +1 -0
  73. package/lib/manifest/index.js +53 -0
  74. package/lib/manifest/index.js.map +1 -0
  75. package/lib/manifest/index.mjs +18 -0
  76. package/lib/manifest/index.mjs.map +1 -0
  77. package/lib/manifest/list-view.d.mts +138 -0
  78. package/lib/manifest/list-view.d.mts.map +1 -0
  79. package/lib/manifest/list-view.d.ts +138 -0
  80. package/lib/manifest/list-view.d.ts.map +1 -0
  81. package/lib/manifest/list-view.js +109 -0
  82. package/lib/manifest/list-view.js.map +1 -0
  83. package/lib/manifest/list-view.mjs +105 -0
  84. package/lib/manifest/list-view.mjs.map +1 -0
  85. package/lib/manifest/manifest-builder.d.mts +110 -0
  86. package/lib/manifest/manifest-builder.d.mts.map +1 -0
  87. package/lib/manifest/manifest-builder.d.ts +110 -0
  88. package/lib/manifest/manifest-builder.d.ts.map +1 -0
  89. package/lib/manifest/manifest-builder.js +91 -0
  90. package/lib/manifest/manifest-builder.js.map +1 -0
  91. package/lib/manifest/manifest-builder.mjs +87 -0
  92. package/lib/manifest/manifest-builder.mjs.map +1 -0
  93. package/lib/manifest/manifest-function.d.mts +119 -0
  94. package/lib/manifest/manifest-function.d.mts.map +1 -0
  95. package/lib/manifest/manifest-function.d.ts +119 -0
  96. package/lib/manifest/manifest-function.d.ts.map +1 -0
  97. package/lib/manifest/manifest-function.js +85 -0
  98. package/lib/manifest/manifest-function.js.map +1 -0
  99. package/lib/manifest/manifest-function.mjs +81 -0
  100. package/lib/manifest/manifest-function.mjs.map +1 -0
  101. package/lib/manifest/rule.d.mts +128 -0
  102. package/lib/manifest/rule.d.mts.map +1 -0
  103. package/lib/manifest/rule.d.ts +128 -0
  104. package/lib/manifest/rule.d.ts.map +1 -0
  105. package/lib/manifest/rule.js +105 -0
  106. package/lib/manifest/rule.js.map +1 -0
  107. package/lib/manifest/rule.mjs +101 -0
  108. package/lib/manifest/rule.mjs.map +1 -0
  109. package/lib/manifest/sdui-page.d.mts +89 -0
  110. package/lib/manifest/sdui-page.d.mts.map +1 -0
  111. package/lib/manifest/sdui-page.d.ts +89 -0
  112. package/lib/manifest/sdui-page.d.ts.map +1 -0
  113. package/lib/manifest/sdui-page.js +83 -0
  114. package/lib/manifest/sdui-page.js.map +1 -0
  115. package/lib/manifest/sdui-page.mjs +79 -0
  116. package/lib/manifest/sdui-page.mjs.map +1 -0
  117. package/lib/manifest/validation.d.mts +116 -0
  118. package/lib/manifest/validation.d.mts.map +1 -0
  119. package/lib/manifest/validation.d.ts +116 -0
  120. package/lib/manifest/validation.d.ts.map +1 -0
  121. package/lib/manifest/validation.js +83 -0
  122. package/lib/manifest/validation.js.map +1 -0
  123. package/lib/manifest/validation.mjs +79 -0
  124. package/lib/manifest/validation.mjs.map +1 -0
  125. package/lib/sdui/actions.d.mts +25 -0
  126. package/lib/sdui/actions.d.mts.map +1 -0
  127. package/lib/sdui/actions.d.ts +25 -0
  128. package/lib/sdui/actions.d.ts.map +1 -0
  129. package/lib/sdui/actions.js +53 -0
  130. package/lib/sdui/actions.js.map +1 -0
  131. package/lib/sdui/actions.mjs +42 -0
  132. package/lib/sdui/actions.mjs.map +1 -0
  133. package/lib/sdui/component.d.mts +41 -0
  134. package/lib/sdui/component.d.mts.map +1 -0
  135. package/lib/sdui/component.d.ts +41 -0
  136. package/lib/sdui/component.d.ts.map +1 -0
  137. package/lib/sdui/component.js +95 -0
  138. package/lib/sdui/component.js.map +1 -0
  139. package/lib/sdui/component.mjs +91 -0
  140. package/lib/sdui/component.mjs.map +1 -0
  141. package/lib/sdui/components.d.mts +149 -0
  142. package/lib/sdui/components.d.mts.map +1 -0
  143. package/lib/sdui/components.d.ts +149 -0
  144. package/lib/sdui/components.d.ts.map +1 -0
  145. package/lib/sdui/components.js +303 -0
  146. package/lib/sdui/components.js.map +1 -0
  147. package/lib/sdui/components.mjs +298 -0
  148. package/lib/sdui/components.mjs.map +1 -0
  149. package/lib/sdui/conditions.d.mts +25 -0
  150. package/lib/sdui/conditions.d.mts.map +1 -0
  151. package/lib/sdui/conditions.d.ts +25 -0
  152. package/lib/sdui/conditions.d.ts.map +1 -0
  153. package/lib/sdui/conditions.js +47 -0
  154. package/lib/sdui/conditions.js.map +1 -0
  155. package/lib/sdui/conditions.mjs +35 -0
  156. package/lib/sdui/conditions.mjs.map +1 -0
  157. package/lib/sdui/directives.d.mts +18 -0
  158. package/lib/sdui/directives.d.mts.map +1 -0
  159. package/lib/sdui/directives.d.ts +18 -0
  160. package/lib/sdui/directives.d.ts.map +1 -0
  161. package/lib/sdui/directives.js +28 -0
  162. package/lib/sdui/directives.js.map +1 -0
  163. package/lib/sdui/directives.mjs +21 -0
  164. package/lib/sdui/directives.mjs.map +1 -0
  165. package/lib/sdui/index.d.mts +362 -0
  166. package/lib/sdui/index.d.mts.map +1 -0
  167. package/lib/sdui/index.d.ts +362 -0
  168. package/lib/sdui/index.d.ts.map +1 -0
  169. package/lib/sdui/index.js +375 -0
  170. package/lib/sdui/index.js.map +1 -0
  171. package/lib/sdui/index.mjs +199 -0
  172. package/lib/sdui/index.mjs.map +1 -0
  173. package/lib/sdui/props.d.mts +12 -0
  174. package/lib/sdui/props.d.mts.map +1 -0
  175. package/lib/sdui/props.d.ts +12 -0
  176. package/lib/sdui/props.d.ts.map +1 -0
  177. package/lib/sdui/props.js +25 -0
  178. package/lib/sdui/props.js.map +1 -0
  179. package/lib/sdui/props.mjs +21 -0
  180. package/lib/sdui/props.mjs.map +1 -0
  181. package/lib/sdui/render.d.mts +23 -0
  182. package/lib/sdui/render.d.mts.map +1 -0
  183. package/lib/sdui/render.d.ts +23 -0
  184. package/lib/sdui/render.d.ts.map +1 -0
  185. package/lib/sdui/render.js +40 -0
  186. package/lib/sdui/render.js.map +1 -0
  187. package/lib/sdui/render.mjs +37 -0
  188. package/lib/sdui/render.mjs.map +1 -0
  189. package/lib/sdui/state.d.mts +22 -0
  190. package/lib/sdui/state.d.mts.map +1 -0
  191. package/lib/sdui/state.d.ts +22 -0
  192. package/lib/sdui/state.d.ts.map +1 -0
  193. package/lib/sdui/state.js +57 -0
  194. package/lib/sdui/state.js.map +1 -0
  195. package/lib/sdui/state.mjs +53 -0
  196. package/lib/sdui/state.mjs.map +1 -0
  197. package/package.json +11 -1
  198. package/resources/draft-hires.d.mts +26 -0
  199. package/resources/draft-hires.d.mts.map +1 -1
  200. package/resources/draft-hires.d.ts +26 -0
  201. package/resources/draft-hires.d.ts.map +1 -1
  202. package/resources/earnings-inputs.d.mts +99 -1
  203. package/resources/earnings-inputs.d.mts.map +1 -1
  204. package/resources/earnings-inputs.d.ts +99 -1
  205. package/resources/earnings-inputs.d.ts.map +1 -1
  206. package/resources/earnings-inputs.js +6 -0
  207. package/resources/earnings-inputs.js.map +1 -1
  208. package/resources/earnings-inputs.mjs +6 -0
  209. package/resources/earnings-inputs.mjs.map +1 -1
  210. package/resources/files.d.mts +164 -0
  211. package/resources/files.d.mts.map +1 -0
  212. package/resources/files.d.ts +164 -0
  213. package/resources/files.d.ts.map +1 -0
  214. package/resources/files.js +25 -0
  215. package/resources/files.js.map +1 -0
  216. package/resources/files.mjs +21 -0
  217. package/resources/files.mjs.map +1 -0
  218. package/resources/headcount-positions.d.mts +2 -2
  219. package/resources/headcount-positions.d.mts.map +1 -1
  220. package/resources/headcount-positions.d.ts +2 -2
  221. package/resources/headcount-positions.d.ts.map +1 -1
  222. package/resources/index.d.mts +2 -1
  223. package/resources/index.d.mts.map +1 -1
  224. package/resources/index.d.ts +2 -1
  225. package/resources/index.d.ts.map +1 -1
  226. package/resources/index.js +4 -2
  227. package/resources/index.js.map +1 -1
  228. package/resources/index.mjs +1 -0
  229. package/resources/index.mjs.map +1 -1
  230. package/resources/legal-entities.d.mts +1 -1
  231. package/resources/legal-entities.d.mts.map +1 -1
  232. package/resources/legal-entities.d.ts +1 -1
  233. package/resources/legal-entities.d.ts.map +1 -1
  234. package/resources/location-factors.d.mts +2 -2
  235. package/resources/location-factors.d.mts.map +1 -1
  236. package/resources/location-factors.d.ts +2 -2
  237. package/resources/location-factors.d.ts.map +1 -1
  238. package/resources/shared.d.mts +1 -1
  239. package/resources/shared.d.mts.map +1 -1
  240. package/resources/shared.d.ts +1 -1
  241. package/resources/shared.d.ts.map +1 -1
  242. package/resources/workers.d.mts +2 -2
  243. package/resources/workers.d.mts.map +1 -1
  244. package/resources/workers.d.ts +2 -2
  245. package/resources/workers.d.ts.map +1 -1
  246. package/src/client.ts +29 -0
  247. package/src/internal/types.ts +6 -8
  248. package/src/lib/manifest/_helpers.ts +164 -0
  249. package/src/lib/manifest/app.ts +163 -0
  250. package/src/lib/manifest/category.ts +100 -0
  251. package/src/lib/manifest/custom-object-field-section.ts +93 -0
  252. package/src/lib/manifest/custom-object-page-layout.ts +427 -0
  253. package/src/lib/manifest/custom-object.ts +360 -0
  254. package/src/lib/manifest/field.ts +822 -0
  255. package/src/lib/manifest/index.ts +114 -0
  256. package/src/lib/manifest/list-view.ts +183 -0
  257. package/src/lib/manifest/manifest-builder.ts +140 -0
  258. package/src/lib/manifest/manifest-function.ts +151 -0
  259. package/src/lib/manifest/rule.ts +191 -0
  260. package/src/lib/manifest/sdui-page.ts +118 -0
  261. package/src/lib/manifest/validation.ts +150 -0
  262. package/src/lib/sdui/actions.ts +58 -0
  263. package/src/lib/sdui/component.ts +122 -0
  264. package/src/lib/sdui/components.ts +308 -0
  265. package/src/lib/sdui/conditions.ts +59 -0
  266. package/src/lib/sdui/directives.ts +42 -0
  267. package/src/lib/sdui/index.ts +365 -0
  268. package/src/lib/sdui/props.ts +32 -0
  269. package/src/lib/sdui/render.ts +58 -0
  270. package/src/lib/sdui/state.ts +60 -0
  271. package/src/resources/draft-hires.ts +31 -0
  272. package/src/resources/earnings-inputs.ts +127 -0
  273. package/src/resources/files.ts +203 -0
  274. package/src/resources/headcount-positions.ts +2 -0
  275. package/src/resources/index.ts +3 -0
  276. package/src/resources/legal-entities.ts +1 -0
  277. package/src/resources/location-factors.ts +2 -0
  278. package/src/resources/shared.ts +1 -0
  279. package/src/resources/workers.ts +2 -0
  280. package/src/version.ts +1 -1
  281. package/version.d.mts +1 -1
  282. package/version.d.ts +1 -1
  283. package/version.js +1 -1
  284. package/version.mjs +1 -1
@@ -0,0 +1,822 @@
1
+ import {
2
+ FILTER_OPERATORS_BY_TYPE,
3
+ type FieldType,
4
+ type FormulaFieldType,
5
+ type SummaryAggregation,
6
+ type SummaryFilterOperator,
7
+ } from './_helpers';
8
+ import type { CustomObject } from './custom-object';
9
+ import type { CustomObjectFieldSection } from './custom-object-field-section';
10
+
11
+ /**
12
+ * A filter condition applied to a {@link SummaryField}.
13
+ *
14
+ * Filters restrict which child records are included in the aggregation.
15
+ * Only scalar field types are supported as filter fields — see
16
+ * `FILTER_OPERATORS_BY_TYPE` in `_helpers.ts` for the per-type operator allow-list.
17
+ */
18
+ export interface SummaryFilter {
19
+ /**
20
+ * The child-CO field to filter on.
21
+ *
22
+ * Required. Must be a scalar field on the same child object as `lookup`.
23
+ * Cannot be a formula field, summary field, or the `lookup` field itself.
24
+ */
25
+ field: Field;
26
+ /**
27
+ * Comparison operator. Allowed operators depend on the field's type —
28
+ * text/boolean fields support `=` and `!=`; numeric/date fields additionally
29
+ * support `>`, `>=`, `<`, `<=`.
30
+ *
31
+ * Required.
32
+ */
33
+ operator: SummaryFilterOperator;
34
+ /**
35
+ * Raw RQL value string, e.g. `"'Active'"`, `"50"`, `"true"`, `"DATE(2026,1,1)"`.
36
+ *
37
+ * Required.
38
+ */
39
+ value: string;
40
+ }
41
+
42
+ interface SummarySpec {
43
+ aggregates: Field;
44
+ using: SummaryAggregation;
45
+ lookup: Field;
46
+ filters: SummaryFilter[];
47
+ currencyCode: string | null;
48
+ }
49
+
50
+ function validateApiName(apiName: string): void {
51
+ if (!apiName.endsWith('__c')) {
52
+ throw new Error(`Field api_name must end with __c: ${apiName}`);
53
+ }
54
+ }
55
+
56
+ /** Props shared by every field variant. */
57
+ interface CommonFieldProps {
58
+ apiName: string;
59
+ displayName: string;
60
+ description?: string;
61
+ section?: CustomObjectFieldSection | string;
62
+ }
63
+
64
+ /**
65
+ * Low-level props for the base `Field`. Most users should use one of the
66
+ * concrete subclasses (`TextField`, `LookupField`, …) instead of constructing
67
+ * `Field` directly.
68
+ */
69
+ export interface FieldProps {
70
+ apiName: string;
71
+ displayName: string;
72
+ // Optional members use `| undefined` so subclass constructors can spread
73
+ // props under `exactOptionalPropertyTypes`.
74
+ fieldType?: FieldType | undefined;
75
+ dataType?: Record<string, any> | undefined;
76
+ description?: string | null | undefined;
77
+ required?: boolean | null | undefined;
78
+ unique?: boolean | null | undefined;
79
+ indexed?: boolean | null | undefined;
80
+ rqlFormula?: string | null | undefined;
81
+ formulaAttrMetas?: Record<string, any> | null | undefined;
82
+ derivedAggregatedField?: Record<string, any> | null | undefined;
83
+ derivedFieldFormula?: string | null | undefined;
84
+ section?: CustomObjectFieldSection | string | null | undefined;
85
+ maxLength?: number | undefined;
86
+ decimalPlaces?: number | undefined;
87
+ currency?: string | undefined;
88
+ options?: string[] | undefined;
89
+ /** Internal — set by `SummaryField`; drives the `derived_aggregated_field` wire field. */
90
+ summarySpec?: SummarySpec | undefined;
91
+ }
92
+
93
+ // ────────────────────────────────────────────────────────────────────────
94
+ // Base class
95
+ // ────────────────────────────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Base class for every custom-object field.
99
+ *
100
+ * Prefer the typed subclasses (`TextField`, `NumberField`, `LookupField`, …) over
101
+ * constructing `Field` directly. Use `Field` only for wire-level field types the
102
+ * SDK does not yet model with a dedicated subclass.
103
+ *
104
+ * All field classes share the same scope-first CDK pattern: pass the parent
105
+ * {@link CustomObject} as the first argument and the field auto-registers with
106
+ * the manifest. Every field api_name must end with `__c`.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * // Prefer typed subclasses:
111
+ * const firstName = new TextField(memberObj, {
112
+ * apiName: 'first_name__c',
113
+ * displayName: 'First name',
114
+ * required: true,
115
+ * maxLength: 80,
116
+ * });
117
+ * ```
118
+ *
119
+ * @see {@link TextField}, {@link NumberField}, {@link LookupField}, {@link SummaryField}
120
+ */
121
+ export class Field {
122
+ private readonly _fieldApiName: string;
123
+ private readonly _fieldDisplayName: string;
124
+ private readonly _customObjectApiName: string;
125
+ private readonly _dataType: Record<string, any>;
126
+ private readonly _description: string | null;
127
+ private readonly _isRequired: boolean | null;
128
+ private readonly _isUnique: boolean | null;
129
+ private readonly _isIndexed: boolean | null;
130
+ private readonly _rqlFormula: string | null;
131
+ private readonly _formulaAttrMetas: Record<string, any> | null;
132
+ private readonly _derivedFieldFormula: string | null;
133
+ private readonly _section: string | null;
134
+ private readonly _summarySpec: SummarySpec | null;
135
+ private readonly _derivedAggregatedFieldStatic: Record<string, any> | null;
136
+
137
+ /**
138
+ * @param customObject - The custom object this field belongs to.
139
+ * @param props - Field configuration. See {@link FieldProps}.
140
+ * @throws {Error} If `props.apiName` does not end with `__c`.
141
+ */
142
+ constructor(customObject: CustomObject, props: FieldProps) {
143
+ validateApiName(props.apiName);
144
+ this._fieldApiName = props.apiName;
145
+ this._fieldDisplayName = props.displayName;
146
+ this._customObjectApiName = customObject.getApiName();
147
+
148
+ const dt: Record<string, any> = { ...(props.dataType ?? {}) };
149
+ if (props.fieldType != null) dt['field_type'] = props.fieldType;
150
+ if (props.maxLength != null) dt['max_length'] = props.maxLength;
151
+ if (props.decimalPlaces != null) dt['num_of_decimal_places'] = props.decimalPlaces;
152
+ if (props.currency != null) dt['currency'] = props.currency;
153
+ if (props.options != null) dt['options_list'] = [...props.options];
154
+ this._dataType = dt;
155
+
156
+ this._description = props.description ?? null;
157
+ this._isRequired = props.required ?? null;
158
+ this._isUnique = props.unique ?? null;
159
+ this._isIndexed = props.indexed ?? null;
160
+ this._rqlFormula = props.rqlFormula ?? null;
161
+ this._formulaAttrMetas = props.formulaAttrMetas ?? null;
162
+ this._derivedAggregatedFieldStatic = props.derivedAggregatedField ?? null;
163
+ this._derivedFieldFormula = props.derivedFieldFormula ?? null;
164
+ this._summarySpec = props.summarySpec ?? null;
165
+
166
+ const rawSection = props.section;
167
+ this._section =
168
+ rawSection == null ? null
169
+ : typeof rawSection === 'string' ? rawSection
170
+ : rawSection.getSectionId();
171
+
172
+ customObject._register(this);
173
+ }
174
+
175
+ // ── Getters ────────────────────────────────────────────────────────────
176
+
177
+ /** Returns the api_name of this field (e.g. `'first_name__c'`). */
178
+ getApiName(): string {
179
+ return this._fieldApiName;
180
+ }
181
+ /** Returns the display name of this field. */
182
+ getDisplayName(): string {
183
+ return this._fieldDisplayName;
184
+ }
185
+ /** Returns the api_name of the custom object this field belongs to. */
186
+ getCustomObjectApiName(): string {
187
+ return this._customObjectApiName;
188
+ }
189
+ /** Returns the resolved `field_type` (e.g. `'TEXT'`), or `null` if unset. */
190
+ getFieldType(): FieldType | null {
191
+ return (this._dataType['field_type'] as FieldType | undefined) ?? null;
192
+ }
193
+ /** Returns a read-only view of the raw `data_type` dict. */
194
+ getDataType(): Readonly<Record<string, any>> {
195
+ return this._dataType;
196
+ }
197
+ /** Returns `true` if this is a formula (computed) field. */
198
+ isFormula(): boolean {
199
+ return this._derivedFieldFormula != null;
200
+ }
201
+ /** Returns `true` if this is a summary (rollup) field. */
202
+ isSummary(): boolean {
203
+ return this._summarySpec != null;
204
+ }
205
+
206
+ // ── Serialization ──────────────────────────────────────────────────────
207
+
208
+ /**
209
+ * Serializes this field to the wire format consumed by the manifest install endpoint.
210
+ *
211
+ * @returns A plain object with `type: 'CUSTOM_OBJECT_FIELD'`.
212
+ */
213
+ toDict(): Record<string, any> {
214
+ return {
215
+ type: 'CUSTOM_OBJECT_FIELD',
216
+ custom_object_api_name: this._customObjectApiName,
217
+ field_api_name: this._fieldApiName,
218
+ field_display_name: this._fieldDisplayName,
219
+ data_type: { options_list: [], supported_types: [], ...this._dataType },
220
+ description: this._description,
221
+ is_required: this._isRequired,
222
+ is_unique: this._isUnique,
223
+ is_indexed: this._isIndexed,
224
+ rql_formula: this._rqlFormula ?? null,
225
+ formula_attr_metas: this._formulaAttrMetas,
226
+ // User-authored package fields are never standard (backend invariant).
227
+ is_standard: false,
228
+ derived_aggregated_field: this._resolveAggregatedField(),
229
+ derived_field_formula: this._derivedFieldFormula ?? null,
230
+ section: this._section,
231
+ };
232
+ }
233
+
234
+ /** Build the `derived_aggregated_field` dict from `_summarySpec` with cross-CO checks. */
235
+ private _resolveAggregatedField(): Record<string, any> | null {
236
+ if (this._summarySpec == null) return this._derivedAggregatedFieldStatic;
237
+ const spec = this._summarySpec;
238
+
239
+ const aggParent = spec.aggregates.getCustomObjectApiName();
240
+ const lookupParent = spec.lookup.getCustomObjectApiName();
241
+ if (lookupParent !== aggParent) {
242
+ throw new Error(
243
+ `SummaryField "${this._fieldApiName}": lookup lives on "${lookupParent}" ` +
244
+ `but aggregates lives on "${aggParent}" — both must live on the same ` +
245
+ `child CustomObject.`,
246
+ );
247
+ }
248
+
249
+ // Per-filter residency / back-ref / LONG_TEXT / formula checks: backend only.
250
+
251
+ return {
252
+ aggregation_function: spec.using,
253
+ child_model_name: aggParent,
254
+ currency_code: spec.currencyCode,
255
+ field_to_aggregate: spec.aggregates.getApiName(),
256
+ filters: spec.filters.map((f) => ({
257
+ field_name: f.field.getApiName(),
258
+ filter_value: f.value,
259
+ operator: f.operator,
260
+ })),
261
+ lookup_field_api_name: spec.lookup.getApiName(),
262
+ };
263
+ }
264
+ }
265
+
266
+ // ────────────────────────────────────────────────────────────────────────
267
+ // Concrete field subclasses — one per wire-level variant.
268
+ // Each sets its own `fieldType` / `dataType` and forwards the rest to
269
+ // `Field`'s constructor. Order matches the UI's "Data type" dropdown.
270
+ // ────────────────────────────────────────────────────────────────────────
271
+
272
+ // ── Text-like ──────────────────────────────────────────────────────────
273
+
274
+ /** Initialization properties for {@link TextField}. */
275
+ export interface TextFieldProps extends CommonFieldProps {
276
+ /** Whether a value is required on save. Optional. */
277
+ required?: boolean;
278
+ /** Whether values must be unique across records. Optional. */
279
+ unique?: boolean;
280
+ /** Maximum character length. Optional. */
281
+ maxLength?: number;
282
+ }
283
+ /** Single-line text field. */
284
+ export class TextField extends Field {
285
+ constructor(customObject: CustomObject, props: TextFieldProps) {
286
+ super(customObject, { ...props, fieldType: 'TEXT' });
287
+ }
288
+ }
289
+
290
+ /** Initialization properties for {@link LongTextField}. */
291
+ export interface LongTextFieldProps extends CommonFieldProps {
292
+ /** Whether a value is required on save. Optional. */
293
+ required?: boolean;
294
+ /** Maximum character length. Optional. */
295
+ maxLength?: number;
296
+ }
297
+ /** Multi-line text field. */
298
+ export class LongTextField extends Field {
299
+ constructor(customObject: CustomObject, props: LongTextFieldProps) {
300
+ super(customObject, { ...props, fieldType: 'LONG_TEXT' });
301
+ }
302
+ }
303
+
304
+ /** Initialization properties for {@link EmailField}. */
305
+ export interface EmailFieldProps extends CommonFieldProps {
306
+ /** Whether a value is required on save. Optional. */
307
+ required?: boolean;
308
+ /** Whether values must be unique across records. Optional. */
309
+ unique?: boolean;
310
+ }
311
+ /** Email address field. Format is validated by the Rippling UI. */
312
+ export class EmailField extends Field {
313
+ constructor(customObject: CustomObject, props: EmailFieldProps) {
314
+ super(customObject, { ...props, fieldType: 'EMAIL' });
315
+ }
316
+ }
317
+
318
+ /** Initialization properties for {@link UrlField}. */
319
+ export interface UrlFieldProps extends CommonFieldProps {
320
+ /** Whether a value is required on save. Optional. */
321
+ required?: boolean;
322
+ }
323
+ /** URL field. Format is validated by the Rippling UI. */
324
+ export class UrlField extends Field {
325
+ constructor(customObject: CustomObject, props: UrlFieldProps) {
326
+ super(customObject, { ...props, fieldType: 'URL' });
327
+ }
328
+ }
329
+
330
+ /** Initialization properties for {@link PhoneField}. */
331
+ export interface PhoneFieldProps extends CommonFieldProps {
332
+ /** Whether a value is required on save. Optional. */
333
+ required?: boolean;
334
+ }
335
+ /** Phone number field. */
336
+ export class PhoneField extends Field {
337
+ constructor(customObject: CustomObject, props: PhoneFieldProps) {
338
+ super(customObject, { ...props, fieldType: 'PHONE_NUMBER' });
339
+ }
340
+ }
341
+
342
+ // ── Numeric ────────────────────────────────────────────────────────────
343
+
344
+ /** Initialization properties for {@link NumberField}. */
345
+ export interface NumberFieldProps extends CommonFieldProps {
346
+ /** Whether a value is required on save. Optional. */
347
+ required?: boolean;
348
+ /** Whether values must be unique across records. Optional. */
349
+ unique?: boolean;
350
+ /** Number of decimal places to display and store. Optional. */
351
+ decimalPlaces?: number;
352
+ }
353
+ /** Numeric field. */
354
+ export class NumberField extends Field {
355
+ constructor(customObject: CustomObject, props: NumberFieldProps) {
356
+ super(customObject, { ...props, fieldType: 'NUMBER' });
357
+ }
358
+ }
359
+
360
+ /** Initialization properties for {@link CurrencyField}. */
361
+ export interface CurrencyFieldProps extends CommonFieldProps {
362
+ /** Whether a value is required on save. Optional. */
363
+ required?: boolean;
364
+ /** ISO 4217 currency code (e.g. `'USD'`). Optional. Forwarded to the wire only when set. */
365
+ currency?: string;
366
+ /** Number of decimal places. Optional. */
367
+ decimalPlaces?: number;
368
+ }
369
+ /** Currency amount field. */
370
+ export class CurrencyField extends Field {
371
+ constructor(customObject: CustomObject, props: CurrencyFieldProps) {
372
+ super(customObject, { ...props, fieldType: 'CURRENCY' });
373
+ }
374
+ }
375
+
376
+ /** Initialization properties for {@link PercentageField}. */
377
+ export interface PercentageFieldProps extends CommonFieldProps {
378
+ /** Whether a value is required on save. Optional. */
379
+ required?: boolean;
380
+ /** Number of decimal places. Optional. */
381
+ decimalPlaces?: number;
382
+ }
383
+ /** Percentage field. */
384
+ export class PercentageField extends Field {
385
+ constructor(customObject: CustomObject, props: PercentageFieldProps) {
386
+ super(customObject, { ...props, fieldType: 'PERCENTAGE' });
387
+ }
388
+ }
389
+
390
+ // ── Date/time ──────────────────────────────────────────────────────────
391
+
392
+ /** Initialization properties for {@link DateField}. */
393
+ export interface DateFieldProps extends CommonFieldProps {
394
+ /** Whether a value is required on save. Optional. */
395
+ required?: boolean;
396
+ }
397
+ /** Date field (no time component). */
398
+ export class DateField extends Field {
399
+ constructor(customObject: CustomObject, props: DateFieldProps) {
400
+ super(customObject, { ...props, fieldType: 'DATE' });
401
+ }
402
+ }
403
+
404
+ /** Initialization properties for {@link DatetimeField}. */
405
+ export interface DatetimeFieldProps extends CommonFieldProps {
406
+ /** Whether a value is required on save. Optional. */
407
+ required?: boolean;
408
+ }
409
+ /** Date and time field. */
410
+ export class DatetimeField extends Field {
411
+ constructor(customObject: CustomObject, props: DatetimeFieldProps) {
412
+ super(customObject, { ...props, fieldType: 'DATETIME' });
413
+ }
414
+ }
415
+
416
+ /** Initialization properties for {@link TimeField}. */
417
+ export interface TimeFieldProps extends CommonFieldProps {
418
+ /** Whether a value is required on save. Optional. */
419
+ required?: boolean;
420
+ }
421
+ /** Time-of-day field. */
422
+ export class TimeField extends Field {
423
+ constructor(customObject: CustomObject, props: TimeFieldProps) {
424
+ super(customObject, { ...props, fieldType: 'TIME' });
425
+ }
426
+ }
427
+
428
+ // ── Choice ─────────────────────────────────────────────────────────────
429
+
430
+ /** Initialization properties for {@link SelectField}. */
431
+ export interface SelectFieldProps extends CommonFieldProps {
432
+ /**
433
+ * The list of selectable options (e.g. `['Active', 'Frozen', 'Cancelled']`).
434
+ *
435
+ * Required.
436
+ */
437
+ options: string[];
438
+ /** Whether a value is required on save. Optional. */
439
+ required?: boolean;
440
+ }
441
+ /** Dropdown single-select field. */
442
+ export class SelectField extends Field {
443
+ constructor(customObject: CustomObject, props: SelectFieldProps) {
444
+ super(customObject, { ...props, fieldType: 'SELECT' });
445
+ }
446
+ }
447
+
448
+ /** Initialization properties for {@link RadioField}. */
449
+ export interface RadioFieldProps extends CommonFieldProps {
450
+ /**
451
+ * The list of selectable options.
452
+ *
453
+ * Required.
454
+ */
455
+ options: string[];
456
+ /** Whether a value is required on save. Optional. */
457
+ required?: boolean;
458
+ }
459
+ /** Radio button group field. */
460
+ export class RadioField extends Field {
461
+ constructor(customObject: CustomObject, props: RadioFieldProps) {
462
+ super(customObject, { ...props, fieldType: 'RADIO' });
463
+ }
464
+ }
465
+
466
+ /** Initialization properties for {@link BooleanField}. */
467
+ export interface BooleanFieldProps extends CommonFieldProps {
468
+ /** Whether a value is required on save. Optional. */
469
+ required?: boolean;
470
+ }
471
+ /** Boolean (checkbox) field. */
472
+ export class BooleanField extends Field {
473
+ constructor(customObject: CustomObject, props: BooleanFieldProps) {
474
+ super(customObject, { ...props, fieldType: 'BOOLEAN' });
475
+ }
476
+ }
477
+
478
+ // ── File ───────────────────────────────────────────────────────────────
479
+
480
+ /** Initialization properties for {@link FileField}. */
481
+ export interface FileFieldProps extends CommonFieldProps {
482
+ /**
483
+ * Restricts which file types can be uploaded. Mirrors the UI's "File type" dropdown.
484
+ *
485
+ * Required.
486
+ * - `'images'` — jpg, jpeg, png, gif, bmp, svg
487
+ * - `'documents'` — doc, docx, pdf, csv, txt, rtf, ppt, pptx
488
+ * - `'any'` — any file type
489
+ */
490
+ allowedType: 'images' | 'documents' | 'any';
491
+ /** Whether a file is required on save. Optional. */
492
+ required?: boolean;
493
+ }
494
+ /** File attachment field. */
495
+ export class FileField extends Field {
496
+ constructor(customObject: CustomObject, props: FileFieldProps) {
497
+ const { allowedType, ...rest } = props;
498
+ const supported = allowedType === 'any' ? ['*'] : [allowedType];
499
+ super(customObject, {
500
+ ...rest,
501
+ fieldType: 'FILE',
502
+ dataType: { supported_types: supported },
503
+ });
504
+ }
505
+ }
506
+
507
+ // ── Relational ─────────────────────────────────────────────────────────
508
+
509
+ /** Initialization properties for {@link LookupField}. */
510
+ export interface LookupFieldProps extends CommonFieldProps {
511
+ /**
512
+ * The object this field points to. Pass a live {@link CustomObject} instance,
513
+ * or a raw api_name string for native Rippling models (e.g. `'role'`).
514
+ *
515
+ * Required.
516
+ */
517
+ target: CustomObject | string;
518
+ /**
519
+ * Label for the related-records panel on the target object's detail page
520
+ * (e.g. `'Class enrollments'`).
521
+ *
522
+ * Optional.
523
+ */
524
+ relatedDataLabel?: string;
525
+ /** Whether a value is required on save. Optional. */
526
+ required?: boolean;
527
+ }
528
+ /**
529
+ * Many-to-one reference field. Links this record to one record on the target object.
530
+ *
531
+ * Use {@link ParentChildField} instead when the relationship should create a
532
+ * parent-child hierarchy with rollup support.
533
+ */
534
+ export class LookupField extends Field {
535
+ constructor(customObject: CustomObject, props: LookupFieldProps) {
536
+ const targetApiName = typeof props.target === 'string' ? props.target : props.target.getApiName();
537
+ super(customObject, {
538
+ apiName: props.apiName,
539
+ displayName: props.displayName,
540
+ dataType: {
541
+ field_type: 'OG_REFERENCE_FIELD',
542
+ og_model_rql_name: targetApiName,
543
+ edge_type: 'LOOKUP',
544
+ ...(props.relatedDataLabel != null ? { related_data_label: props.relatedDataLabel } : {}),
545
+ },
546
+ required: props.required,
547
+ description: props.description,
548
+ section: props.section,
549
+ });
550
+ }
551
+ }
552
+
553
+ /** Initialization properties for {@link ParentChildField}. */
554
+ export interface ParentChildFieldProps extends CommonFieldProps {
555
+ /**
556
+ * The parent object. Must be a live {@link CustomObject} instance.
557
+ *
558
+ * Required.
559
+ */
560
+ target: CustomObject;
561
+ /**
562
+ * Label for the child-records panel on the parent's detail page
563
+ * (e.g. `'Enrollments'`).
564
+ *
565
+ * Optional.
566
+ */
567
+ relatedDataLabel?: string;
568
+ /**
569
+ * Whether this is the primary or secondary parent-child edge.
570
+ * One of `'PRIMARY'` | `'SECONDARY'`.
571
+ *
572
+ * Optional.
573
+ */
574
+ parentChildType?: 'PRIMARY' | 'SECONDARY';
575
+ /**
576
+ * Whether the child record inherits the owner from the parent.
577
+ *
578
+ * Optional.
579
+ */
580
+ enableParentOwnerInheritance?: boolean;
581
+ }
582
+ /**
583
+ * Parent-child reference field. Creates a hierarchical relationship between
584
+ * this object (child) and the target (parent), enabling {@link SummaryField}
585
+ * rollups from the child to the parent.
586
+ *
587
+ * Always emitted as `required: true` on the wire.
588
+ */
589
+ export class ParentChildField extends Field {
590
+ constructor(customObject: CustomObject, props: ParentChildFieldProps) {
591
+ super(customObject, {
592
+ apiName: props.apiName,
593
+ displayName: props.displayName,
594
+ dataType: {
595
+ field_type: 'OG_REFERENCE_FIELD',
596
+ og_model_rql_name: props.target.getApiName(),
597
+ edge_type: 'PARENT_CHILD',
598
+ ...(props.relatedDataLabel != null ? { related_data_label: props.relatedDataLabel } : {}),
599
+ ...(props.parentChildType != null ? { parent_child_type: props.parentChildType } : {}),
600
+ ...(props.enableParentOwnerInheritance != null ?
601
+ { enable_parent_owner_inheritance: props.enableParentOwnerInheritance }
602
+ : {}),
603
+ },
604
+ required: true,
605
+ description: props.description,
606
+ section: props.section,
607
+ });
608
+ }
609
+ }
610
+
611
+ // ── Computed ───────────────────────────────────────────────────────────
612
+
613
+ /** Initialization properties for {@link FormulaField}. */
614
+ export interface FormulaFieldProps extends CommonFieldProps {
615
+ /**
616
+ * The RQL expression that computes this field's value
617
+ * (e.g. `"CONCATENATE(TRIM(first_name__c), ' ', TRIM(last_name__c))"`).
618
+ *
619
+ * Required.
620
+ */
621
+ formula: string;
622
+ /**
623
+ * Output data type. One of `'TEXT'` | `'NUMBER'` | `'BOOLEAN'` | `'CURRENCY'`
624
+ * | `'PERCENTAGE'` | `'DATE'` | `'URL'`.
625
+ *
626
+ * Required.
627
+ */
628
+ fieldType: FormulaFieldType;
629
+ /**
630
+ * Maximum character length of the computed value.
631
+ *
632
+ * Required when `fieldType === 'TEXT'`. The constructor throws if omitted for text formulas.
633
+ */
634
+ maxLength?: number;
635
+ }
636
+ /**
637
+ * Read-only computed field whose value is derived from an RQL formula.
638
+ *
639
+ * `fieldType` is required and restricted to the 7 output types the Rippling
640
+ * formula editor supports. `maxLength` is required when `fieldType === 'TEXT'`.
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * const memberDisplayName = new FormulaField(memberObj, {
645
+ * apiName: 'member_display_name__c',
646
+ * displayName: 'Display name',
647
+ * fieldType: 'TEXT',
648
+ * maxLength: 200,
649
+ * formula: "CONCATENATE(TRIM(first_name__c), ' ', TRIM(last_name__c))",
650
+ * });
651
+ * ```
652
+ */
653
+ export class FormulaField extends Field {
654
+ constructor(customObject: CustomObject, props: FormulaFieldProps) {
655
+ if (props.fieldType === 'TEXT' && props.maxLength == null) {
656
+ throw new Error(`FormulaField "${props.apiName}": maxLength is required when fieldType is 'TEXT'.`);
657
+ }
658
+ const { formula, ...rest } = props;
659
+ super(customObject, {
660
+ ...rest,
661
+ derivedFieldFormula: formula,
662
+ required: null,
663
+ unique: null,
664
+ indexed: null,
665
+ });
666
+ }
667
+ }
668
+
669
+ /** Initialization properties for {@link SummaryField}. */
670
+ export interface SummaryFieldProps extends CommonFieldProps {
671
+ /**
672
+ * The child-CO field whose values are aggregated.
673
+ *
674
+ * Required. Must have a known `fieldType` (use a typed subclass, not bare `Field`).
675
+ * Must live on the same child object as `lookup`.
676
+ */
677
+ aggregates: Field;
678
+ /**
679
+ * Aggregation function. One of `'COUNT'` | `'SUM'` | `'MIN'` | `'MAX'`.
680
+ *
681
+ * Required. `COUNT` always produces a `NUMBER` output; other functions
682
+ * inherit the output type from `aggregates`.
683
+ */
684
+ using: SummaryAggregation;
685
+ /**
686
+ * The {@link ParentChildField} on the child CO that points back at this parent object.
687
+ *
688
+ * Required. Must be a `ParentChildField` (the constructor throws otherwise).
689
+ */
690
+ lookup: Field;
691
+ /**
692
+ * Optional filter conditions applied before aggregating. Each filter restricts
693
+ * which child records are included.
694
+ *
695
+ * Optional. @default `[]`
696
+ */
697
+ filters?: SummaryFilter[];
698
+ /**
699
+ * ISO 4217 currency code (e.g. `'USD'`). Only valid when the aggregated field
700
+ * resolves to `'CURRENCY'`.
701
+ *
702
+ * Optional.
703
+ */
704
+ currencyCode?: string;
705
+ }
706
+ /**
707
+ * Read-only rollup field that aggregates a child object's field values.
708
+ *
709
+ * Defined on the **parent** object. Requires a {@link ParentChildField} on the
710
+ * child object pointing back at this parent as the `lookup` edge.
711
+ *
712
+ * @example
713
+ * ```ts
714
+ * // Count enrollments on a class session:
715
+ * const enrollmentCount = new SummaryField(sessionObj, {
716
+ * apiName: 'enrollment_count__c',
717
+ * displayName: 'Enrolled (count)',
718
+ * aggregates: enrollmentMemberRef,
719
+ * using: 'COUNT',
720
+ * lookup: enrollmentSessionRef, // ParentChildField on enrollmentObj → sessionObj
721
+ * });
722
+ * ```
723
+ *
724
+ * @see {@link ParentChildField} — must be used as `lookup`.
725
+ */
726
+ export class SummaryField extends Field {
727
+ constructor(customObject: CustomObject, props: SummaryFieldProps) {
728
+ // lookup must be a parentChild field.
729
+ if (props.lookup.getDataType()['edge_type'] !== 'PARENT_CHILD') {
730
+ throw new Error(
731
+ `SummaryField "${props.apiName}": 'lookup' must be a parentChild ` +
732
+ `field on the child CO (got edge_type=` +
733
+ `${props.lookup.getDataType()['edge_type'] ?? 'undefined'}).`,
734
+ );
735
+ }
736
+
737
+ // Infer output fieldType: NUMBER for COUNT, else the aggregated field's type.
738
+ let outputFieldType: FieldType;
739
+ if (props.using === 'COUNT') {
740
+ outputFieldType = 'NUMBER';
741
+ } else {
742
+ const agg = props.aggregates.getFieldType();
743
+ if (agg == null) {
744
+ throw new Error(
745
+ `SummaryField "${props.apiName}": cannot infer output type — ` +
746
+ `aggregates field "${props.aggregates.getApiName()}" has no ` +
747
+ `known field_type. Construct it with a typed subclass (e.g. ` +
748
+ `CurrencyField / NumberField).`,
749
+ );
750
+ }
751
+ outputFieldType = agg;
752
+ }
753
+
754
+ // Summary-filter field restrictions. Mirror the admin condition-builder
755
+ // picker so SDK-authored manifests are fully editable in the UI, and
756
+ // catch the backend's own hard rejects ahead of /validate.
757
+ for (const f of props.filters ?? []) {
758
+ const fname = f.field.getApiName();
759
+ const ft = f.field.getFieldType();
760
+
761
+ // (a) Can't filter on the parent-child lookup field itself
762
+ // (backend: parent_child_util.py rejects this explicitly).
763
+ if (f.field === props.lookup) {
764
+ throw new Error(
765
+ `SummaryField "${props.apiName}": filter field "${fname}" is the ` +
766
+ `parent-child lookup field — that's reserved as the aggregation ` +
767
+ `axis and can't be a filter. Pick a scalar field on the child CO.`,
768
+ );
769
+ }
770
+ // (b) Can't filter on a derived (formula or summary) field
771
+ // (backend rejects both).
772
+ if (f.field.isFormula() || f.field.isSummary()) {
773
+ throw new Error(
774
+ `SummaryField "${props.apiName}": filter field "${fname}" is ` +
775
+ `itself a derived field (formula/summary). The backend does not ` +
776
+ `allow derived fields as summary-filter fields. Filter on a ` +
777
+ `plain scalar field on the child CO instead.`,
778
+ );
779
+ }
780
+ if (ft == null) continue;
781
+ // (c) FILTER_OPERATORS_BY_TYPE is the single source of truth for
782
+ // "which field types can be used as summary-filter fields".
783
+ // Absent key ⇒ UI picker hides this type ⇒ SDK rejects it.
784
+ const allowed = FILTER_OPERATORS_BY_TYPE[ft];
785
+ if (allowed == null) {
786
+ throw new Error(
787
+ `SummaryField "${props.apiName}": filter field "${fname}" has type ` +
788
+ `${ft}, which the admin UI excludes from summary-filter pickers ` +
789
+ `(ambiguous semantics for aggregations). Model a plain BOOLEAN ` +
790
+ `field on the child CO (updated via a Rule) and filter on that.`,
791
+ );
792
+ }
793
+ // (d) Per-type operator allow-list.
794
+ if (!allowed.has(f.operator)) {
795
+ throw new Error(
796
+ `SummaryField "${props.apiName}": operator "${f.operator}" is ` +
797
+ `not valid for ${ft} field "${fname}". ` +
798
+ `Allowed: ${[...allowed].join(', ')}.`,
799
+ );
800
+ }
801
+ }
802
+
803
+ super(customObject, {
804
+ apiName: props.apiName,
805
+ displayName: props.displayName,
806
+ fieldType: outputFieldType,
807
+ currency: outputFieldType === 'CURRENCY' ? props.currencyCode : undefined,
808
+ required: null,
809
+ unique: null,
810
+ indexed: null,
811
+ description: props.description,
812
+ section: props.section,
813
+ summarySpec: {
814
+ aggregates: props.aggregates,
815
+ using: props.using,
816
+ lookup: props.lookup,
817
+ filters: props.filters ?? [],
818
+ currencyCode: props.currencyCode ?? null,
819
+ },
820
+ });
821
+ }
822
+ }