@rippling/rippling-sdk 0.2.0-alpha.34 → 0.2.0-alpha.36

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 (270) hide show
  1. package/client.d.mts +6 -0
  2. package/client.d.mts.map +1 -1
  3. package/client.d.ts +6 -0
  4. package/client.d.ts.map +1 -1
  5. package/client.js +6 -0
  6. package/client.js.map +1 -1
  7. package/client.mjs +6 -0
  8. package/client.mjs.map +1 -1
  9. package/examples/.keep +4 -0
  10. package/examples/manifest/dental-practice-management/dental-practice-management.ts +651 -0
  11. package/examples/manifest/dental-practice-management/manifest.json +1870 -0
  12. package/examples/manifest/gym-membership-management/gym-membership-management.ts +757 -0
  13. package/examples/manifest/gym-membership-management/manifest.json +1975 -0
  14. package/examples/sdui/sdui-crud-todo/rippling-function.ts +367 -0
  15. package/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +282 -0
  16. package/examples/sdui/sdui-showcase/rippling-function.ts +381 -0
  17. package/examples/sdui/sdui-showcase/sdui-showcase.ts +351 -0
  18. package/examples/sdui/sdui-tab-multi-view/rippling-function.ts +1061 -0
  19. package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +924 -0
  20. package/examples/sdui/vote/rippling-function.ts +330 -0
  21. package/examples/sdui/vote/vote.ts +298 -0
  22. package/lib/manifest/app.d.mts +81 -13
  23. package/lib/manifest/app.d.mts.map +1 -1
  24. package/lib/manifest/app.d.ts +81 -13
  25. package/lib/manifest/app.d.ts.map +1 -1
  26. package/lib/manifest/app.js +37 -16
  27. package/lib/manifest/app.js.map +1 -1
  28. package/lib/manifest/app.mjs +37 -16
  29. package/lib/manifest/app.mjs.map +1 -1
  30. package/lib/manifest/category.d.mts +63 -7
  31. package/lib/manifest/category.d.mts.map +1 -1
  32. package/lib/manifest/category.d.ts +63 -7
  33. package/lib/manifest/category.d.ts.map +1 -1
  34. package/lib/manifest/category.js +45 -10
  35. package/lib/manifest/category.js.map +1 -1
  36. package/lib/manifest/category.mjs +43 -8
  37. package/lib/manifest/category.mjs.map +1 -1
  38. package/lib/manifest/custom-object-field-section.d.mts +56 -6
  39. package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
  40. package/lib/manifest/custom-object-field-section.d.ts +56 -6
  41. package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
  42. package/lib/manifest/custom-object-field-section.js +41 -14
  43. package/lib/manifest/custom-object-field-section.js.map +1 -1
  44. package/lib/manifest/custom-object-field-section.mjs +39 -12
  45. package/lib/manifest/custom-object-field-section.mjs.map +1 -1
  46. package/lib/manifest/custom-object-page-layout.d.mts +201 -41
  47. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  48. package/lib/manifest/custom-object-page-layout.d.ts +201 -41
  49. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  50. package/lib/manifest/custom-object-page-layout.js +76 -30
  51. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  52. package/lib/manifest/custom-object-page-layout.mjs +74 -28
  53. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  54. package/lib/manifest/custom-object.d.mts +128 -15
  55. package/lib/manifest/custom-object.d.mts.map +1 -1
  56. package/lib/manifest/custom-object.d.ts +128 -15
  57. package/lib/manifest/custom-object.d.ts.map +1 -1
  58. package/lib/manifest/custom-object.js +61 -26
  59. package/lib/manifest/custom-object.js.map +1 -1
  60. package/lib/manifest/custom-object.mjs +61 -26
  61. package/lib/manifest/custom-object.mjs.map +1 -1
  62. package/lib/manifest/field.d.mts +265 -54
  63. package/lib/manifest/field.d.mts.map +1 -1
  64. package/lib/manifest/field.d.ts +265 -54
  65. package/lib/manifest/field.d.ts.map +1 -1
  66. package/lib/manifest/field.js +100 -57
  67. package/lib/manifest/field.js.map +1 -1
  68. package/lib/manifest/field.mjs +100 -57
  69. package/lib/manifest/field.mjs.map +1 -1
  70. package/lib/manifest/list-view.d.mts +105 -14
  71. package/lib/manifest/list-view.d.mts.map +1 -1
  72. package/lib/manifest/list-view.d.ts +105 -14
  73. package/lib/manifest/list-view.d.ts.map +1 -1
  74. package/lib/manifest/list-view.js +50 -14
  75. package/lib/manifest/list-view.js.map +1 -1
  76. package/lib/manifest/list-view.mjs +48 -12
  77. package/lib/manifest/list-view.mjs.map +1 -1
  78. package/lib/manifest/manifest-builder.d.mts +77 -11
  79. package/lib/manifest/manifest-builder.d.mts.map +1 -1
  80. package/lib/manifest/manifest-builder.d.ts +77 -11
  81. package/lib/manifest/manifest-builder.d.ts.map +1 -1
  82. package/lib/manifest/manifest-builder.js +46 -24
  83. package/lib/manifest/manifest-builder.js.map +1 -1
  84. package/lib/manifest/manifest-builder.mjs +44 -22
  85. package/lib/manifest/manifest-builder.mjs.map +1 -1
  86. package/lib/manifest/manifest-function.d.mts +92 -12
  87. package/lib/manifest/manifest-function.d.mts.map +1 -1
  88. package/lib/manifest/manifest-function.d.ts +92 -12
  89. package/lib/manifest/manifest-function.d.ts.map +1 -1
  90. package/lib/manifest/manifest-function.js +40 -11
  91. package/lib/manifest/manifest-function.js.map +1 -1
  92. package/lib/manifest/manifest-function.mjs +38 -9
  93. package/lib/manifest/manifest-function.mjs.map +1 -1
  94. package/lib/manifest/rule.d.mts +94 -16
  95. package/lib/manifest/rule.d.mts.map +1 -1
  96. package/lib/manifest/rule.d.ts +94 -16
  97. package/lib/manifest/rule.d.ts.map +1 -1
  98. package/lib/manifest/rule.js +36 -15
  99. package/lib/manifest/rule.js.map +1 -1
  100. package/lib/manifest/rule.mjs +36 -15
  101. package/lib/manifest/rule.mjs.map +1 -1
  102. package/lib/manifest/sdui-page.d.mts +67 -11
  103. package/lib/manifest/sdui-page.d.mts.map +1 -1
  104. package/lib/manifest/sdui-page.d.ts +67 -11
  105. package/lib/manifest/sdui-page.d.ts.map +1 -1
  106. package/lib/manifest/sdui-page.js +44 -13
  107. package/lib/manifest/sdui-page.js.map +1 -1
  108. package/lib/manifest/sdui-page.mjs +42 -11
  109. package/lib/manifest/sdui-page.mjs.map +1 -1
  110. package/lib/manifest/validation.d.mts +87 -12
  111. package/lib/manifest/validation.d.mts.map +1 -1
  112. package/lib/manifest/validation.d.ts +87 -12
  113. package/lib/manifest/validation.d.ts.map +1 -1
  114. package/lib/manifest/validation.js +41 -15
  115. package/lib/manifest/validation.js.map +1 -1
  116. package/lib/manifest/validation.mjs +39 -13
  117. package/lib/manifest/validation.mjs.map +1 -1
  118. package/lib/sdui/actions.d.mts +25 -0
  119. package/lib/sdui/actions.d.mts.map +1 -0
  120. package/lib/sdui/actions.d.ts +25 -0
  121. package/lib/sdui/actions.d.ts.map +1 -0
  122. package/lib/sdui/actions.js +53 -0
  123. package/lib/sdui/actions.js.map +1 -0
  124. package/lib/sdui/actions.mjs +42 -0
  125. package/lib/sdui/actions.mjs.map +1 -0
  126. package/lib/sdui/component.d.mts +41 -0
  127. package/lib/sdui/component.d.mts.map +1 -0
  128. package/lib/sdui/component.d.ts +41 -0
  129. package/lib/sdui/component.d.ts.map +1 -0
  130. package/lib/sdui/component.js +95 -0
  131. package/lib/sdui/component.js.map +1 -0
  132. package/lib/sdui/component.mjs +91 -0
  133. package/lib/sdui/component.mjs.map +1 -0
  134. package/lib/sdui/components.d.mts +149 -0
  135. package/lib/sdui/components.d.mts.map +1 -0
  136. package/lib/sdui/components.d.ts +149 -0
  137. package/lib/sdui/components.d.ts.map +1 -0
  138. package/lib/sdui/components.js +303 -0
  139. package/lib/sdui/components.js.map +1 -0
  140. package/lib/sdui/components.mjs +298 -0
  141. package/lib/sdui/components.mjs.map +1 -0
  142. package/lib/sdui/conditions.d.mts +25 -0
  143. package/lib/sdui/conditions.d.mts.map +1 -0
  144. package/lib/sdui/conditions.d.ts +25 -0
  145. package/lib/sdui/conditions.d.ts.map +1 -0
  146. package/lib/sdui/conditions.js +47 -0
  147. package/lib/sdui/conditions.js.map +1 -0
  148. package/lib/sdui/conditions.mjs +35 -0
  149. package/lib/sdui/conditions.mjs.map +1 -0
  150. package/lib/sdui/directives.d.mts +18 -0
  151. package/lib/sdui/directives.d.mts.map +1 -0
  152. package/lib/sdui/directives.d.ts +18 -0
  153. package/lib/sdui/directives.d.ts.map +1 -0
  154. package/lib/sdui/directives.js +28 -0
  155. package/lib/sdui/directives.js.map +1 -0
  156. package/lib/sdui/directives.mjs +21 -0
  157. package/lib/sdui/directives.mjs.map +1 -0
  158. package/lib/sdui/index.d.mts +362 -0
  159. package/lib/sdui/index.d.mts.map +1 -0
  160. package/lib/sdui/index.d.ts +362 -0
  161. package/lib/sdui/index.d.ts.map +1 -0
  162. package/lib/sdui/index.js +375 -0
  163. package/lib/sdui/index.js.map +1 -0
  164. package/lib/sdui/index.mjs +199 -0
  165. package/lib/sdui/index.mjs.map +1 -0
  166. package/lib/sdui/props.d.mts +12 -0
  167. package/lib/sdui/props.d.mts.map +1 -0
  168. package/lib/sdui/props.d.ts +12 -0
  169. package/lib/sdui/props.d.ts.map +1 -0
  170. package/lib/sdui/props.js +25 -0
  171. package/lib/sdui/props.js.map +1 -0
  172. package/lib/sdui/props.mjs +21 -0
  173. package/lib/sdui/props.mjs.map +1 -0
  174. package/lib/sdui/render.d.mts +23 -0
  175. package/lib/sdui/render.d.mts.map +1 -0
  176. package/lib/sdui/render.d.ts +23 -0
  177. package/lib/sdui/render.d.ts.map +1 -0
  178. package/lib/sdui/render.js +40 -0
  179. package/lib/sdui/render.js.map +1 -0
  180. package/lib/sdui/render.mjs +37 -0
  181. package/lib/sdui/render.mjs.map +1 -0
  182. package/lib/sdui/state.d.mts +22 -0
  183. package/lib/sdui/state.d.mts.map +1 -0
  184. package/lib/sdui/state.d.ts +22 -0
  185. package/lib/sdui/state.d.ts.map +1 -0
  186. package/lib/sdui/state.js +57 -0
  187. package/lib/sdui/state.js.map +1 -0
  188. package/lib/sdui/state.mjs +53 -0
  189. package/lib/sdui/state.mjs.map +1 -0
  190. package/package.json +11 -1
  191. package/resources/company-legal-entity-workers.d.mts +79 -0
  192. package/resources/company-legal-entity-workers.d.mts.map +1 -0
  193. package/resources/company-legal-entity-workers.d.ts +79 -0
  194. package/resources/company-legal-entity-workers.d.ts.map +1 -0
  195. package/resources/company-legal-entity-workers.js +30 -0
  196. package/resources/company-legal-entity-workers.js.map +1 -0
  197. package/resources/company-legal-entity-workers.mjs +26 -0
  198. package/resources/company-legal-entity-workers.mjs.map +1 -0
  199. package/resources/draft-hires.d.mts +4 -0
  200. package/resources/draft-hires.d.mts.map +1 -1
  201. package/resources/draft-hires.d.ts +4 -0
  202. package/resources/draft-hires.d.ts.map +1 -1
  203. package/resources/files.d.mts +16 -1
  204. package/resources/files.d.mts.map +1 -1
  205. package/resources/files.d.ts +16 -1
  206. package/resources/files.d.ts.map +1 -1
  207. package/resources/headcount-positions.d.mts +2 -2
  208. package/resources/headcount-positions.d.mts.map +1 -1
  209. package/resources/headcount-positions.d.ts +2 -2
  210. package/resources/headcount-positions.d.ts.map +1 -1
  211. package/resources/index.d.mts +1 -0
  212. package/resources/index.d.mts.map +1 -1
  213. package/resources/index.d.ts +1 -0
  214. package/resources/index.d.ts.map +1 -1
  215. package/resources/index.js +4 -2
  216. package/resources/index.js.map +1 -1
  217. package/resources/index.mjs +1 -0
  218. package/resources/index.mjs.map +1 -1
  219. package/resources/legal-entities.d.mts +1 -1
  220. package/resources/legal-entities.d.mts.map +1 -1
  221. package/resources/legal-entities.d.ts +1 -1
  222. package/resources/legal-entities.d.ts.map +1 -1
  223. package/resources/location-factors.d.mts +2 -2
  224. package/resources/location-factors.d.mts.map +1 -1
  225. package/resources/location-factors.d.ts +2 -2
  226. package/resources/location-factors.d.ts.map +1 -1
  227. package/resources/shared.d.mts +1 -1
  228. package/resources/shared.d.mts.map +1 -1
  229. package/resources/shared.d.ts +1 -1
  230. package/resources/shared.d.ts.map +1 -1
  231. package/resources/workers.d.mts +2 -2
  232. package/resources/workers.d.mts.map +1 -1
  233. package/resources/workers.d.ts +2 -2
  234. package/resources/workers.d.ts.map +1 -1
  235. package/src/client.ts +20 -0
  236. package/src/lib/manifest/app.ts +81 -26
  237. package/src/lib/manifest/category.ts +63 -14
  238. package/src/lib/manifest/custom-object-field-section.ts +56 -17
  239. package/src/lib/manifest/custom-object-page-layout.ts +201 -66
  240. package/src/lib/manifest/custom-object.ts +128 -37
  241. package/src/lib/manifest/field.ts +265 -75
  242. package/src/lib/manifest/list-view.ts +105 -25
  243. package/src/lib/manifest/manifest-builder.ts +75 -32
  244. package/src/lib/manifest/manifest-function.ts +92 -22
  245. package/src/lib/manifest/rule.ts +95 -29
  246. package/src/lib/manifest/sdui-page.ts +67 -21
  247. package/src/lib/manifest/validation.ts +87 -24
  248. package/src/lib/sdui/actions.ts +58 -0
  249. package/src/lib/sdui/component.ts +122 -0
  250. package/src/lib/sdui/components.ts +308 -0
  251. package/src/lib/sdui/conditions.ts +59 -0
  252. package/src/lib/sdui/directives.ts +42 -0
  253. package/src/lib/sdui/index.ts +365 -0
  254. package/src/lib/sdui/props.ts +32 -0
  255. package/src/lib/sdui/render.ts +58 -0
  256. package/src/lib/sdui/state.ts +60 -0
  257. package/src/resources/company-legal-entity-workers.ts +115 -0
  258. package/src/resources/draft-hires.ts +5 -0
  259. package/src/resources/files.ts +18 -1
  260. package/src/resources/headcount-positions.ts +2 -0
  261. package/src/resources/index.ts +7 -0
  262. package/src/resources/legal-entities.ts +1 -0
  263. package/src/resources/location-factors.ts +2 -0
  264. package/src/resources/shared.ts +1 -0
  265. package/src/resources/workers.ts +2 -0
  266. package/src/version.ts +1 -1
  267. package/version.d.mts +1 -1
  268. package/version.d.ts +1 -1
  269. package/version.js +1 -1
  270. package/version.mjs +1 -1
@@ -0,0 +1,757 @@
1
+ /*
2
+ * Gym Membership Management — members, trainers, class sessions, enrollments
3
+ * with roster rollups, validations, list views (including today’s sessions),
4
+ * page layouts, and SDUI tabs for today’s schedule and front-desk check-in.
5
+ *
6
+ * gym_member__c ← membership profile & status
7
+ * gym_trainer__c ← instructors / coaches
8
+ * gym_class_session__c ← scheduled class (trainer, time, type, room)
9
+ * gym_class_enrollment__c ← member on a session + attendance / check-in
10
+ *
11
+ * Run:
12
+ * yarn tsn examples/manifest/gym-membership-management/gym-membership-management.ts \
13
+ * > examples/manifest/gym-membership-management/manifest.json
14
+ * (With npx, use `2>/dev/null` before `>` if npm prints warnings into the file.)
15
+ */
16
+
17
+ import {
18
+ App,
19
+ Category,
20
+ CustomObject,
21
+ CustomObjectFieldSection,
22
+ CustomObjectPageLayout,
23
+ DatetimeField,
24
+ DateField,
25
+ EmailField,
26
+ FormulaField,
27
+ ListViewDef,
28
+ LongTextField,
29
+ LookupField,
30
+ ManifestBuilder,
31
+ ManifestFunction,
32
+ NumberField,
33
+ ParentChildField,
34
+ PhoneField,
35
+ Rule,
36
+ SelectField,
37
+ SduiPage,
38
+ SummaryField,
39
+ TextField,
40
+ Validation,
41
+ } from '@rippling/rippling-sdk/lib/manifest';
42
+
43
+ const manifest = new ManifestBuilder({
44
+ key: 'gym_membership_management',
45
+ name: 'Gym Membership Management',
46
+ description:
47
+ 'Members, trainers, class schedules and assignments, roster rollups, and SDUI for today’s classes and check-in',
48
+ });
49
+
50
+ const gymCategory = new Category(manifest, {
51
+ apiName: 'gym__c',
52
+ name: 'Gym',
53
+ description: 'Memberships, staff, schedules, and attendance',
54
+ });
55
+
56
+ // ── Member ────────────────────────────────────────────────────────────────────
57
+ const memberObj = new CustomObject(manifest, {
58
+ apiName: 'gym_member__c',
59
+ name: 'Member',
60
+ pluralLabel: 'Members',
61
+ category: gymCategory,
62
+ description: 'Gym member profile, contact, and membership status',
63
+ icon: { emoji: '🏋️' },
64
+ });
65
+
66
+ const memberProfileSection = new CustomObjectFieldSection(memberObj, {
67
+ name: 'Profile',
68
+ sectionId: 'sec_gm_profile',
69
+ });
70
+
71
+ const memberMembershipSection = new CustomObjectFieldSection(memberObj, {
72
+ name: 'Membership',
73
+ sectionId: 'sec_gm_membership',
74
+ });
75
+
76
+ const memberFirstName = new TextField(memberObj, {
77
+ apiName: 'first_name__c',
78
+ displayName: 'First name',
79
+ required: true,
80
+ maxLength: 80,
81
+ section: memberProfileSection,
82
+ });
83
+
84
+ const memberLastName = new TextField(memberObj, {
85
+ apiName: 'last_name__c',
86
+ displayName: 'Last name',
87
+ required: true,
88
+ maxLength: 80,
89
+ section: memberProfileSection,
90
+ });
91
+
92
+ const memberEmail = new EmailField(memberObj, {
93
+ apiName: 'email__c',
94
+ displayName: 'Email',
95
+ unique: true,
96
+ section: memberProfileSection,
97
+ });
98
+
99
+ const memberPhone = new PhoneField(memberObj, {
100
+ apiName: 'phone__c',
101
+ displayName: 'Phone',
102
+ section: memberProfileSection,
103
+ });
104
+
105
+ const memberStatusField = new SelectField(memberObj, {
106
+ apiName: 'membership_status__c',
107
+ displayName: 'Membership status',
108
+ options: ['Active', 'Frozen', 'Cancelled', 'Trial'],
109
+ required: true,
110
+ section: memberMembershipSection,
111
+ });
112
+
113
+ const memberJoinDate = new DateField(memberObj, {
114
+ apiName: 'join_date__c',
115
+ displayName: 'Join date',
116
+ section: memberMembershipSection,
117
+ });
118
+
119
+ const memberEmergencyField = new LongTextField(memberObj, {
120
+ apiName: 'emergency_contact__c',
121
+ displayName: 'Emergency contact',
122
+ maxLength: 2000,
123
+ description: 'Name and phone for emergencies',
124
+ section: memberMembershipSection,
125
+ });
126
+
127
+ const memberDisplayName = new FormulaField(memberObj, {
128
+ apiName: 'member_display_name__c',
129
+ displayName: 'Display name',
130
+ fieldType: 'TEXT',
131
+ maxLength: 200,
132
+ formula: "CONCATENATE(TRIM(first_name__c), ' ', TRIM(last_name__c))",
133
+ section: memberProfileSection,
134
+ });
135
+
136
+ // ── Trainer ─────────────────────────────────────────────────────────────────
137
+ const trainerObj = new CustomObject(manifest, {
138
+ apiName: 'gym_trainer__c',
139
+ name: 'Trainer',
140
+ pluralLabel: 'Trainers',
141
+ category: gymCategory,
142
+ description: 'Instructors and coaches who lead scheduled classes',
143
+ icon: { emoji: '🎯' },
144
+ });
145
+
146
+ const trainerSection = new CustomObjectFieldSection(trainerObj, {
147
+ name: 'Trainer',
148
+ sectionId: 'sec_tr_profile',
149
+ });
150
+
151
+ const trainerNameField = new TextField(trainerObj, {
152
+ apiName: 'full_name__c',
153
+ displayName: 'Full name',
154
+ required: true,
155
+ maxLength: 160,
156
+ section: trainerSection,
157
+ });
158
+
159
+ const trainerEmailField = new EmailField(trainerObj, {
160
+ apiName: 'email__c',
161
+ displayName: 'Email',
162
+ section: trainerSection,
163
+ });
164
+
165
+ const trainerSpecialtyField = new SelectField(trainerObj, {
166
+ apiName: 'specialty__c',
167
+ displayName: 'Primary specialty',
168
+ options: ['Yoga', 'Spin', 'HIIT', 'Strength', 'Pilates', 'Dance', 'Aquatics', 'General'],
169
+ section: trainerSection,
170
+ });
171
+
172
+ const trainerBioField = new LongTextField(trainerObj, {
173
+ apiName: 'bio__c',
174
+ displayName: 'Bio & certifications',
175
+ maxLength: 4000,
176
+ section: trainerSection,
177
+ });
178
+
179
+ const trainerLabelField = new FormulaField(trainerObj, {
180
+ apiName: 'trainer_label__c',
181
+ displayName: 'List label',
182
+ fieldType: 'TEXT',
183
+ maxLength: 220,
184
+ formula: "CONCATENATE(full_name__c, ' · ', specialty__c)",
185
+ section: trainerSection,
186
+ });
187
+
188
+ // ── Class session ───────────────────────────────────────────────────────────
189
+ const sessionObj = new CustomObject(manifest, {
190
+ apiName: 'gym_class_session__c',
191
+ name: 'Class session',
192
+ pluralLabel: 'Class sessions',
193
+ category: gymCategory,
194
+ description: 'A scheduled class with assigned trainer, time, and capacity',
195
+ icon: { emoji: '📅' },
196
+ });
197
+
198
+ const sessionScheduleSection = new CustomObjectFieldSection(sessionObj, {
199
+ name: 'Schedule',
200
+ sectionId: 'sec_cs_schedule',
201
+ });
202
+
203
+ const sessionTrainerSection = new CustomObjectFieldSection(sessionObj, {
204
+ name: 'Class & room',
205
+ sectionId: 'sec_cs_class',
206
+ });
207
+
208
+ const sessionTrainerRef = new LookupField(sessionObj, {
209
+ apiName: 'trainer_ref__c',
210
+ displayName: 'Trainer',
211
+ target: trainerObj,
212
+ relatedDataLabel: 'Class sessions',
213
+ required: true,
214
+ description: 'Instructor leading this session',
215
+ section: sessionTrainerSection,
216
+ });
217
+
218
+ const sessionStartField = new DatetimeField(sessionObj, {
219
+ apiName: 'start_time__c',
220
+ displayName: 'Start',
221
+ required: true,
222
+ description: 'Session start (local)',
223
+ section: sessionScheduleSection,
224
+ });
225
+
226
+ const sessionEndField = new DatetimeField(sessionObj, {
227
+ apiName: 'end_time__c',
228
+ displayName: 'End',
229
+ required: true,
230
+ description: 'Session end (local)',
231
+ section: sessionScheduleSection,
232
+ });
233
+
234
+ const sessionClassTypeField = new SelectField(sessionObj, {
235
+ apiName: 'class_type__c',
236
+ displayName: 'Class type',
237
+ options: [
238
+ 'Yoga',
239
+ 'Spin',
240
+ 'HIIT',
241
+ 'Strength',
242
+ 'Pilates',
243
+ 'Dance',
244
+ 'Aquatics',
245
+ 'Recovery / stretch',
246
+ 'Other',
247
+ ],
248
+ required: true,
249
+ section: sessionTrainerSection,
250
+ });
251
+
252
+ const sessionRoomField = new TextField(sessionObj, {
253
+ apiName: 'room_name__c',
254
+ displayName: 'Studio / room',
255
+ maxLength: 80,
256
+ description: 'E.g. Studio A, Pool lane 2',
257
+ section: sessionTrainerSection,
258
+ });
259
+
260
+ const sessionCapacityField = new NumberField(sessionObj, {
261
+ apiName: 'capacity__c',
262
+ displayName: 'Capacity',
263
+ decimalPlaces: 0,
264
+ description: 'Maximum attendees for this session',
265
+ section: sessionTrainerSection,
266
+ });
267
+
268
+ const sessionNotesField = new LongTextField(sessionObj, {
269
+ apiName: 'session_notes__c',
270
+ displayName: 'Notes',
271
+ maxLength: 4000,
272
+ section: sessionTrainerSection,
273
+ });
274
+
275
+ const sessionDayField = new FormulaField(sessionObj, {
276
+ apiName: 'session_date__c',
277
+ displayName: 'Session date',
278
+ fieldType: 'DATE',
279
+ formula: "DATETRUNC('DAY', start_time__c)",
280
+ description: 'Calendar day for filtering “today” views',
281
+ section: sessionScheduleSection,
282
+ });
283
+
284
+ const sessionBoardLabel = new FormulaField(sessionObj, {
285
+ apiName: 'session_board_label__c',
286
+ displayName: 'Board label',
287
+ fieldType: 'TEXT',
288
+ maxLength: 260,
289
+ formula: "CONCATENATE(class_type__c, ' · ', room_name__c)",
290
+ section: sessionScheduleSection,
291
+ });
292
+
293
+ // ── Enrollment (child of session) ───────────────────────────────────────────
294
+ const enrollmentObj = new CustomObject(manifest, {
295
+ apiName: 'gym_class_enrollment__c',
296
+ name: 'Class enrollment',
297
+ pluralLabel: 'Class enrollments',
298
+ category: gymCategory,
299
+ description: 'Member registration for a session with attendance and check-in',
300
+ icon: { emoji: '✅' },
301
+ });
302
+
303
+ const enrollmentSessionSection = new CustomObjectFieldSection(enrollmentObj, {
304
+ name: 'Session & member',
305
+ sectionId: 'sec_ce_link',
306
+ });
307
+
308
+ const enrollmentAttendanceSection = new CustomObjectFieldSection(enrollmentObj, {
309
+ name: 'Attendance',
310
+ sectionId: 'sec_ce_att',
311
+ });
312
+
313
+ const enrollmentSessionRef = new ParentChildField(enrollmentObj, {
314
+ apiName: 'class_session_ref__c',
315
+ displayName: 'Class session',
316
+ target: sessionObj,
317
+ relatedDataLabel: 'Enrollments',
318
+ parentChildType: 'PRIMARY',
319
+ enableParentOwnerInheritance: false,
320
+ description: 'Which scheduled class this row belongs to',
321
+ section: enrollmentSessionSection,
322
+ });
323
+
324
+ const enrollmentMemberRef = new LookupField(enrollmentObj, {
325
+ apiName: 'member_ref__c',
326
+ displayName: 'Member',
327
+ target: memberObj,
328
+ relatedDataLabel: 'Class enrollments',
329
+ required: true,
330
+ section: enrollmentSessionSection,
331
+ });
332
+
333
+ const enrollmentAttendanceField = new SelectField(enrollmentObj, {
334
+ apiName: 'attendance_status__c',
335
+ displayName: 'Attendance',
336
+ options: ['Registered', 'Checked in', 'Late', 'No-show', 'Cancelled'],
337
+ required: true,
338
+ description: 'Front desk: set to Checked in when the member arrives',
339
+ section: enrollmentAttendanceSection,
340
+ });
341
+
342
+ const enrollmentCheckedInAt = new DatetimeField(enrollmentObj, {
343
+ apiName: 'checked_in_at__c',
344
+ displayName: 'Checked in at',
345
+ description: 'Timestamp when staff recorded arrival (optional)',
346
+ section: enrollmentAttendanceSection,
347
+ });
348
+
349
+ const enrollmentNotesField = new LongTextField(enrollmentObj, {
350
+ apiName: 'enrollment_notes__c',
351
+ displayName: 'Notes',
352
+ maxLength: 2000,
353
+ section: enrollmentAttendanceSection,
354
+ });
355
+
356
+ const enrollmentRosterTitle = new FormulaField(enrollmentObj, {
357
+ apiName: 'roster_row_label__c',
358
+ displayName: 'Roster row label',
359
+ fieldType: 'TEXT',
360
+ maxLength: 160,
361
+ formula: "CONCATENATE(attendance_status__c, ' · Enrollment')",
362
+ section: enrollmentAttendanceSection,
363
+ });
364
+
365
+ // ── Rollups ─────────────────────────────────────────────────────────────────
366
+ const enrollmentCountOnSession = new SummaryField(sessionObj, {
367
+ apiName: 'enrollment_count__c',
368
+ displayName: 'Enrolled (count)',
369
+ aggregates: enrollmentMemberRef,
370
+ using: 'COUNT',
371
+ lookup: enrollmentSessionRef,
372
+ description: 'Rows on this session’s roster',
373
+ section: sessionScheduleSection,
374
+ });
375
+
376
+ // Note: Rollups from session → trainer would require a parent–child edge from
377
+ // session to trainer; trainer is linked via LookupField instead, so session
378
+ // counts per trainer are surfaced via session list views and SDUI, not a rollup.
379
+
380
+ // ── Validations & defaults ────────────────────────────────────────────────────
381
+ new Validation(sessionObj, {
382
+ ruleId: 'gym_session_end_after_start',
383
+ displayName: 'End time must be after start',
384
+ rqlFormula: '(end_time__c <= start_time__c)',
385
+ errorMessage: 'End time must be after the start time.',
386
+ ruleDescription: 'Prevents zero-length or inverted intervals',
387
+ state: 'ACTIVE',
388
+ level: 'FIELD',
389
+ fieldRef: sessionEndField,
390
+ });
391
+
392
+ new Rule(enrollmentObj, {
393
+ flowId: 'gym_default_enrollment_status',
394
+ flowName: 'Default attendance to Registered',
395
+ triggerType: 'CREATE',
396
+ description: 'New roster rows start as Registered unless changed',
397
+ actions: [{ targetField: enrollmentAttendanceField, fixedValue: ['Registered'] }],
398
+ });
399
+
400
+ // ── List views ───────────────────────────────────────────────────────────────
401
+ new ListViewDef(memberObj, {
402
+ viewId: 'view_gym_all_members',
403
+ name: 'All members',
404
+ description: 'Directory with membership status',
405
+ fields: [memberDisplayName, memberEmail, memberPhone, memberStatusField, memberJoinDate],
406
+ orderBy: memberLastName,
407
+ sortOrder: 'ASC',
408
+ isPublic: true,
409
+ objectScope: 'all',
410
+ });
411
+
412
+ new ListViewDef(trainerObj, {
413
+ viewId: 'view_gym_all_trainers',
414
+ name: 'All trainers',
415
+ description: 'Staff roster with session counts',
416
+ fields: [trainerNameField, trainerEmailField, trainerSpecialtyField, trainerLabelField],
417
+ orderBy: trainerNameField,
418
+ sortOrder: 'ASC',
419
+ isPublic: true,
420
+ objectScope: 'all',
421
+ });
422
+
423
+ new ListViewDef(sessionObj, {
424
+ viewId: 'view_gym_all_sessions',
425
+ name: 'All class sessions',
426
+ description: 'Chronological schedule with trainer and enrollments',
427
+ fields: [
428
+ sessionTrainerRef,
429
+ sessionStartField,
430
+ sessionEndField,
431
+ sessionClassTypeField,
432
+ sessionRoomField,
433
+ sessionCapacityField,
434
+ enrollmentCountOnSession,
435
+ sessionBoardLabel,
436
+ ],
437
+ orderBy: sessionStartField,
438
+ sortOrder: 'ASC',
439
+ isPublic: true,
440
+ objectScope: 'all',
441
+ });
442
+
443
+ new ListViewDef(sessionObj, {
444
+ viewId: 'view_gym_today_sessions',
445
+ name: 'Today’s class sessions',
446
+ description: 'Sessions whose calendar day is today — use with Today’s schedule SDUI tab',
447
+ fields: [
448
+ sessionTrainerRef,
449
+ sessionStartField,
450
+ sessionEndField,
451
+ sessionClassTypeField,
452
+ sessionRoomField,
453
+ enrollmentCountOnSession,
454
+ sessionBoardLabel,
455
+ sessionDayField,
456
+ ],
457
+ orderBy: sessionStartField,
458
+ sortOrder: 'ASC',
459
+ isPublic: true,
460
+ objectScope: 'all',
461
+ rqlFilter: '(session_date__c == TODAY())',
462
+ });
463
+
464
+ new ListViewDef(enrollmentObj, {
465
+ viewId: 'view_gym_all_enrollments',
466
+ name: 'All enrollments',
467
+ description: 'Full roster grid',
468
+ fields: [
469
+ enrollmentSessionRef,
470
+ enrollmentMemberRef,
471
+ enrollmentAttendanceField,
472
+ enrollmentCheckedInAt,
473
+ enrollmentRosterTitle,
474
+ ],
475
+ orderBy: enrollmentMemberRef,
476
+ sortOrder: 'ASC',
477
+ isPublic: true,
478
+ objectScope: 'all',
479
+ });
480
+
481
+ // ── Page layouts ─────────────────────────────────────────────────────────────
482
+ new CustomObjectPageLayout(memberObj, {
483
+ apiName: 'default',
484
+ name: 'Member',
485
+ layoutId: 'layout_gym_member',
486
+ headerEdits: { newTitleField: memberDisplayName.getApiName() },
487
+ tabEdits: {
488
+ newTabs: [
489
+ {
490
+ key: 'member',
491
+ name: 'Member',
492
+ type: 'tab_with_sections',
493
+ sections: [
494
+ {
495
+ key: 'profile',
496
+ name: 'Profile',
497
+ type: 'fields_section',
498
+ fields: [
499
+ { fieldRqlName: memberFirstName.getApiName() },
500
+ { fieldRqlName: memberLastName.getApiName() },
501
+ { fieldRqlName: memberDisplayName.getApiName() },
502
+ { fieldRqlName: memberEmail.getApiName() },
503
+ { fieldRqlName: memberPhone.getApiName() },
504
+ ],
505
+ },
506
+ {
507
+ key: 'membership',
508
+ name: 'Membership',
509
+ type: 'fields_section',
510
+ fields: [
511
+ { fieldRqlName: memberStatusField.getApiName() },
512
+ { fieldRqlName: memberJoinDate.getApiName() },
513
+ { fieldRqlName: memberEmergencyField.getApiName() },
514
+ ],
515
+ },
516
+ ],
517
+ },
518
+ ],
519
+ systemTabEdits: {},
520
+ tabsOrder: ['member'],
521
+ },
522
+ visibilityConditions: {},
523
+ });
524
+
525
+ new CustomObjectPageLayout(trainerObj, {
526
+ apiName: 'default',
527
+ name: 'Trainer',
528
+ layoutId: 'layout_gym_trainer',
529
+ headerEdits: { newTitleField: trainerNameField.getApiName() },
530
+ tabEdits: {
531
+ newTabs: [
532
+ {
533
+ key: 'trainer',
534
+ name: 'Trainer',
535
+ type: 'tab_with_sections',
536
+ sections: [
537
+ {
538
+ key: 'info',
539
+ name: 'Details',
540
+ type: 'fields_section',
541
+ fields: [
542
+ { fieldRqlName: trainerNameField.getApiName() },
543
+ { fieldRqlName: trainerEmailField.getApiName() },
544
+ { fieldRqlName: trainerSpecialtyField.getApiName() },
545
+ { fieldRqlName: trainerLabelField.getApiName() },
546
+ { fieldRqlName: trainerBioField.getApiName() },
547
+ ],
548
+ },
549
+ ],
550
+ },
551
+ ],
552
+ systemTabEdits: {},
553
+ tabsOrder: ['trainer'],
554
+ },
555
+ visibilityConditions: {},
556
+ });
557
+
558
+ new CustomObjectPageLayout(sessionObj, {
559
+ apiName: 'default',
560
+ name: 'Class session',
561
+ layoutId: 'layout_gym_class_session',
562
+ headerEdits: { newTitleField: sessionBoardLabel.getApiName() },
563
+ tabEdits: {
564
+ newTabs: [
565
+ {
566
+ key: 'session',
567
+ name: 'Session',
568
+ type: 'tab_with_sections',
569
+ sections: [
570
+ {
571
+ key: 'schedule',
572
+ name: 'Schedule',
573
+ type: 'fields_section',
574
+ fields: [
575
+ { fieldRqlName: sessionTrainerRef.getApiName() },
576
+ { fieldRqlName: sessionStartField.getApiName() },
577
+ { fieldRqlName: sessionEndField.getApiName() },
578
+ { fieldRqlName: sessionDayField.getApiName() },
579
+ { fieldRqlName: sessionBoardLabel.getApiName() },
580
+ { fieldRqlName: enrollmentCountOnSession.getApiName() },
581
+ ],
582
+ },
583
+ {
584
+ key: 'class',
585
+ name: 'Class & room',
586
+ type: 'fields_section',
587
+ fields: [
588
+ { fieldRqlName: sessionClassTypeField.getApiName() },
589
+ { fieldRqlName: sessionRoomField.getApiName() },
590
+ { fieldRqlName: sessionCapacityField.getApiName() },
591
+ { fieldRqlName: sessionNotesField.getApiName() },
592
+ ],
593
+ },
594
+ ],
595
+ },
596
+ ],
597
+ systemTabEdits: {},
598
+ tabsOrder: ['session'],
599
+ },
600
+ visibilityConditions: {},
601
+ });
602
+
603
+ new CustomObjectPageLayout(enrollmentObj, {
604
+ apiName: 'default',
605
+ name: 'Enrollment',
606
+ layoutId: 'layout_gym_enrollment',
607
+ headerEdits: { newTitleField: enrollmentRosterTitle.getApiName() },
608
+ tabEdits: {
609
+ newTabs: [
610
+ {
611
+ key: 'roster',
612
+ name: 'Roster',
613
+ type: 'tab_with_sections',
614
+ sections: [
615
+ {
616
+ key: 'link',
617
+ name: 'Session & member',
618
+ type: 'fields_section',
619
+ fields: [
620
+ { fieldRqlName: enrollmentSessionRef.getApiName() },
621
+ { fieldRqlName: enrollmentMemberRef.getApiName() },
622
+ ],
623
+ },
624
+ {
625
+ key: 'attendance',
626
+ name: 'Attendance & check-in',
627
+ type: 'fields_section',
628
+ fields: [
629
+ { fieldRqlName: enrollmentAttendanceField.getApiName() },
630
+ { fieldRqlName: enrollmentCheckedInAt.getApiName() },
631
+ { fieldRqlName: enrollmentRosterTitle.getApiName() },
632
+ { fieldRqlName: enrollmentNotesField.getApiName() },
633
+ ],
634
+ },
635
+ ],
636
+ },
637
+ ],
638
+ systemTabEdits: {},
639
+ tabsOrder: ['roster'],
640
+ },
641
+ visibilityConditions: {},
642
+ });
643
+
644
+ // ── SDUI: today’s schedule + check-in desk ──────────────────────────────────
645
+ const todayScheduleCode = [
646
+ "import { FunctionContext, FunctionEvent, FunctionResponse } from '@rippling/rippling-sdk';",
647
+ "import SDUI from '@rippling/sdui-builder';",
648
+ '',
649
+ `const OBJECT = '${sessionObj.getApiName()}';`,
650
+ '',
651
+ 'export async function onRipplingEvent(',
652
+ ' event: FunctionEvent,',
653
+ ' context: FunctionContext,',
654
+ '): Promise<FunctionResponse> {',
655
+ ' const action = event.parameters?.action;',
656
+ '',
657
+ " if (!action || action === 'init') {",
658
+ ' const s = SDUI.createState({});',
659
+ ' const root = SDUI.components.VStack({',
660
+ ' children: [',
661
+ ' SDUI.components.ObjectListView({',
662
+ ' objectApiName: OBJECT,',
663
+ ' canSearchInGrid: true,',
664
+ ' }),',
665
+ ' ],',
666
+ ' });',
667
+ ' return new FunctionResponse({',
668
+ ' body: { spec: JSON.stringify(SDUI.render(s, root)) },',
669
+ ' statusCode: 200,',
670
+ ' headers: {},',
671
+ " message: '',",
672
+ ' });',
673
+ ' }',
674
+ '',
675
+ " return new FunctionResponse({ body: {}, statusCode: 200, headers: {}, message: '' });",
676
+ '}',
677
+ ].join('\n');
678
+
679
+ const checkInDeskCode = [
680
+ "import { FunctionContext, FunctionEvent, FunctionResponse } from '@rippling/rippling-sdk';",
681
+ "import SDUI from '@rippling/sdui-builder';",
682
+ '',
683
+ `const OBJECT = '${enrollmentObj.getApiName()}';`,
684
+ '',
685
+ 'export async function onRipplingEvent(',
686
+ ' event: FunctionEvent,',
687
+ ' context: FunctionContext,',
688
+ '): Promise<FunctionResponse> {',
689
+ ' const action = event.parameters?.action;',
690
+ '',
691
+ " if (!action || action === 'init') {",
692
+ ' const s = SDUI.createState({});',
693
+ ' const root = SDUI.components.VStack({',
694
+ ' children: [',
695
+ ' SDUI.components.ObjectListView({',
696
+ ' objectApiName: OBJECT,',
697
+ ' canSearchInGrid: true,',
698
+ ' }),',
699
+ ' ],',
700
+ ' });',
701
+ ' return new FunctionResponse({',
702
+ ' body: { spec: JSON.stringify(SDUI.render(s, root)) },',
703
+ ' statusCode: 200,',
704
+ ' headers: {},',
705
+ " message: '',",
706
+ ' });',
707
+ ' }',
708
+ '',
709
+ " return new FunctionResponse({ body: {}, statusCode: 200, headers: {}, message: '' });",
710
+ '}',
711
+ ].join('\n');
712
+
713
+ const todayScheduleFn = new ManifestFunction(manifest, {
714
+ apiName: 'gym_today_schedule_fn',
715
+ name: 'Today’s schedule (SDUI)',
716
+ description:
717
+ 'Searchable grid of class sessions — pair with list view “Today’s class sessions” or filter by date',
718
+ runtime: 'lambda-deno',
719
+ code: todayScheduleCode,
720
+ dependencies: { '@rippling/rippling-sdk': '^0.2.0-alpha.33', '@rippling/sdui-builder': '^0.1.0' },
721
+ isAsync: false,
722
+ });
723
+
724
+ const checkInDeskFn = new ManifestFunction(manifest, {
725
+ apiName: 'gym_check_in_desk_fn',
726
+ name: 'Check-in desk (SDUI)',
727
+ description: 'Searchable roster grid — update attendance and checked-in time as members arrive',
728
+ runtime: 'lambda-deno',
729
+ code: checkInDeskCode,
730
+ dependencies: { '@rippling/rippling-sdk': '^0.2.0-alpha.33', '@rippling/sdui-builder': '^0.1.0' },
731
+ isAsync: false,
732
+ });
733
+
734
+ const todaySchedulePage = new SduiPage(manifest, {
735
+ sduiPageId: 'gym_today_schedule',
736
+ name: 'Today’s schedule',
737
+ function: todayScheduleFn,
738
+ description: 'SDUI list of class sessions for front desk (filter to today in the grid)',
739
+ });
740
+
741
+ const checkInDeskPage = new SduiPage(manifest, {
742
+ sduiPageId: 'gym_check_in_desk',
743
+ name: 'Check-in desk',
744
+ function: checkInDeskFn,
745
+ description: 'SDUI roster — set attendance to Checked in and optional checked-in time',
746
+ });
747
+
748
+ new App(manifest, {
749
+ apiName: 'gym_membership_management_app',
750
+ name: 'Gym Membership Management',
751
+ description:
752
+ 'Members, trainers, schedules, enrollments, today’s sessions list view, and SDUI for schedule + check-in',
753
+ appType: 'custom',
754
+ pages: [memberObj, trainerObj, sessionObj, enrollmentObj, todaySchedulePage, checkInDeskPage],
755
+ });
756
+
757
+ console.log(manifest.preview());