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

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 (232) hide show
  1. package/client.d.mts +7 -1
  2. package/client.d.mts.map +1 -1
  3. package/client.d.ts +7 -1
  4. package/client.d.ts.map +1 -1
  5. package/client.js +7 -1
  6. package/client.js.map +1 -1
  7. package/client.mjs +7 -1
  8. package/client.mjs.map +1 -1
  9. package/examples/manifest/dental-practice-management/dental-practice-management.ts +21 -34
  10. package/examples/manifest/gym-membership-management/gym-membership-management.ts +27 -38
  11. package/examples/manifest/gym-membership-management/manifest.json +1 -1
  12. package/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +3 -3
  13. package/examples/sdui/sdui-showcase/sdui-showcase.ts +6 -11
  14. package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +14 -24
  15. package/examples/sdui/vote/vote.ts +7 -11
  16. package/internal/utils/log.d.mts.map +1 -1
  17. package/internal/utils/log.d.ts.map +1 -1
  18. package/internal/utils/log.js +2 -0
  19. package/internal/utils/log.js.map +1 -1
  20. package/internal/utils/log.mjs +2 -0
  21. package/internal/utils/log.mjs.map +1 -1
  22. package/lib/manifest/category.d.mts +4 -2
  23. package/lib/manifest/category.d.mts.map +1 -1
  24. package/lib/manifest/category.d.ts +4 -2
  25. package/lib/manifest/category.d.ts.map +1 -1
  26. package/lib/manifest/category.js +2 -2
  27. package/lib/manifest/category.js.map +1 -1
  28. package/lib/manifest/category.mjs +2 -2
  29. package/lib/manifest/category.mjs.map +1 -1
  30. package/lib/manifest/custom-object-field-section.d.mts +4 -0
  31. package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
  32. package/lib/manifest/custom-object-field-section.d.ts +4 -0
  33. package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
  34. package/lib/manifest/custom-object-field-section.js +6 -0
  35. package/lib/manifest/custom-object-field-section.js.map +1 -1
  36. package/lib/manifest/custom-object-field-section.mjs +6 -0
  37. package/lib/manifest/custom-object-field-section.mjs.map +1 -1
  38. package/lib/manifest/custom-object-page-layout.d.mts +23 -13
  39. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  40. package/lib/manifest/custom-object-page-layout.d.ts +23 -13
  41. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  42. package/lib/manifest/custom-object-page-layout.js +122 -9
  43. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  44. package/lib/manifest/custom-object-page-layout.mjs +122 -9
  45. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  46. package/lib/manifest/custom-object.d.mts +30 -1
  47. package/lib/manifest/custom-object.d.mts.map +1 -1
  48. package/lib/manifest/custom-object.d.ts +30 -1
  49. package/lib/manifest/custom-object.d.ts.map +1 -1
  50. package/lib/manifest/custom-object.js +15 -1
  51. package/lib/manifest/custom-object.js.map +1 -1
  52. package/lib/manifest/custom-object.mjs +15 -1
  53. package/lib/manifest/custom-object.mjs.map +1 -1
  54. package/lib/manifest/field.d.mts +25 -3
  55. package/lib/manifest/field.d.mts.map +1 -1
  56. package/lib/manifest/field.d.ts +25 -3
  57. package/lib/manifest/field.d.ts.map +1 -1
  58. package/lib/manifest/field.js +28 -2
  59. package/lib/manifest/field.js.map +1 -1
  60. package/lib/manifest/field.mjs +28 -2
  61. package/lib/manifest/field.mjs.map +1 -1
  62. package/lib/manifest/index.d.mts +1 -1
  63. package/lib/manifest/index.d.mts.map +1 -1
  64. package/lib/manifest/index.d.ts +1 -1
  65. package/lib/manifest/index.d.ts.map +1 -1
  66. package/lib/manifest/index.js.map +1 -1
  67. package/lib/manifest/index.mjs.map +1 -1
  68. package/lib/manifest/manifest-builder.d.mts +1 -1
  69. package/lib/manifest/manifest-builder.d.ts +1 -1
  70. package/lib/manifest/manifest-builder.js +1 -1
  71. package/lib/manifest/manifest-builder.mjs +1 -1
  72. package/lib/manifest/manifest-function.d.mts +8 -2
  73. package/lib/manifest/manifest-function.d.mts.map +1 -1
  74. package/lib/manifest/manifest-function.d.ts +8 -2
  75. package/lib/manifest/manifest-function.d.ts.map +1 -1
  76. package/lib/manifest/manifest-function.js +8 -2
  77. package/lib/manifest/manifest-function.js.map +1 -1
  78. package/lib/manifest/manifest-function.mjs +8 -2
  79. package/lib/manifest/manifest-function.mjs.map +1 -1
  80. package/lib/sdui/actions.d.mts.map +1 -1
  81. package/lib/sdui/actions.d.ts.map +1 -1
  82. package/lib/sdui/actions.js +23 -13
  83. package/lib/sdui/actions.js.map +1 -1
  84. package/lib/sdui/actions.mjs +23 -13
  85. package/lib/sdui/actions.mjs.map +1 -1
  86. package/lib/sdui/component.d.mts +28 -11
  87. package/lib/sdui/component.d.mts.map +1 -1
  88. package/lib/sdui/component.d.ts +28 -11
  89. package/lib/sdui/component.d.ts.map +1 -1
  90. package/lib/sdui/component.js +69 -36
  91. package/lib/sdui/component.js.map +1 -1
  92. package/lib/sdui/component.mjs +69 -36
  93. package/lib/sdui/component.mjs.map +1 -1
  94. package/lib/sdui/components.d.mts +1141 -132
  95. package/lib/sdui/components.d.mts.map +1 -1
  96. package/lib/sdui/components.d.ts +1141 -132
  97. package/lib/sdui/components.d.ts.map +1 -1
  98. package/lib/sdui/components.js +834 -206
  99. package/lib/sdui/components.js.map +1 -1
  100. package/lib/sdui/components.mjs +830 -203
  101. package/lib/sdui/components.mjs.map +1 -1
  102. package/lib/sdui/directives.d.mts.map +1 -1
  103. package/lib/sdui/directives.d.ts.map +1 -1
  104. package/lib/sdui/directives.js +4 -1
  105. package/lib/sdui/directives.js.map +1 -1
  106. package/lib/sdui/directives.mjs +4 -1
  107. package/lib/sdui/directives.mjs.map +1 -1
  108. package/lib/sdui/index.d.mts +314 -261
  109. package/lib/sdui/index.d.mts.map +1 -1
  110. package/lib/sdui/index.d.ts +314 -261
  111. package/lib/sdui/index.d.ts.map +1 -1
  112. package/lib/sdui/index.js +255 -204
  113. package/lib/sdui/index.js.map +1 -1
  114. package/lib/sdui/index.mjs +128 -103
  115. package/lib/sdui/index.mjs.map +1 -1
  116. package/lib/sdui/propTypes.d.mts +6330 -0
  117. package/lib/sdui/propTypes.d.mts.map +1 -0
  118. package/lib/sdui/propTypes.d.ts +6330 -0
  119. package/lib/sdui/propTypes.d.ts.map +1 -0
  120. package/lib/sdui/propTypes.js +14 -0
  121. package/lib/sdui/propTypes.js.map +1 -0
  122. package/lib/sdui/propTypes.mjs +13 -0
  123. package/lib/sdui/propTypes.mjs.map +1 -0
  124. package/lib/sdui/render.d.mts +1 -1
  125. package/lib/sdui/render.d.mts.map +1 -1
  126. package/lib/sdui/render.d.ts +1 -1
  127. package/lib/sdui/render.d.ts.map +1 -1
  128. package/lib/sdui/render.js +11 -3
  129. package/lib/sdui/render.js.map +1 -1
  130. package/lib/sdui/render.mjs +11 -3
  131. package/lib/sdui/render.mjs.map +1 -1
  132. package/lib/sdui/state.d.mts.map +1 -1
  133. package/lib/sdui/state.d.ts.map +1 -1
  134. package/lib/sdui/state.js +3 -1
  135. package/lib/sdui/state.js.map +1 -1
  136. package/lib/sdui/state.mjs +3 -1
  137. package/lib/sdui/state.mjs.map +1 -1
  138. package/package.json +1 -1
  139. package/resources/company-legal-entities.d.mts +180 -0
  140. package/resources/company-legal-entities.d.mts.map +1 -0
  141. package/resources/company-legal-entities.d.ts +180 -0
  142. package/resources/company-legal-entities.d.ts.map +1 -0
  143. package/resources/company-legal-entities.js +30 -0
  144. package/resources/company-legal-entities.js.map +1 -0
  145. package/resources/company-legal-entities.mjs +26 -0
  146. package/resources/company-legal-entities.mjs.map +1 -0
  147. package/resources/earning-types.d.mts +1 -6
  148. package/resources/earning-types.d.mts.map +1 -1
  149. package/resources/earning-types.d.ts +1 -6
  150. package/resources/earning-types.d.ts.map +1 -1
  151. package/resources/earning-types.js +1 -1
  152. package/resources/earning-types.mjs +1 -1
  153. package/resources/earnings-inputs.d.mts +1 -1
  154. package/resources/earnings-inputs.d.ts +1 -1
  155. package/resources/earnings-inputs.js +1 -1
  156. package/resources/earnings-inputs.mjs +1 -1
  157. package/resources/index.d.mts +2 -1
  158. package/resources/index.d.mts.map +1 -1
  159. package/resources/index.d.ts +2 -1
  160. package/resources/index.d.ts.map +1 -1
  161. package/resources/index.js +5 -3
  162. package/resources/index.js.map +1 -1
  163. package/resources/index.mjs +2 -1
  164. package/resources/index.mjs.map +1 -1
  165. package/resources/legal-entities.d.mts +1 -1
  166. package/resources/legal-entities.d.mts.map +1 -1
  167. package/resources/legal-entities.d.ts +1 -1
  168. package/resources/legal-entities.d.ts.map +1 -1
  169. package/resources/payroll-runs/index.d.mts +3 -0
  170. package/resources/payroll-runs/index.d.mts.map +1 -0
  171. package/resources/payroll-runs/index.d.ts +3 -0
  172. package/resources/payroll-runs/index.d.ts.map +1 -0
  173. package/resources/payroll-runs/index.js +9 -0
  174. package/resources/payroll-runs/index.js.map +1 -0
  175. package/resources/payroll-runs/index.mjs +4 -0
  176. package/resources/payroll-runs/index.mjs.map +1 -0
  177. package/resources/payroll-runs/payroll-runs.d.mts +199 -0
  178. package/resources/payroll-runs/payroll-runs.d.mts.map +1 -0
  179. package/resources/payroll-runs/payroll-runs.d.ts +199 -0
  180. package/resources/payroll-runs/payroll-runs.d.ts.map +1 -0
  181. package/resources/payroll-runs/payroll-runs.js +40 -0
  182. package/resources/payroll-runs/payroll-runs.js.map +1 -0
  183. package/resources/payroll-runs/payroll-runs.mjs +35 -0
  184. package/resources/payroll-runs/payroll-runs.mjs.map +1 -0
  185. package/resources/payroll-runs/worker-payroll-records.d.mts +200 -0
  186. package/resources/payroll-runs/worker-payroll-records.d.mts.map +1 -0
  187. package/resources/payroll-runs/worker-payroll-records.d.ts +200 -0
  188. package/resources/payroll-runs/worker-payroll-records.d.ts.map +1 -0
  189. package/resources/payroll-runs/worker-payroll-records.js +23 -0
  190. package/resources/payroll-runs/worker-payroll-records.js.map +1 -0
  191. package/resources/payroll-runs/worker-payroll-records.mjs +19 -0
  192. package/resources/payroll-runs/worker-payroll-records.mjs.map +1 -0
  193. package/resources/payroll-runs.d.mts +1 -194
  194. package/resources/payroll-runs.d.mts.map +1 -1
  195. package/resources/payroll-runs.d.ts +1 -194
  196. package/resources/payroll-runs.d.ts.map +1 -1
  197. package/resources/payroll-runs.js +2 -28
  198. package/resources/payroll-runs.js.map +1 -1
  199. package/resources/payroll-runs.mjs +1 -26
  200. package/resources/payroll-runs.mjs.map +1 -1
  201. package/src/client.ts +27 -7
  202. package/src/internal/utils/log.ts +2 -0
  203. package/src/lib/manifest/category.ts +7 -5
  204. package/src/lib/manifest/custom-object-field-section.ts +7 -0
  205. package/src/lib/manifest/custom-object-page-layout.ts +225 -18
  206. package/src/lib/manifest/custom-object.ts +30 -1
  207. package/src/lib/manifest/field.ts +29 -3
  208. package/src/lib/manifest/index.ts +2 -0
  209. package/src/lib/manifest/manifest-builder.ts +1 -1
  210. package/src/lib/manifest/manifest-function.ts +8 -2
  211. package/src/lib/sdui/actions.ts +23 -10
  212. package/src/lib/sdui/component.ts +84 -44
  213. package/src/lib/sdui/components.ts +1627 -212
  214. package/src/lib/sdui/directives.ts +4 -1
  215. package/src/lib/sdui/index.ts +565 -200
  216. package/src/lib/sdui/propTypes.ts +8470 -0
  217. package/src/lib/sdui/render.ts +13 -5
  218. package/src/lib/sdui/state.ts +8 -2
  219. package/src/resources/company-legal-entities.ts +237 -0
  220. package/src/resources/earning-types.ts +1 -7
  221. package/src/resources/earnings-inputs.ts +1 -1
  222. package/src/resources/index.ts +8 -1
  223. package/src/resources/legal-entities.ts +1 -1
  224. package/src/resources/payroll-runs/index.ts +15 -0
  225. package/src/resources/payroll-runs/payroll-runs.ts +270 -0
  226. package/src/resources/payroll-runs/worker-payroll-records.ts +255 -0
  227. package/src/resources/payroll-runs.ts +1 -249
  228. package/src/version.ts +1 -1
  229. package/version.d.mts +1 -1
  230. package/version.d.ts +1 -1
  231. package/version.js +1 -1
  232. package/version.mjs +1 -1
@@ -1,4 +1,5 @@
1
1
  import { generateId } from './_helpers';
2
+ import { CustomObjectFieldSection } from './custom-object-field-section';
2
3
  import type { CustomObject } from './custom-object';
3
4
  import type { Field } from './field';
4
5
 
@@ -65,10 +66,8 @@ export interface HeaderEdits {
65
66
  // tab_edits — TabEditsDTO + section/tab DTOs
66
67
  // ---------------------------------------------------------------------------
67
68
 
68
- /** One field entry inside a `fields_section`. */
69
- export interface SectionField {
70
- /** RQL name of the field to display. Required. */
71
- fieldRqlName: string;
69
+ /** Options for one field entry inside a `fields_section`. */
70
+ export interface SectionFieldOptions {
72
71
  /** Hide this field from view. Optional. */
73
72
  hidden?: boolean;
74
73
  /** Allow inline editing of this field. Optional. */
@@ -77,15 +76,28 @@ export interface SectionField {
77
76
  movementOption?: 'tab' | 'free';
78
77
  }
79
78
 
79
+ /** A typed field entry inside a `fields_section`. */
80
+ export interface SectionFieldRef extends SectionFieldOptions {
81
+ /** Field to display. Required. */
82
+ field: Field;
83
+ }
84
+
85
+ /** One field entry inside a `fields_section`. */
86
+ export type SectionField = Field | SectionFieldRef;
87
+
80
88
  /** A standard field-grid section inside a tab. */
81
89
  export interface FieldsSection {
82
90
  type: 'fields_section';
83
- /** Stable key for this section. Required. */
84
- key: string;
85
- /** Display name of the section. Required. */
86
- name: string;
91
+ /** Field section metadata for the fields displayed in this layout section. Required. */
92
+ section: CustomObjectFieldSection;
93
+ /** Stable layout key for this section. Optional. Defaults to the field section id. */
94
+ key?: string;
95
+ /** Display name of the layout section. Optional. Defaults to the field section name. */
96
+ name?: string;
87
97
  /** Fields to display in this section. Required. */
88
98
  fields: SectionField[];
99
+ /** Column layout for the fields. Optional. @default `'responsive'` (backend default). */
100
+ layout?: SectionFieldsLayout;
89
101
  }
90
102
 
91
103
  /**
@@ -255,6 +267,197 @@ export interface CustomObjectPageLayoutProps {
255
267
  blueprintKey?: string;
256
268
  }
257
269
 
270
+ interface SerializedSectionField extends SectionFieldOptions {
271
+ fieldRqlName: string;
272
+ }
273
+
274
+ interface SerializedFieldsSection {
275
+ type: 'fields_section';
276
+ key: string;
277
+ name: string;
278
+ fields: SerializedSectionField[];
279
+ layout?: SectionFieldsLayout;
280
+ }
281
+
282
+ interface SerializedTabWithSections {
283
+ type: 'tab_with_sections';
284
+ key: string;
285
+ name: string;
286
+ sections: SerializedFieldsSection[];
287
+ }
288
+
289
+ type SerializedPageLayoutTab = SerializedTabWithSections | CustomTab;
290
+
291
+ interface SerializedSystemSectionEdit extends Omit<SystemSectionEdit, 'newFields'> {
292
+ newFields?: SerializedSectionField[];
293
+ }
294
+
295
+ interface SerializedSystemTabEdit extends Omit<SystemTabEdit, 'newSections' | 'systemSectionEdits'> {
296
+ newSections?: SerializedFieldsSection[];
297
+ systemSectionEdits?: Record<string, SerializedSystemSectionEdit>;
298
+ }
299
+
300
+ interface SerializedTabEdits extends Omit<TabEdits, 'newTabs' | 'systemTabEdits'> {
301
+ newTabs?: SerializedPageLayoutTab[];
302
+ systemTabEdits?: Record<string, SerializedSystemTabEdit>;
303
+ }
304
+
305
+ function isField(value: unknown): value is Field {
306
+ const candidate = value as {
307
+ getApiName?: unknown;
308
+ getCustomObjectApiName?: unknown;
309
+ getSectionId?: unknown;
310
+ _assignSection?: unknown;
311
+ } | null;
312
+ return (
313
+ candidate != null &&
314
+ typeof candidate.getApiName === 'function' &&
315
+ typeof candidate.getCustomObjectApiName === 'function' &&
316
+ typeof candidate.getSectionId === 'function' &&
317
+ typeof candidate._assignSection === 'function'
318
+ );
319
+ }
320
+
321
+ function isFieldRef(value: SectionField): value is SectionFieldRef {
322
+ const candidate = value as { field?: unknown } | null;
323
+ return candidate != null && typeof candidate === 'object' && isField(candidate.field);
324
+ }
325
+
326
+ function assertSameObject(customObject: CustomObject, field: Field, sectionKey: string): void {
327
+ if (field.getCustomObjectApiName() !== customObject.getApiName()) {
328
+ throw new Error(
329
+ `CustomObjectPageLayout fields_section "${sectionKey}" references field "${field.getApiName()}" ` +
330
+ `from "${field.getCustomObjectApiName()}", but the layout belongs to "${customObject.getApiName()}".`,
331
+ );
332
+ }
333
+ }
334
+
335
+ function assertSectionBelongsTo(customObject: CustomObject, section: CustomObjectFieldSection): void {
336
+ if (section.getModelApiName() !== customObject.getApiName()) {
337
+ throw new Error(
338
+ `CustomObjectPageLayout section "${section.getSectionId()}" belongs to "${section.getModelApiName()}", ` +
339
+ `but the layout belongs to "${customObject.getApiName()}".`,
340
+ );
341
+ }
342
+ }
343
+
344
+ function toSerializedSectionField(
345
+ fieldRqlName: string,
346
+ options: SectionFieldOptions = {},
347
+ ): SerializedSectionField {
348
+ const normalized: SerializedSectionField = { fieldRqlName };
349
+ if (options.hidden != null) normalized.hidden = options.hidden;
350
+ if (options.editable != null) normalized.editable = options.editable;
351
+ if (options.movementOption != null) normalized.movementOption = options.movementOption;
352
+ return normalized;
353
+ }
354
+
355
+ function normalizeSectionField(
356
+ customObject: CustomObject,
357
+ section: CustomObjectFieldSection | string,
358
+ sectionKey: string,
359
+ entry: SectionField,
360
+ ): SerializedSectionField {
361
+ if (isField(entry)) {
362
+ assertSameObject(customObject, entry, sectionKey);
363
+ entry._assignSection(section);
364
+ return toSerializedSectionField(entry.getApiName());
365
+ }
366
+
367
+ if (isFieldRef(entry)) {
368
+ assertSameObject(customObject, entry.field, sectionKey);
369
+ entry.field._assignSection(section);
370
+ return toSerializedSectionField(entry.field.getApiName(), entry);
371
+ }
372
+
373
+ throw new Error(
374
+ `CustomObjectPageLayout fields_section "${sectionKey}" has an invalid field entry; ` +
375
+ 'pass a Field instance or { field }.',
376
+ );
377
+ }
378
+
379
+ function normalizeFieldsSection(
380
+ customObject: CustomObject,
381
+ input: PageLayoutSection,
382
+ ): SerializedFieldsSection {
383
+ assertSectionBelongsTo(customObject, input.section);
384
+ const key = input.key ?? input.section.getSectionId();
385
+ const name = input.name ?? input.section.getName();
386
+ const normalized: SerializedFieldsSection = {
387
+ key,
388
+ name,
389
+ type: 'fields_section',
390
+ fields: input.fields.map((field) => normalizeSectionField(customObject, input.section, key, field)),
391
+ };
392
+ if (input.layout != null) normalized.layout = input.layout;
393
+ return normalized;
394
+ }
395
+
396
+ function normalizeTab(customObject: CustomObject, tab: PageLayoutTab): SerializedPageLayoutTab {
397
+ if (tab.type === 'custom_tab') return tab;
398
+ return {
399
+ ...tab,
400
+ sections: tab.sections.map((section) => normalizeFieldsSection(customObject, section)),
401
+ };
402
+ }
403
+
404
+ function normalizeSystemSectionEdit(
405
+ customObject: CustomObject,
406
+ tabKey: string,
407
+ sectionKey: string,
408
+ edit: SystemSectionEdit,
409
+ ): SerializedSystemSectionEdit {
410
+ const { newFields: _newFields, ...rest } = edit;
411
+ const normalized: SerializedSystemSectionEdit = { ...rest };
412
+ if (edit.newFields != null) {
413
+ normalized.newFields = edit.newFields.map((field) =>
414
+ normalizeSectionField(customObject, sectionKey, `${tabKey}.${sectionKey}`, field),
415
+ );
416
+ }
417
+ return normalized;
418
+ }
419
+
420
+ function normalizeSystemTabEdit(
421
+ customObject: CustomObject,
422
+ tabKey: string,
423
+ edit: SystemTabEdit,
424
+ ): SerializedSystemTabEdit {
425
+ const { newSections: _newSections, systemSectionEdits: _systemSectionEdits, ...rest } = edit;
426
+ const normalized: SerializedSystemTabEdit = { ...rest };
427
+ if (edit.newSections != null) {
428
+ normalized.newSections = edit.newSections.map((section) => normalizeFieldsSection(customObject, section));
429
+ }
430
+ if (edit.systemSectionEdits != null) {
431
+ const systemSectionEdits: Record<string, SerializedSystemSectionEdit> = {};
432
+ for (const [sectionKey, sectionEdit] of Object.entries(edit.systemSectionEdits)) {
433
+ systemSectionEdits[sectionKey] = normalizeSystemSectionEdit(
434
+ customObject,
435
+ tabKey,
436
+ sectionKey,
437
+ sectionEdit,
438
+ );
439
+ }
440
+ normalized.systemSectionEdits = systemSectionEdits;
441
+ }
442
+ return normalized;
443
+ }
444
+
445
+ function normalizeTabEdits(customObject: CustomObject, tabEdits: TabEdits): SerializedTabEdits {
446
+ const { newTabs: _newTabs, systemTabEdits: _systemTabEdits, ...rest } = tabEdits;
447
+ const normalized: SerializedTabEdits = { ...rest };
448
+ if (tabEdits.newTabs != null) {
449
+ normalized.newTabs = tabEdits.newTabs.map((tab) => normalizeTab(customObject, tab));
450
+ }
451
+ if (tabEdits.systemTabEdits != null) {
452
+ const systemTabEdits: Record<string, SerializedSystemTabEdit> = {};
453
+ for (const [tabKey, tabEdit] of Object.entries(tabEdits.systemTabEdits)) {
454
+ systemTabEdits[tabKey] = normalizeSystemTabEdit(customObject, tabKey, tabEdit);
455
+ }
456
+ normalized.systemTabEdits = systemTabEdits;
457
+ }
458
+ return normalized;
459
+ }
460
+
258
461
  // ---------------------------------------------------------------------------
259
462
  // Class
260
463
  // ---------------------------------------------------------------------------
@@ -283,11 +486,11 @@ export interface CustomObjectPageLayoutProps {
283
486
  * newTabs: [{
284
487
  * key: 'member', name: 'Member', type: 'tab_with_sections',
285
488
  * sections: [{
286
- * key: 'profile', name: 'Profile', type: 'fields_section',
287
- * fields: [
288
- * { fieldRqlName: memberFirstName.getApiName() },
289
- * { fieldRqlName: memberEmail.getApiName() },
290
- * ],
489
+ * key: 'profile',
490
+ * name: 'Profile',
491
+ * type: 'fields_section',
492
+ * section: profileSection,
493
+ * fields: [memberFirstName, memberEmail],
291
494
  * }],
292
495
  * }],
293
496
  * systemTabEdits: {},
@@ -304,7 +507,7 @@ export class CustomObjectPageLayout {
304
507
  private readonly _apiName: string;
305
508
  private readonly _customObjectApiName: string;
306
509
  private readonly _name: string;
307
- private readonly _tabEdits: TabEdits;
510
+ private readonly _tabEdits: SerializedTabEdits;
308
511
  private readonly _headerEdits: HeaderEdits;
309
512
  private readonly _visibilityConditions: VisibilityConditions;
310
513
  private readonly _blueprintKey: string | undefined;
@@ -321,7 +524,7 @@ export class CustomObjectPageLayout {
321
524
  this._apiName = props.apiName;
322
525
  this._customObjectApiName = customObject.getApiName();
323
526
  this._name = props.name;
324
- this._tabEdits = props.tabEdits ?? {};
527
+ this._tabEdits = normalizeTabEdits(customObject, props.tabEdits ?? {});
325
528
  this._headerEdits = props.headerEdits ?? {};
326
529
  this._visibilityConditions = props.visibilityConditions ?? {};
327
530
  this._blueprintKey = props.blueprintKey;
@@ -396,6 +599,11 @@ export class CustomObjectPageLayout {
396
599
  ): CustomObjectPageLayout {
397
600
  const coApiName = customObject.getApiName();
398
601
  const coName = customObject.getName();
602
+ const sectionPrefix = coApiName.replace(/__c$/, '').replace(/[^a-zA-Z0-9]+/g, '_');
603
+ const generalSection = new CustomObjectFieldSection(customObject, {
604
+ sectionId: `sec_${sectionPrefix}_general`,
605
+ name: 'General',
606
+ });
399
607
  return new CustomObjectPageLayout(customObject, {
400
608
  apiName: 'default',
401
609
  name: `${coName} Layout`,
@@ -410,9 +618,8 @@ export class CustomObjectPageLayout {
410
618
  key: 'general',
411
619
  name: 'General',
412
620
  type: 'fields_section',
413
- fields: (props.fields ?? []).map((f: Field) => ({
414
- fieldRqlName: f.getApiName(),
415
- })),
621
+ section: generalSection,
622
+ fields: props.fields ?? [],
416
623
  },
417
624
  ],
418
625
  },
@@ -215,6 +215,21 @@ export interface CustomObjectProps {
215
215
  * Overrides for the auto-number name field — controls the format string and starting number.
216
216
  * Leave `null` to use the default name field behavior.
217
217
  *
218
+ * Example:
219
+ * ```ts
220
+ * nameFieldDetails: {
221
+ * is_autonumber_field: true,
222
+ * starting_number: 1,
223
+ * autonumber_format: 'EXP-{0000}',
224
+ * }
225
+ * ```
226
+ *
227
+ * @remarks
228
+ * The backend auto-appends `{0000}` to `autonumber_format` if the format string
229
+ * doesn't already contain a sequence token — so `'EXP-'` becomes `'EXP-{0000}'`
230
+ * on the wire. Include the token explicitly when you want a different width
231
+ * (e.g. `'EXP-{00000}'`).
232
+ *
218
233
  * Optional. @default null
219
234
  */
220
235
  nameFieldDetails?: Record<string, any> | null;
@@ -241,7 +256,7 @@ export interface CustomObjectProps {
241
256
  * name: 'Gym Membership Management',
242
257
  * });
243
258
  *
244
- * const gymCategory = new Category(manifest, { apiName: 'gym__c', name: 'Gym' });
259
+ * const gymCategory = new Category(manifest, { apiName: 'gym__c', name: 'Gym', description: 'Gym domain' });
245
260
  *
246
261
  * const memberObj = new CustomObject(manifest, {
247
262
  * apiName: 'gym_member__c',
@@ -266,6 +281,20 @@ export interface CustomObjectProps {
266
281
  * with `DEPENDENCY_ERROR: Page layout config for CO (api_name=...) not found in package
267
282
  * configs. CO requires at least one page layout in the package.`
268
283
  * Use {@link CustomObjectPageLayout.basic} for a minimal single-tab layout.
284
+ *
285
+ * **Consider defining at least one {@link ListViewDef}.** Without a list view,
286
+ * the records page has nothing to display by default — users land on an empty grid.
287
+ * Consider shipping a "default" view with the columns most users want to see
288
+ * (typically `name`, the status / category field, owner, and `created_at`), sorted
289
+ * however the records are most commonly browsed.
290
+ *
291
+ * **Consider system fields before authoring new ones.** Every CO ships with
292
+ * `owner_role`, `created_by`, and `last_modified_by` (all `Employee` references),
293
+ * plus `name`, `created_at`, `updated_at`, `external_id`, and `id`. For any
294
+ * "the employee responsible for this record" semantics, consider using
295
+ * `owner_role` rather than adding a new {@link LookupField} targeting `'role'`.
296
+ * `owner_role` drives Rippling's record-level permissions, so a parallel field
297
+ * can fragment ownership and complicate the permission story.
269
298
  */
270
299
  export class CustomObject implements ManifestScope {
271
300
  private readonly _scope: ManifestScope;
@@ -130,7 +130,7 @@ export class Field {
130
130
  private readonly _rqlFormula: string | null;
131
131
  private readonly _formulaAttrMetas: Record<string, any> | null;
132
132
  private readonly _derivedFieldFormula: string | null;
133
- private readonly _section: string | null;
133
+ private _section: string | null;
134
134
  private readonly _summarySpec: SummarySpec | null;
135
135
  private readonly _derivedAggregatedFieldStatic: Record<string, any> | null;
136
136
 
@@ -202,6 +202,14 @@ export class Field {
202
202
  isSummary(): boolean {
203
203
  return this._summarySpec != null;
204
204
  }
205
+ /** Returns the field section id assigned to this field, if any. */
206
+ getSectionId(): string | null {
207
+ return this._section;
208
+ }
209
+ /** @internal Assigns this field to a field section during page-layout normalization. */
210
+ _assignSection(section: CustomObjectFieldSection | string): void {
211
+ this._section = typeof section === 'string' ? section : section.getSectionId();
212
+ }
205
213
 
206
214
  // ── Serialization ──────────────────────────────────────────────────────
207
215
 
@@ -438,7 +446,12 @@ export interface SelectFieldProps extends CommonFieldProps {
438
446
  /** Whether a value is required on save. Optional. */
439
447
  required?: boolean;
440
448
  }
441
- /** Dropdown single-select field. */
449
+ /**
450
+ * Multi-select dropdown field. Renders as a dropdown that allows multiple
451
+ * options to be selected at once.
452
+ *
453
+ * Use {@link RadioField} instead when only one option should be selectable.
454
+ */
442
455
  export class SelectField extends Field {
443
456
  constructor(customObject: CustomObject, props: SelectFieldProps) {
444
457
  super(customObject, { ...props, fieldType: 'SELECT' });
@@ -456,7 +469,11 @@ export interface RadioFieldProps extends CommonFieldProps {
456
469
  /** Whether a value is required on save. Optional. */
457
470
  required?: boolean;
458
471
  }
459
- /** Radio button group field. */
472
+ /**
473
+ * Single-select radio button group field. Exactly one option can be selected.
474
+ *
475
+ * Use {@link SelectField} instead when multiple options should be selectable.
476
+ */
460
477
  export class RadioField extends Field {
461
478
  constructor(customObject: CustomObject, props: RadioFieldProps) {
462
479
  super(customObject, { ...props, fieldType: 'RADIO' });
@@ -530,6 +547,15 @@ export interface LookupFieldProps extends CommonFieldProps {
530
547
  *
531
548
  * Use {@link ParentChildField} instead when the relationship should create a
532
549
  * parent-child hierarchy with rollup support.
550
+ *
551
+ * @remarks
552
+ * **For "the employee responsible for this record" semantics, consider the built-in
553
+ * `owner_role` standard field instead of a new `LookupField` to `'role'`.** Every
554
+ * `CustomObject` ships with `owner_role` (an Employee reference) plus `created_by`
555
+ * and `last_modified_by`. `owner_role` drives Rippling's record-level permissions,
556
+ * so a parallel custom employee field can fragment ownership and complicate the
557
+ * permission story. Consider a new `LookupField` to `'role'` when you need a
558
+ * *second*, non-owner employee reference (e.g. an "approver", a "manager on record").
533
559
  */
534
560
  export class LookupField extends Field {
535
561
  constructor(customObject: CustomObject, props: LookupFieldProps) {
@@ -35,6 +35,8 @@ export {
35
35
  type PageLayoutSection,
36
36
  type PageLayoutTab,
37
37
  type SectionField,
38
+ type SectionFieldOptions,
39
+ type SectionFieldRef,
38
40
  type SectionFieldsLayout,
39
41
  type SystemHeaderFieldEdit,
40
42
  type SystemSectionEdit,
@@ -63,7 +63,7 @@ export interface ManifestBuilderProps {
63
63
  * description: 'Members, trainers, schedules, and check-in',
64
64
  * });
65
65
  *
66
- * const category = new Category(manifest, { apiName: 'gym__c', name: 'Gym' });
66
+ * const category = new Category(manifest, { apiName: 'gym__c', name: 'Gym', description: 'Gym domain' });
67
67
  * const memberObj = new CustomObject(manifest, { apiName: 'gym_member__c', name: 'Member', category });
68
68
  * // ... define fields, rules, list views, etc. ...
69
69
  *
@@ -68,8 +68,14 @@ export interface ManifestFunctionProps {
68
68
  * Defines a serverless function and registers it with the manifest.
69
69
  *
70
70
  * A `ManifestFunction` packages TypeScript handler code that runs on Rippling's
71
- * serverless infrastructure. Functions are the backing implementation for
72
- * {@link SduiPage} surfaces. The handler must export `onRipplingEvent`.
71
+ * serverless infrastructure. The handler must export `onRipplingEvent`.
72
+ *
73
+ * Functions have two primary uses:
74
+ * 1. **Authoring SDUI for Custom Apps** — the function returns an SDUI spec
75
+ * (built with `@rippling/sdui-builder`) that renders as a page tab. Wrap the
76
+ * function in an {@link SduiPage} and reference it from an {@link App}.
77
+ * 2. **Workflow / event handlers** — functions invoked from rules, automations,
78
+ * or callbacks that do not render UI.
73
79
  *
74
80
  * This class is named `ManifestFunction` to avoid collision with the JavaScript
75
81
  * built-in `Function`. It is also re-exported as `Function` from the barrel for
@@ -12,15 +12,19 @@ export function set(ref: unknown, value: unknown): ActionBinding {
12
12
  }
13
13
 
14
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
- }
15
+ const params: Record<string, unknown> = {
16
+ statePath: extractPath(ref),
17
+ value,
18
+ ...(opts?.clear ? { clearStatePath: extractPath(opts.clear) } : {}),
19
+ };
19
20
  return { action: 'pushState', params };
20
21
  }
21
22
 
22
23
  export function remove(ref: unknown, index: unknown): ActionBinding {
23
- return { action: 'removeState', params: { statePath: extractPath(ref), index } };
24
+ return {
25
+ action: 'removeState',
26
+ params: { statePath: extractPath(ref), index },
27
+ };
24
28
  }
25
29
 
26
30
  export function callFunction(
@@ -29,11 +33,20 @@ export function callFunction(
29
33
  opts?: Record<string, unknown>,
30
34
  ): ActionBinding {
31
35
  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;
36
+ // Spread rest FIRST so explicit functionId/action always win if opts
37
+ // happens to carry colliding keys.
38
+ const params: Record<string, unknown> = {
39
+ ...rest,
40
+ functionId,
41
+ action,
42
+ ...(responsePath !== undefined ? { responsePath } : {}),
43
+ };
44
+ const binding: ActionBinding = {
45
+ action: 'triggerFunction',
46
+ params,
47
+ ...(onSuccess ? { onSuccess: onSuccess as ActionBinding } : {}),
48
+ ...(onError ? { onError: onError as ActionBinding } : {}),
49
+ };
37
50
  return binding;
38
51
  }
39
52