@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,191 @@
1
+ import { generateId, type TriggerType, type UpdateTriggerType } from './_helpers';
2
+ import type { CustomObject } from './custom-object';
3
+ import type { Field } from './field';
4
+
5
+ /**
6
+ * A single action performed when a {@link Rule} fires.
7
+ */
8
+ export interface RuleAction {
9
+ /**
10
+ * The field to set when this action runs.
11
+ *
12
+ * Required.
13
+ */
14
+ targetField: Field;
15
+ /**
16
+ * Set the target field to a fixed value. Mutually exclusive with `rqlFormula`.
17
+ *
18
+ * Optional. For `SELECT` / `RADIO` fields, pass a string or single-element array —
19
+ * both are normalized to an array on the wire.
20
+ */
21
+ fixedValue?: any;
22
+ /**
23
+ * Set the target field to the result of an RQL formula. Mutually exclusive with `fixedValue`.
24
+ *
25
+ * Optional.
26
+ */
27
+ rqlFormula?: string;
28
+ }
29
+
30
+ /**
31
+ * Initialization properties for {@link Rule}.
32
+ */
33
+ export interface RuleProps {
34
+ /**
35
+ * Display name of this automation rule shown in the UI.
36
+ *
37
+ * Required.
38
+ */
39
+ flowName: string;
40
+ /**
41
+ * When this rule fires. One of `'CREATE'` | `'UPDATE'` | `'CREATE_OR_UPDATE'`.
42
+ *
43
+ * Required. The constructor throws if an invalid value is passed.
44
+ */
45
+ triggerType: TriggerType;
46
+ /**
47
+ * Stable identifier for this rule. Pass a human-readable slug
48
+ * (e.g. `'gym_default_enrollment_status'`) to keep it stable across regenerations.
49
+ *
50
+ * Optional. Auto-generated when omitted. @default generateId('flow')
51
+ */
52
+ flowId?: string;
53
+ /**
54
+ * RQL formula guard — the rule fires only when this expression is truthy.
55
+ *
56
+ * Optional. @default null
57
+ */
58
+ rqlCondition?: string;
59
+ /**
60
+ * Controls when an `UPDATE` trigger fires relative to the condition.
61
+ * One of `'MEETS_CONDITION_AFTER_ONLY'` | `'MEETS_CONDITION_BEFORE_OR_AFTER'`.
62
+ *
63
+ * Conditional. Only meaningful when `triggerType` is `'UPDATE'` or `'CREATE_OR_UPDATE'`.
64
+ * @default `'MEETS_CONDITION_AFTER_ONLY'`
65
+ */
66
+ updateTriggerType?: UpdateTriggerType;
67
+ /**
68
+ * Internal description of this rule (for developer reference only).
69
+ *
70
+ * Optional. @default null
71
+ */
72
+ description?: string;
73
+ /**
74
+ * Actions to run when the rule fires. Each action sets one field to a fixed value
75
+ * or RQL formula result.
76
+ *
77
+ * Optional. @default `[]`
78
+ */
79
+ actions?: RuleAction[];
80
+ }
81
+
82
+ /** SELECT / RADIO rule actions use list `fixed_value` (Rippling rule builder). */
83
+ function fixedValueToWire(targetField: Field, value: any): any {
84
+ const ft = targetField.getFieldType();
85
+ if (ft !== 'SELECT' && ft !== 'RADIO') {
86
+ return value;
87
+ }
88
+ if (Array.isArray(value)) {
89
+ return value;
90
+ }
91
+ return [value];
92
+ }
93
+
94
+ function actionToDict(action: RuleAction): Record<string, any> {
95
+ const out: Record<string, any> = {
96
+ target_field: action.targetField.getApiName(),
97
+ };
98
+ if (action.fixedValue != null) {
99
+ out['fixed_value'] = fixedValueToWire(action.targetField, action.fixedValue);
100
+ } else if (action.rqlFormula != null) {
101
+ out['rql_formula'] = action.rqlFormula;
102
+ }
103
+ return out;
104
+ }
105
+
106
+ /**
107
+ * Defines a record-triggered automation rule and registers it with the manifest.
108
+ *
109
+ * A rule fires automatically when a record on the parent custom object is created,
110
+ * updated, or both — depending on `triggerType`. Each rule runs a list of actions
111
+ * that set field values, optionally guarded by an RQL condition.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * new Rule(enrollmentObj, {
116
+ * flowId: 'gym_default_enrollment_status',
117
+ * flowName: 'Default attendance to Registered',
118
+ * triggerType: 'CREATE',
119
+ * description: 'New roster rows start as Registered unless changed',
120
+ * actions: [{ targetField: enrollmentAttendanceField, fixedValue: ['Registered'] }],
121
+ * });
122
+ * ```
123
+ */
124
+ export class Rule {
125
+ private readonly _flowId: string;
126
+ private readonly _customObjectApiName: string;
127
+ private readonly _flowName: string;
128
+ private readonly _triggerType: TriggerType;
129
+ private readonly _rqlCondition: string | undefined;
130
+ private readonly _updateTriggerType: UpdateTriggerType | undefined;
131
+ private readonly _description: string | undefined;
132
+ private readonly _actions: RuleAction[];
133
+
134
+ /**
135
+ * @param customObject - The custom object this rule is attached to.
136
+ * @param props - Initialization properties.
137
+ * @throws {Error} If `props.triggerType` is not one of `'CREATE'`, `'UPDATE'`, `'CREATE_OR_UPDATE'`.
138
+ */
139
+ constructor(customObject: CustomObject, props: RuleProps) {
140
+ const VALID_TRIGGERS: TriggerType[] = ['CREATE', 'UPDATE', 'CREATE_OR_UPDATE'];
141
+ if (!VALID_TRIGGERS.includes(props.triggerType)) {
142
+ throw new Error(
143
+ `Invalid trigger_type: ${props.triggerType}. Must be one of: ${VALID_TRIGGERS.join(', ')}`,
144
+ );
145
+ }
146
+ this._flowId = props.flowId ?? generateId('flow');
147
+ this._customObjectApiName = customObject.getApiName();
148
+ this._flowName = props.flowName;
149
+ this._triggerType = props.triggerType;
150
+ this._rqlCondition = props.rqlCondition;
151
+ this._updateTriggerType = props.updateTriggerType;
152
+ this._description = props.description;
153
+ this._actions = [...(props.actions ?? [])];
154
+ customObject._register(this);
155
+ }
156
+
157
+ /**
158
+ * Returns the stable identifier for this rule (e.g. `'gym_default_enrollment_status'`).
159
+ */
160
+ getFlowId(): string {
161
+ return this._flowId;
162
+ }
163
+
164
+ /**
165
+ * Returns the api_name of the custom object this rule is attached to.
166
+ */
167
+ getCustomObjectApiName(): string {
168
+ return this._customObjectApiName;
169
+ }
170
+
171
+ /**
172
+ * Serializes this rule to the wire format consumed by the manifest install endpoint.
173
+ *
174
+ * @returns A plain object with `type: 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW'`.
175
+ * Optional fields are omitted when unset; `actions` is omitted when empty.
176
+ */
177
+ toDict(): Record<string, any> {
178
+ const component: Record<string, any> = {
179
+ type: 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW',
180
+ model_rql_name: this._customObjectApiName,
181
+ flow_id: this._flowId,
182
+ flow_name: this._flowName,
183
+ trigger_type: this._triggerType,
184
+ };
185
+ if (this._description != null) component['description'] = this._description;
186
+ if (this._updateTriggerType != null) component['update_trigger_type'] = this._updateTriggerType;
187
+ if (this._rqlCondition != null) component['rql_condition'] = this._rqlCondition;
188
+ if (this._actions.length > 0) component['actions'] = this._actions.map(actionToDict);
189
+ return component;
190
+ }
191
+ }
@@ -0,0 +1,118 @@
1
+ import type { ManifestFunction } from './manifest-function';
2
+ import type { ManifestScope } from './manifest-builder';
3
+
4
+ /**
5
+ * Initialization properties for {@link SduiPage}.
6
+ */
7
+ export interface SduiPageProps {
8
+ /**
9
+ * Unique identifier for this page within the package (e.g. `'gym_today_schedule'`).
10
+ *
11
+ * Required.
12
+ */
13
+ sduiPageId: string;
14
+ /**
15
+ * Display name shown as the tab label in the app (e.g. `'Today\'s schedule'`).
16
+ *
17
+ * Required.
18
+ */
19
+ name: string;
20
+ /**
21
+ * The function that renders this page. Pass a live {@link ManifestFunction} reference,
22
+ * or a raw api_name string for functions defined outside this manifest.
23
+ *
24
+ * Required.
25
+ */
26
+ function: ManifestFunction | string;
27
+ /**
28
+ * Human-readable description of what this page shows.
29
+ *
30
+ * Optional. @default `''`
31
+ */
32
+ description?: string;
33
+ }
34
+
35
+ /**
36
+ * Defines an SDUI page and registers it with the manifest.
37
+ *
38
+ * An SDUI page is a custom UI surface backed by a {@link ManifestFunction}. It appears
39
+ * as a tab in a Rippling {@link App}. You would normally define SDUI pages after the
40
+ * functions they reference, then pass them to an `App` via `pages`.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const todaySchedulePage = new SduiPage(manifest, {
45
+ * sduiPageId: 'gym_today_schedule',
46
+ * name: 'Today\'s schedule',
47
+ * function: todayScheduleFn,
48
+ * description: 'SDUI list of class sessions for front desk',
49
+ * });
50
+ *
51
+ * new App(manifest, {
52
+ * apiName: 'gym_membership_management_app',
53
+ * name: 'Gym Membership Management',
54
+ * pages: [memberObj, trainerObj, todaySchedulePage],
55
+ * });
56
+ * ```
57
+ *
58
+ * @see {@link ManifestFunction} — provides the handler that renders this page.
59
+ * @see {@link App} — hosts this page as a tab via `pages`.
60
+ */
61
+ export class SduiPage {
62
+ private readonly _sduiPageId: string;
63
+ private readonly _name: string;
64
+ private readonly _functionApiName: string;
65
+ private readonly _description: string;
66
+
67
+ /**
68
+ * @param scope - The manifest to register this page with.
69
+ * @param props - Initialization properties.
70
+ * @throws {Error} If `sduiPageId`, `name`, or `function` is missing.
71
+ */
72
+ constructor(scope: ManifestScope, props: SduiPageProps) {
73
+ if (!props.sduiPageId) throw new Error('sduiPageId is required');
74
+ if (!props.name) throw new Error('name is required');
75
+ if (!props.function) throw new Error('function is required');
76
+ this._sduiPageId = props.sduiPageId;
77
+ this._name = props.name;
78
+ this._functionApiName = typeof props.function === 'string' ? props.function : props.function.getApiName();
79
+ this._description = props.description ?? '';
80
+ scope._register(this);
81
+ }
82
+
83
+ /**
84
+ * Returns the unique page identifier (e.g. `'gym_today_schedule'`).
85
+ */
86
+ getSduiPageId(): string {
87
+ return this._sduiPageId;
88
+ }
89
+
90
+ /**
91
+ * Returns the display name (tab label) of this page.
92
+ */
93
+ getName(): string {
94
+ return this._name;
95
+ }
96
+
97
+ /**
98
+ * Returns the api_name of the function backing this page.
99
+ */
100
+ getFunctionApiName(): string {
101
+ return this._functionApiName;
102
+ }
103
+
104
+ /**
105
+ * Serializes this SDUI page to the wire format consumed by the manifest install endpoint.
106
+ *
107
+ * @returns A plain object with `type: 'SDUI_PAGE'`.
108
+ */
109
+ toDict(): Record<string, any> {
110
+ return {
111
+ type: 'SDUI_PAGE',
112
+ name: this._name,
113
+ sdui_page_id: this._sduiPageId,
114
+ function_api_name: this._functionApiName,
115
+ description: this._description,
116
+ };
117
+ }
118
+ }
@@ -0,0 +1,150 @@
1
+ import { generateId, type ValidationLevel, type ValidationState } from './_helpers';
2
+ import type { CustomObject } from './custom-object';
3
+ import type { Field } from './field';
4
+
5
+ /**
6
+ * Initialization properties for {@link Validation}.
7
+ */
8
+ export interface ValidationProps {
9
+ /**
10
+ * Display name of this validation rule shown in the UI.
11
+ *
12
+ * Required.
13
+ */
14
+ displayName: string;
15
+ /**
16
+ * RQL formula that returns `true` when the record is **invalid**.
17
+ *
18
+ * Required. Note the violation semantics: a truthy result triggers the error,
19
+ * not a passing check. Example: `'(end_time__c <= start_time__c)'` fires when
20
+ * end is not after start.
21
+ */
22
+ rqlFormula: string;
23
+ /**
24
+ * User-facing error message shown when the validation fires.
25
+ *
26
+ * Required. Pass an empty string for no message.
27
+ */
28
+ errorMessage: string;
29
+ /**
30
+ * Whether this rule is active or inactive.
31
+ *
32
+ * Required. One of `'ACTIVE'` | `'INACTIVE'`.
33
+ */
34
+ state: ValidationState;
35
+ /**
36
+ * Whether the error is shown at the record level or on a specific field.
37
+ *
38
+ * Required. One of `'RECORD'` | `'FIELD'`. When `'FIELD'`, also set `fieldRef`.
39
+ */
40
+ level: ValidationLevel;
41
+ /**
42
+ * Internal description of this rule (for developer reference only).
43
+ *
44
+ * Required. Pass an empty string if unused.
45
+ */
46
+ ruleDescription: string;
47
+ /**
48
+ * Stable identifier for this rule. Pass a human-readable slug
49
+ * (e.g. `'gym_session_end_after_start'`) to keep it stable across regenerations.
50
+ *
51
+ * Optional. Auto-generated when omitted. @default generateId('rule')
52
+ */
53
+ ruleId?: string;
54
+ /**
55
+ * The field the error is pinned to. Typically paired with `level: 'FIELD'`.
56
+ *
57
+ * Optional.
58
+ */
59
+ fieldRef?: Field;
60
+ }
61
+
62
+ /**
63
+ * Defines a validation rule and registers it with the manifest.
64
+ *
65
+ * A validation rule evaluates an RQL formula on save and blocks the operation
66
+ * when the formula returns `true`. You would normally define validations after
67
+ * the fields they reference.
68
+ *
69
+ * **Violation semantics:** the formula encodes the *invalid* condition, not the
70
+ * valid one. `'(end_time__c <= start_time__c)'` fires when end is before or equal
71
+ * to start — i.e., it is truthy when the record is wrong.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * new Validation(sessionObj, {
76
+ * ruleId: 'gym_session_end_after_start',
77
+ * displayName: 'End time must be after start',
78
+ * rqlFormula: '(end_time__c <= start_time__c)',
79
+ * errorMessage: 'End time must be after the start time.',
80
+ * ruleDescription: 'Prevents zero-length or inverted intervals',
81
+ * state: 'ACTIVE',
82
+ * level: 'FIELD',
83
+ * fieldRef: sessionEndField,
84
+ * });
85
+ * ```
86
+ */
87
+ export class Validation {
88
+ private readonly _ruleId: string;
89
+ private readonly _customObjectApiName: string;
90
+ private readonly _displayName: string;
91
+ private readonly _rqlFormula: string;
92
+ private readonly _errorMessage: string;
93
+ private readonly _state: ValidationState;
94
+ private readonly _level: ValidationLevel;
95
+ private readonly _ruleDescription: string;
96
+ private readonly _fieldRef: Field | undefined;
97
+
98
+ /**
99
+ * @param customObject - The custom object this rule applies to.
100
+ * @param props - Initialization properties.
101
+ */
102
+ constructor(customObject: CustomObject, props: ValidationProps) {
103
+ this._ruleId = props.ruleId ?? generateId('rule');
104
+ this._customObjectApiName = customObject.getApiName();
105
+ this._displayName = props.displayName;
106
+ this._rqlFormula = props.rqlFormula;
107
+ this._errorMessage = props.errorMessage;
108
+ this._state = props.state;
109
+ this._level = props.level;
110
+ this._ruleDescription = props.ruleDescription;
111
+ this._fieldRef = props.fieldRef;
112
+ customObject._register(this);
113
+ }
114
+
115
+ /**
116
+ * Returns the stable identifier for this validation rule (e.g. `'gym_session_end_after_start'`).
117
+ */
118
+ getRuleId(): string {
119
+ return this._ruleId;
120
+ }
121
+
122
+ /**
123
+ * Returns the api_name of the custom object this validation rule applies to.
124
+ */
125
+ getCustomObjectApiName(): string {
126
+ return this._customObjectApiName;
127
+ }
128
+
129
+ /**
130
+ * Serializes this validation rule to the wire format consumed by the manifest install endpoint.
131
+ *
132
+ * @returns A plain object with `type: 'CUSTOM_OBJECT_VALIDATION_RULE'`.
133
+ * `field_rql_name` is omitted when `fieldRef` was not provided.
134
+ */
135
+ toDict(): Record<string, any> {
136
+ const component: Record<string, any> = {
137
+ type: 'CUSTOM_OBJECT_VALIDATION_RULE',
138
+ model_rql_name: this._customObjectApiName,
139
+ rule_id: this._ruleId,
140
+ state: this._state,
141
+ level: this._level,
142
+ display_name: this._displayName,
143
+ rule_description: this._ruleDescription,
144
+ custom_error_message: this._errorMessage,
145
+ rql_formula: this._rqlFormula,
146
+ };
147
+ if (this._fieldRef != null) component['field_rql_name'] = this._fieldRef.getApiName();
148
+ return component;
149
+ }
150
+ }
@@ -0,0 +1,58 @@
1
+ import { extractPath } from './state';
2
+
3
+ export interface ActionBinding {
4
+ action: string;
5
+ params?: Record<string, unknown>;
6
+ onSuccess?: ActionBinding | { set: Record<string, unknown> };
7
+ onError?: ActionBinding | { set: Record<string, unknown> };
8
+ }
9
+
10
+ export function set(ref: unknown, value: unknown): ActionBinding {
11
+ return { action: 'setState', params: { statePath: extractPath(ref), value } };
12
+ }
13
+
14
+ export function push(ref: unknown, value: unknown, opts?: { clear?: unknown }): ActionBinding {
15
+ const params: Record<string, unknown> = { statePath: extractPath(ref), value };
16
+ if (opts?.clear) {
17
+ params['clearStatePath'] = extractPath(opts.clear);
18
+ }
19
+ return { action: 'pushState', params };
20
+ }
21
+
22
+ export function remove(ref: unknown, index: unknown): ActionBinding {
23
+ return { action: 'removeState', params: { statePath: extractPath(ref), index } };
24
+ }
25
+
26
+ export function callFunction(
27
+ functionId: string,
28
+ action: string,
29
+ opts?: Record<string, unknown>,
30
+ ): ActionBinding {
31
+ const { responsePath, onSuccess, onError, ...rest } = opts || {};
32
+ const params: Record<string, unknown> = { functionId, action, ...rest };
33
+ if (responsePath !== undefined) params['responsePath'] = responsePath;
34
+ const binding: ActionBinding = { action: 'triggerFunction', params };
35
+ if (onSuccess) binding.onSuccess = onSuccess as ActionBinding;
36
+ if (onError) binding.onError = onError as ActionBinding;
37
+ return binding;
38
+ }
39
+
40
+ export function nav(config: { path: string; openInNewTab?: boolean }): ActionBinding {
41
+ return { action: 'navigate', params: config };
42
+ }
43
+
44
+ export function notify(message: string, variant: string): ActionBinding {
45
+ return { action: 'showNotification', params: { message, variant } };
46
+ }
47
+
48
+ export function validate(statePath = '/formValidation'): ActionBinding {
49
+ return { action: 'validateForm', params: { statePath } };
50
+ }
51
+
52
+ export function openModal(modalId: string): ActionBinding {
53
+ return { action: 'openModal', params: { modalId } };
54
+ }
55
+
56
+ export function closeModal(): ActionBinding {
57
+ return { action: 'closeModal' };
58
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * component() — creates a stateful component boundary.
3
+ *
4
+ * Provides ctx.useState (scoped local state) and ctx.useEffect (onMount actions).
5
+ * The returned element is automatically wrapped with _LocalStateProvider in the rendered spec.
6
+ *
7
+ * Usage:
8
+ * function MembersGrid(props) {
9
+ * return component((ctx) => {
10
+ * const local = ctx.useState({ data: [], loading: true })
11
+ * ctx.useEffect(fetchAction)
12
+ * return { element: DataGrid({ ... }), refresh: fetchAction }
13
+ * })
14
+ * }
15
+ */
16
+
17
+ import type { SpecNode } from './components';
18
+ import type { ActionBinding } from './actions';
19
+
20
+ let _idCounter = 0;
21
+ function generateId(): string {
22
+ return `auto_${(++_idCounter).toString(36)}_${Date.now().toString(36)}`;
23
+ }
24
+
25
+ // Reset counter (for testing)
26
+ export function _resetIdCounter(): void {
27
+ _idCounter = 0;
28
+ }
29
+
30
+ export interface StateScope {
31
+ /** Read state value: { $state: "/auto_xxx/field" } */
32
+ readonly [key: string]: any;
33
+ /** Two-way bind: { $bindState: "/auto_xxx/field" } */
34
+ readonly $bind: { $bindState: string };
35
+ /** Raw path string */
36
+ readonly $path: string;
37
+ }
38
+
39
+ interface ComponentContext {
40
+ /** Declare scoped local state. Returns a proxy for reading/binding. */
41
+ useState<T extends Record<string, unknown>>(initial: T): StateScope;
42
+ /** Register an action to fire on mount (when element becomes visible). */
43
+ useEffect(action: ActionBinding | ActionBinding[]): void;
44
+ }
45
+
46
+ interface ComponentResult {
47
+ element: SpecNode;
48
+ [key: string]: any; // exposed actions like refresh, reset, etc.
49
+ }
50
+
51
+ function createScopeProxy(basePath: string): StateScope {
52
+ const handler: ProxyHandler<object> = {
53
+ get(_target, prop) {
54
+ if (prop === '$bind') return { $bindState: basePath };
55
+ if (prop === '$path') return basePath;
56
+ if (prop === '$state') return basePath;
57
+ if (prop === 'toJSON') return () => ({ $state: basePath });
58
+ if (prop === Symbol.toPrimitive) return () => `$state:${basePath}`;
59
+
60
+ const childPath = `${basePath}/${String(prop)}`;
61
+ return createScopeProxy(childPath);
62
+ },
63
+ };
64
+ return new Proxy({} as any, handler);
65
+ }
66
+
67
+ export function component(factory: (ctx: ComponentContext) => ComponentResult): ComponentResult {
68
+ let localState: { path: string; initial: unknown } | null = null;
69
+ let mountAction: ActionBinding | ActionBinding[] | null = null;
70
+
71
+ const ctx: ComponentContext = {
72
+ useState<T extends Record<string, unknown>>(initial: T): StateScope {
73
+ const path = `/${generateId()}`;
74
+ localState = { path, initial };
75
+ return createScopeProxy(path);
76
+ },
77
+ useEffect(action: ActionBinding | ActionBinding[]) {
78
+ mountAction = action;
79
+ },
80
+ };
81
+
82
+ const result = factory(ctx);
83
+
84
+ // Capture state after factory execution (TS can't track closure mutations)
85
+ const capturedState = localState as { path: string; initial: unknown } | null;
86
+ const capturedMount = mountAction;
87
+
88
+ // If no local state and no effects, return as-is
89
+ if (!capturedState && !capturedMount) {
90
+ return result;
91
+ }
92
+
93
+ // Wrap element with _LocalStateProvider
94
+ const providerProps: Record<string, unknown> = {};
95
+ if (capturedState) {
96
+ providerProps['statePath'] = capturedState.path;
97
+ providerProps['initialValue'] = capturedState.initial;
98
+ }
99
+ if (capturedMount) {
100
+ providerProps['onMountAction'] = capturedMount;
101
+ }
102
+
103
+ const wrapper: SpecNode = {
104
+ type: '_LocalStateProvider',
105
+ props: providerProps,
106
+ children: [result.element],
107
+ };
108
+
109
+ // Attach the local state to the spec's state tree
110
+ // The render() function will collect this via a special marker
111
+ if (capturedState) {
112
+ (wrapper as any)._localStateRegistration = {
113
+ path: capturedState.path,
114
+ initial: capturedState.initial,
115
+ };
116
+ }
117
+
118
+ return {
119
+ ...result,
120
+ element: wrapper,
121
+ };
122
+ }