@rippling/rippling-sdk 0.2.0-alpha.37 → 0.2.0-alpha.39

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 (202) hide show
  1. package/examples/sdui/sdui-crud-todo/sdui-crud-todo.ts +3 -3
  2. package/examples/sdui/sdui-showcase/sdui-showcase.ts +6 -11
  3. package/examples/sdui/sdui-tab-multi-view/sdui-tab-multi-view.ts +14 -24
  4. package/examples/sdui/vote/vote.ts +7 -11
  5. package/lib/manifest/app.d.mts +11 -1
  6. package/lib/manifest/app.d.mts.map +1 -1
  7. package/lib/manifest/app.d.ts +11 -1
  8. package/lib/manifest/app.d.ts.map +1 -1
  9. package/lib/manifest/app.js +42 -1
  10. package/lib/manifest/app.js.map +1 -1
  11. package/lib/manifest/app.mjs +42 -1
  12. package/lib/manifest/app.mjs.map +1 -1
  13. package/lib/manifest/category.d.mts +14 -3
  14. package/lib/manifest/category.d.mts.map +1 -1
  15. package/lib/manifest/category.d.ts +14 -3
  16. package/lib/manifest/category.d.ts.map +1 -1
  17. package/lib/manifest/category.js +25 -3
  18. package/lib/manifest/category.js.map +1 -1
  19. package/lib/manifest/category.mjs +25 -3
  20. package/lib/manifest/category.mjs.map +1 -1
  21. package/lib/manifest/custom-object-field-section.d.mts +13 -0
  22. package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
  23. package/lib/manifest/custom-object-field-section.d.ts +13 -0
  24. package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
  25. package/lib/manifest/custom-object-field-section.js +22 -1
  26. package/lib/manifest/custom-object-field-section.js.map +1 -1
  27. package/lib/manifest/custom-object-field-section.mjs +22 -1
  28. package/lib/manifest/custom-object-field-section.mjs.map +1 -1
  29. package/lib/manifest/custom-object-page-layout.d.mts +23 -0
  30. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  31. package/lib/manifest/custom-object-page-layout.d.ts +23 -0
  32. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  33. package/lib/manifest/custom-object-page-layout.js +45 -2
  34. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  35. package/lib/manifest/custom-object-page-layout.mjs +45 -2
  36. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  37. package/lib/manifest/custom-object.d.mts +41 -2
  38. package/lib/manifest/custom-object.d.mts.map +1 -1
  39. package/lib/manifest/custom-object.d.ts +41 -2
  40. package/lib/manifest/custom-object.d.ts.map +1 -1
  41. package/lib/manifest/custom-object.js +70 -3
  42. package/lib/manifest/custom-object.js.map +1 -1
  43. package/lib/manifest/custom-object.mjs +70 -3
  44. package/lib/manifest/custom-object.mjs.map +1 -1
  45. package/lib/manifest/existing-manifest-context.d.mts +87 -0
  46. package/lib/manifest/existing-manifest-context.d.mts.map +1 -0
  47. package/lib/manifest/existing-manifest-context.d.ts +87 -0
  48. package/lib/manifest/existing-manifest-context.d.ts.map +1 -0
  49. package/lib/manifest/existing-manifest-context.js +511 -0
  50. package/lib/manifest/existing-manifest-context.js.map +1 -0
  51. package/lib/manifest/existing-manifest-context.mjs +506 -0
  52. package/lib/manifest/existing-manifest-context.mjs.map +1 -0
  53. package/lib/manifest/field.d.mts +44 -2
  54. package/lib/manifest/field.d.mts.map +1 -1
  55. package/lib/manifest/field.d.ts +44 -2
  56. package/lib/manifest/field.d.ts.map +1 -1
  57. package/lib/manifest/field.js +134 -3
  58. package/lib/manifest/field.js.map +1 -1
  59. package/lib/manifest/field.mjs +134 -3
  60. package/lib/manifest/field.mjs.map +1 -1
  61. package/lib/manifest/index.d.mts +8 -7
  62. package/lib/manifest/index.d.mts.map +1 -1
  63. package/lib/manifest/index.d.ts +8 -7
  64. package/lib/manifest/index.d.ts.map +1 -1
  65. package/lib/manifest/index.js +3 -1
  66. package/lib/manifest/index.js.map +1 -1
  67. package/lib/manifest/index.mjs +5 -4
  68. package/lib/manifest/index.mjs.map +1 -1
  69. package/lib/manifest/list-view.d.mts +17 -0
  70. package/lib/manifest/list-view.d.mts.map +1 -1
  71. package/lib/manifest/list-view.d.ts +17 -0
  72. package/lib/manifest/list-view.d.ts.map +1 -1
  73. package/lib/manifest/list-view.js +38 -1
  74. package/lib/manifest/list-view.js.map +1 -1
  75. package/lib/manifest/list-view.mjs +38 -1
  76. package/lib/manifest/list-view.mjs.map +1 -1
  77. package/lib/manifest/manifest-builder.d.mts +60 -7
  78. package/lib/manifest/manifest-builder.d.mts.map +1 -1
  79. package/lib/manifest/manifest-builder.d.ts +60 -7
  80. package/lib/manifest/manifest-builder.d.ts.map +1 -1
  81. package/lib/manifest/manifest-builder.js +62 -4
  82. package/lib/manifest/manifest-builder.js.map +1 -1
  83. package/lib/manifest/manifest-builder.mjs +62 -4
  84. package/lib/manifest/manifest-builder.mjs.map +1 -1
  85. package/lib/manifest/manifest-function.d.mts +18 -3
  86. package/lib/manifest/manifest-function.d.mts.map +1 -1
  87. package/lib/manifest/manifest-function.d.ts +18 -3
  88. package/lib/manifest/manifest-function.d.ts.map +1 -1
  89. package/lib/manifest/manifest-function.js +44 -3
  90. package/lib/manifest/manifest-function.js.map +1 -1
  91. package/lib/manifest/manifest-function.mjs +44 -3
  92. package/lib/manifest/manifest-function.mjs.map +1 -1
  93. package/lib/manifest/rule.d.mts +17 -0
  94. package/lib/manifest/rule.d.mts.map +1 -1
  95. package/lib/manifest/rule.d.ts +17 -0
  96. package/lib/manifest/rule.d.ts.map +1 -1
  97. package/lib/manifest/rule.js +45 -1
  98. package/lib/manifest/rule.js.map +1 -1
  99. package/lib/manifest/rule.mjs +45 -1
  100. package/lib/manifest/rule.mjs.map +1 -1
  101. package/lib/manifest/sdui-page.d.mts +10 -1
  102. package/lib/manifest/sdui-page.d.mts.map +1 -1
  103. package/lib/manifest/sdui-page.d.ts +10 -1
  104. package/lib/manifest/sdui-page.d.ts.map +1 -1
  105. package/lib/manifest/sdui-page.js +28 -1
  106. package/lib/manifest/sdui-page.js.map +1 -1
  107. package/lib/manifest/sdui-page.mjs +28 -1
  108. package/lib/manifest/sdui-page.mjs.map +1 -1
  109. package/lib/manifest/validation.d.mts +17 -0
  110. package/lib/manifest/validation.d.mts.map +1 -1
  111. package/lib/manifest/validation.d.ts +17 -0
  112. package/lib/manifest/validation.d.ts.map +1 -1
  113. package/lib/manifest/validation.js +31 -1
  114. package/lib/manifest/validation.js.map +1 -1
  115. package/lib/manifest/validation.mjs +31 -1
  116. package/lib/manifest/validation.mjs.map +1 -1
  117. package/lib/sdui/actions.d.mts.map +1 -1
  118. package/lib/sdui/actions.d.ts.map +1 -1
  119. package/lib/sdui/actions.js +23 -13
  120. package/lib/sdui/actions.js.map +1 -1
  121. package/lib/sdui/actions.mjs +23 -13
  122. package/lib/sdui/actions.mjs.map +1 -1
  123. package/lib/sdui/component.d.mts +28 -11
  124. package/lib/sdui/component.d.mts.map +1 -1
  125. package/lib/sdui/component.d.ts +28 -11
  126. package/lib/sdui/component.d.ts.map +1 -1
  127. package/lib/sdui/component.js +69 -36
  128. package/lib/sdui/component.js.map +1 -1
  129. package/lib/sdui/component.mjs +69 -36
  130. package/lib/sdui/component.mjs.map +1 -1
  131. package/lib/sdui/components.d.mts +1141 -132
  132. package/lib/sdui/components.d.mts.map +1 -1
  133. package/lib/sdui/components.d.ts +1141 -132
  134. package/lib/sdui/components.d.ts.map +1 -1
  135. package/lib/sdui/components.js +834 -206
  136. package/lib/sdui/components.js.map +1 -1
  137. package/lib/sdui/components.mjs +830 -203
  138. package/lib/sdui/components.mjs.map +1 -1
  139. package/lib/sdui/directives.d.mts.map +1 -1
  140. package/lib/sdui/directives.d.ts.map +1 -1
  141. package/lib/sdui/directives.js +4 -1
  142. package/lib/sdui/directives.js.map +1 -1
  143. package/lib/sdui/directives.mjs +4 -1
  144. package/lib/sdui/directives.mjs.map +1 -1
  145. package/lib/sdui/index.d.mts +314 -261
  146. package/lib/sdui/index.d.mts.map +1 -1
  147. package/lib/sdui/index.d.ts +314 -261
  148. package/lib/sdui/index.d.ts.map +1 -1
  149. package/lib/sdui/index.js +255 -204
  150. package/lib/sdui/index.js.map +1 -1
  151. package/lib/sdui/index.mjs +128 -103
  152. package/lib/sdui/index.mjs.map +1 -1
  153. package/lib/sdui/propTypes.d.mts +6330 -0
  154. package/lib/sdui/propTypes.d.mts.map +1 -0
  155. package/lib/sdui/propTypes.d.ts +6330 -0
  156. package/lib/sdui/propTypes.d.ts.map +1 -0
  157. package/lib/sdui/propTypes.js +14 -0
  158. package/lib/sdui/propTypes.js.map +1 -0
  159. package/lib/sdui/propTypes.mjs +13 -0
  160. package/lib/sdui/propTypes.mjs.map +1 -0
  161. package/lib/sdui/render.d.mts +1 -1
  162. package/lib/sdui/render.d.mts.map +1 -1
  163. package/lib/sdui/render.d.ts +1 -1
  164. package/lib/sdui/render.d.ts.map +1 -1
  165. package/lib/sdui/render.js +11 -3
  166. package/lib/sdui/render.js.map +1 -1
  167. package/lib/sdui/render.mjs +11 -3
  168. package/lib/sdui/render.mjs.map +1 -1
  169. package/lib/sdui/state.d.mts.map +1 -1
  170. package/lib/sdui/state.d.ts.map +1 -1
  171. package/lib/sdui/state.js +3 -1
  172. package/lib/sdui/state.js.map +1 -1
  173. package/lib/sdui/state.mjs +3 -1
  174. package/lib/sdui/state.mjs.map +1 -1
  175. package/package.json +1 -1
  176. package/src/lib/manifest/app.ts +46 -2
  177. package/src/lib/manifest/category.ts +35 -7
  178. package/src/lib/manifest/custom-object-field-section.ts +37 -1
  179. package/src/lib/manifest/custom-object-page-layout.ts +67 -2
  180. package/src/lib/manifest/custom-object.ts +88 -4
  181. package/src/lib/manifest/existing-manifest-context.ts +673 -0
  182. package/src/lib/manifest/field.ts +160 -3
  183. package/src/lib/manifest/index.ts +39 -4
  184. package/src/lib/manifest/list-view.ts +50 -1
  185. package/src/lib/manifest/manifest-builder.ts +115 -5
  186. package/src/lib/manifest/manifest-function.ts +44 -4
  187. package/src/lib/manifest/rule.ts +60 -1
  188. package/src/lib/manifest/sdui-page.ts +37 -2
  189. package/src/lib/manifest/validation.ts +48 -1
  190. package/src/lib/sdui/actions.ts +23 -10
  191. package/src/lib/sdui/component.ts +84 -44
  192. package/src/lib/sdui/components.ts +1627 -212
  193. package/src/lib/sdui/directives.ts +4 -1
  194. package/src/lib/sdui/index.ts +565 -200
  195. package/src/lib/sdui/propTypes.ts +8470 -0
  196. package/src/lib/sdui/render.ts +13 -5
  197. package/src/lib/sdui/state.ts +8 -2
  198. package/src/version.ts +1 -1
  199. package/version.d.mts +1 -1
  200. package/version.d.ts +1 -1
  201. package/version.js +1 -1
  202. package/version.mjs +1 -1
@@ -7,6 +7,13 @@ import {
7
7
  } from './_helpers';
8
8
  import type { CustomObject } from './custom-object';
9
9
  import type { CustomObjectFieldSection } from './custom-object-field-section';
10
+ import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
11
+ import type { ManifestComponentDeletion } from './manifest-builder';
12
+
13
+ export interface CustomObjectFieldDeletionIdentifier {
14
+ customObjectApiName: string;
15
+ fieldApiName: string;
16
+ }
10
17
 
11
18
  /**
12
19
  * A filter condition applied to a {@link SummaryField}.
@@ -53,6 +60,11 @@ function validateApiName(apiName: string): void {
53
60
  }
54
61
  }
55
62
 
63
+ function cloneJson<T>(value: T): T {
64
+ if (value == null) return value;
65
+ return JSON.parse(JSON.stringify(value)) as T;
66
+ }
67
+
56
68
  /** Props shared by every field variant. */
57
69
  interface CommonFieldProps {
58
70
  apiName: string;
@@ -90,6 +102,61 @@ export interface FieldProps {
90
102
  summarySpec?: SummarySpec | undefined;
91
103
  }
92
104
 
105
+ export interface FieldLoadFromExistingOptions extends ExistingManifestContextOptions {
106
+ customObjectApiName?: string;
107
+ }
108
+
109
+ class ExistingFieldReference {
110
+ private _section: string | null;
111
+
112
+ constructor(
113
+ private readonly customObjectApiName: string,
114
+ private readonly component: Record<string, any>,
115
+ ) {
116
+ this._section = (component['section'] as string | null | undefined) ?? null;
117
+ }
118
+
119
+ getApiName(): string {
120
+ return this.component['field_api_name'];
121
+ }
122
+
123
+ getDisplayName(): string {
124
+ return this.component['field_display_name'] ?? this.getApiName();
125
+ }
126
+
127
+ getCustomObjectApiName(): string {
128
+ return this.customObjectApiName;
129
+ }
130
+
131
+ getFieldType(): FieldType | null {
132
+ return (this.getDataType()['field_type'] as FieldType | undefined) ?? null;
133
+ }
134
+
135
+ getDataType(): Readonly<Record<string, any>> {
136
+ return this.component['data_type'] ?? {};
137
+ }
138
+
139
+ isFormula(): boolean {
140
+ return this.component['derived_field_formula'] != null;
141
+ }
142
+
143
+ isSummary(): boolean {
144
+ return this.component['derived_aggregated_field'] != null;
145
+ }
146
+
147
+ getSectionId(): string | null {
148
+ return this._section;
149
+ }
150
+
151
+ _assignSection(section: CustomObjectFieldSection | string): void {
152
+ this._section = typeof section === 'string' ? section : section.getSectionId();
153
+ }
154
+
155
+ toDict(): Record<string, any> {
156
+ return cloneJson(this.component);
157
+ }
158
+ }
159
+
93
160
  // ────────────────────────────────────────────────────────────────────────
94
161
  // Base class
95
162
  // ────────────────────────────────────────────────────────────────────────
@@ -119,6 +186,16 @@ export interface FieldProps {
119
186
  * @see {@link TextField}, {@link NumberField}, {@link LookupField}, {@link SummaryField}
120
187
  */
121
188
  export class Field {
189
+ static readonly componentType = 'CUSTOM_OBJECT_FIELD' as const;
190
+
191
+ static toDeletionIdentifier(identifier: CustomObjectFieldDeletionIdentifier): ManifestComponentDeletion {
192
+ return {
193
+ type: Field.componentType,
194
+ custom_object_api_name: identifier.customObjectApiName,
195
+ field_api_name: identifier.fieldApiName,
196
+ };
197
+ }
198
+
122
199
  private readonly _fieldApiName: string;
123
200
  private readonly _fieldDisplayName: string;
124
201
  private readonly _customObjectApiName: string;
@@ -172,6 +249,68 @@ export class Field {
172
249
  customObject._register(this);
173
250
  }
174
251
 
252
+ /**
253
+ * Loads this field from the active existing-manifest JSON context.
254
+ *
255
+ * For fields whose api_name is not globally unique, pass `customObjectApiName`
256
+ * or use a qualified id like `'object__c.field__c'`.
257
+ */
258
+ static loadFromExisting(apiName: string, options: FieldLoadFromExistingOptions = {}): Field {
259
+ return getExistingManifestContext(options).loadField(apiName, options);
260
+ }
261
+
262
+ /** @internal Hydrates a custom object field from existing manifest wire JSON. */
263
+ static _fromExistingComponent(customObject: CustomObject, component: Record<string, any>): Field {
264
+ const props: FieldProps = {
265
+ apiName: component['field_api_name'],
266
+ displayName: component['field_display_name'] ?? component['field_api_name'],
267
+ };
268
+ if (component['data_type'] !== undefined) props.dataType = cloneJson(component['data_type'] ?? {});
269
+ if (component['description'] !== undefined) props.description = component['description'];
270
+ if (component['is_required'] !== undefined) props.required = component['is_required'];
271
+ if (component['is_unique'] !== undefined) props.unique = component['is_unique'];
272
+ if (component['is_indexed'] !== undefined) props.indexed = component['is_indexed'];
273
+ if (component['rql_formula'] !== undefined) props.rqlFormula = component['rql_formula'];
274
+ if (component['formula_attr_metas'] !== undefined)
275
+ props.formulaAttrMetas = component['formula_attr_metas'];
276
+ if (component['derived_aggregated_field'] !== undefined) {
277
+ props.derivedAggregatedField = component['derived_aggregated_field'];
278
+ }
279
+ if (component['derived_field_formula'] !== undefined)
280
+ props.derivedFieldFormula = component['derived_field_formula'];
281
+ if (component['section'] !== undefined) props.section = component['section'];
282
+ return new Field(customObject, props);
283
+ }
284
+
285
+ /** @internal Creates a non-registered reference for standard or external fields. */
286
+ static _referenceFromExistingComponent(component: Record<string, any>): Field {
287
+ return new ExistingFieldReference(
288
+ component['custom_object_api_name'],
289
+ cloneJson(component),
290
+ ) as unknown as Field;
291
+ }
292
+
293
+ /** @internal Creates a non-registered reference for a known wire field name. */
294
+ static _syntheticReference(customObjectApiName: string, fieldApiName: string): Field {
295
+ return new ExistingFieldReference(customObjectApiName, {
296
+ type: 'CUSTOM_OBJECT_FIELD',
297
+ custom_object_api_name: customObjectApiName,
298
+ field_api_name: fieldApiName,
299
+ field_display_name: fieldApiName,
300
+ data_type: {},
301
+ description: null,
302
+ is_required: null,
303
+ is_unique: null,
304
+ is_indexed: null,
305
+ rql_formula: null,
306
+ formula_attr_metas: null,
307
+ is_standard: true,
308
+ derived_aggregated_field: null,
309
+ derived_field_formula: null,
310
+ section: null,
311
+ }) as unknown as Field;
312
+ }
313
+
175
314
  // ── Getters ────────────────────────────────────────────────────────────
176
315
 
177
316
  /** Returns the api_name of this field (e.g. `'first_name__c'`). */
@@ -220,7 +359,7 @@ export class Field {
220
359
  */
221
360
  toDict(): Record<string, any> {
222
361
  return {
223
- type: 'CUSTOM_OBJECT_FIELD',
362
+ type: Field.componentType,
224
363
  custom_object_api_name: this._customObjectApiName,
225
364
  field_api_name: this._fieldApiName,
226
365
  field_display_name: this._fieldDisplayName,
@@ -446,7 +585,12 @@ export interface SelectFieldProps extends CommonFieldProps {
446
585
  /** Whether a value is required on save. Optional. */
447
586
  required?: boolean;
448
587
  }
449
- /** Dropdown single-select field. */
588
+ /**
589
+ * Multi-select dropdown field. Renders as a dropdown that allows multiple
590
+ * options to be selected at once.
591
+ *
592
+ * Use {@link RadioField} instead when only one option should be selectable.
593
+ */
450
594
  export class SelectField extends Field {
451
595
  constructor(customObject: CustomObject, props: SelectFieldProps) {
452
596
  super(customObject, { ...props, fieldType: 'SELECT' });
@@ -464,7 +608,11 @@ export interface RadioFieldProps extends CommonFieldProps {
464
608
  /** Whether a value is required on save. Optional. */
465
609
  required?: boolean;
466
610
  }
467
- /** Radio button group field. */
611
+ /**
612
+ * Single-select radio button group field. Exactly one option can be selected.
613
+ *
614
+ * Use {@link SelectField} instead when multiple options should be selectable.
615
+ */
468
616
  export class RadioField extends Field {
469
617
  constructor(customObject: CustomObject, props: RadioFieldProps) {
470
618
  super(customObject, { ...props, fieldType: 'RADIO' });
@@ -538,6 +686,15 @@ export interface LookupFieldProps extends CommonFieldProps {
538
686
  *
539
687
  * Use {@link ParentChildField} instead when the relationship should create a
540
688
  * parent-child hierarchy with rollup support.
689
+ *
690
+ * @remarks
691
+ * **For "the employee responsible for this record" semantics, consider the built-in
692
+ * `owner_role` standard field instead of a new `LookupField` to `'role'`.** Every
693
+ * `CustomObject` ships with `owner_role` (an Employee reference) plus `created_by`
694
+ * and `last_modified_by`. `owner_role` drives Rippling's record-level permissions,
695
+ * so a parallel custom employee field can fragment ownership and complicate the
696
+ * permission story. Consider a new `LookupField` to `'role'` when you need a
697
+ * *second*, non-owner employee reference (e.g. an "approver", a "manager on record").
541
698
  */
542
699
  export class LookupField extends Field {
543
700
  constructor(customObject: CustomObject, props: LookupFieldProps) {
@@ -23,15 +23,21 @@ export { Category, type CategoryProps } from './category';
23
23
 
24
24
  export { CustomObject, type CustomObjectProps, type IconConfig } from './custom-object';
25
25
 
26
- export { CustomObjectFieldSection, type CustomObjectFieldSectionProps } from './custom-object-field-section';
26
+ export {
27
+ CustomObjectFieldSection,
28
+ type CustomObjectFieldSectionLoadFromExistingOptions,
29
+ type CustomObjectFieldSectionProps,
30
+ } from './custom-object-field-section';
27
31
 
28
32
  export {
29
33
  CustomObjectPageLayout,
34
+ type CustomObjectPageLayoutLoadFromExistingOptions,
30
35
  type CustomObjectPageLayoutProps,
31
36
  type CustomTab,
32
37
  type FieldsSection,
33
38
  type HeaderEdits,
34
39
  type NewHeaderField,
40
+ type PageLayoutDeletionIdentifier,
35
41
  type PageLayoutSection,
36
42
  type PageLayoutTab,
37
43
  type SectionField,
@@ -49,6 +55,8 @@ export {
49
55
 
50
56
  export {
51
57
  Field,
58
+ type CustomObjectFieldDeletionIdentifier,
59
+ type FieldLoadFromExistingOptions,
52
60
  type FieldProps,
53
61
  type SummaryFilter,
54
62
 
@@ -94,12 +102,28 @@ export {
94
102
  type SummaryFieldProps,
95
103
  } from './field';
96
104
 
97
- export { ListViewDef, type ListViewDefProps } from './list-view';
105
+ export {
106
+ ExistingManifestContext,
107
+ type ExistingManifest,
108
+ type ExistingManifestContextOptions,
109
+ type ExistingManifestSource,
110
+ } from './existing-manifest-context';
111
+
112
+ export {
113
+ ListViewDef,
114
+ type ListViewDefLoadFromExistingOptions,
115
+ type ListViewDefProps,
116
+ type ListViewDeletionIdentifier,
117
+ } from './list-view';
98
118
 
99
119
  export {
100
120
  ManifestBuilder,
121
+ type BuiltManifest,
101
122
  type ManifestBuilderProps,
102
123
  type ManifestComponent,
124
+ type ManifestComponentClass,
125
+ type ManifestComponentDeletion,
126
+ type ManifestComponentType,
103
127
  type ManifestScope,
104
128
  } from './manifest-builder';
105
129
 
@@ -109,8 +133,19 @@ export {
109
133
  type ManifestFunctionProps,
110
134
  } from './manifest-function';
111
135
 
112
- export { Rule, type RuleAction, type RuleProps } from './rule';
136
+ export {
137
+ Rule,
138
+ type RuleAction,
139
+ type RuleDeletionIdentifier,
140
+ type RuleLoadFromExistingOptions,
141
+ type RuleProps,
142
+ } from './rule';
113
143
 
114
144
  export { SduiPage, type SduiPageProps } from './sdui-page';
115
145
 
116
- export { Validation, type ValidationProps } from './validation';
146
+ export {
147
+ Validation,
148
+ type ValidationDeletionIdentifier,
149
+ type ValidationLoadFromExistingOptions,
150
+ type ValidationProps,
151
+ } from './validation';
@@ -1,6 +1,13 @@
1
1
  import { generateId, type ObjectScope, type SortOrder } from './_helpers';
2
2
  import type { CustomObject } from './custom-object';
3
+ import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
3
4
  import type { Field } from './field';
5
+ import type { ManifestComponentDeletion } from './manifest-builder';
6
+
7
+ export interface ListViewDeletionIdentifier {
8
+ viewId: string;
9
+ objectRqlName: string;
10
+ }
4
11
 
5
12
  /**
6
13
  * Initialization properties for {@link ListViewDef}.
@@ -70,6 +77,10 @@ export interface ListViewDefProps {
70
77
  rqlFilter?: string;
71
78
  }
72
79
 
80
+ export interface ListViewDefLoadFromExistingOptions extends ExistingManifestContextOptions {
81
+ customObjectApiName?: string;
82
+ }
83
+
73
84
  /**
74
85
  * Defines a list view and registers it with the manifest.
75
86
  *
@@ -104,6 +115,16 @@ export interface ListViewDefProps {
104
115
  * ```
105
116
  */
106
117
  export class ListViewDef {
118
+ static readonly componentType = 'CUSTOM_OBJECT_LIST_VIEW' as const;
119
+
120
+ static toDeletionIdentifier(identifier: ListViewDeletionIdentifier): ManifestComponentDeletion {
121
+ return {
122
+ type: ListViewDef.componentType,
123
+ view_id: identifier.viewId,
124
+ object_rql_name: identifier.objectRqlName,
125
+ };
126
+ }
127
+
107
128
  private readonly _viewId: string;
108
129
  private readonly _customObjectApiName: string;
109
130
  private readonly _name: string;
@@ -143,6 +164,34 @@ export class ListViewDef {
143
164
  customObject._register(this);
144
165
  }
145
166
 
167
+ /**
168
+ * Loads this list view from the active existing-manifest JSON context.
169
+ */
170
+ static loadFromExisting(viewId: string, options: ListViewDefLoadFromExistingOptions = {}): ListViewDef {
171
+ return getExistingManifestContext(options).loadListView(viewId, options);
172
+ }
173
+
174
+ /** @internal Hydrates a list view from existing manifest wire JSON. */
175
+ static _fromExistingComponent(
176
+ customObject: CustomObject,
177
+ component: Record<string, any>,
178
+ resolveField: (apiName: string) => Field,
179
+ ): ListViewDef {
180
+ const props: ListViewDefProps = {
181
+ viewId: component['view_id'],
182
+ name: component['name'] ?? component['view_id'],
183
+ fields: (component['fields'] ?? []).map((fieldApiName: string) => resolveField(fieldApiName)),
184
+ isPublic: Boolean(component['public']),
185
+ };
186
+ if (component['description'] !== undefined) props.description = component['description'];
187
+ if (component['object_scope'] !== undefined) props.objectScope = component['object_scope'];
188
+ if (component['sort_order'] !== undefined) props.sortOrder = component['sort_order'];
189
+ if (component['column_config'] !== undefined) props.columnConfig = component['column_config'];
190
+ if (component['rql_filter'] !== undefined) props.rqlFilter = component['rql_filter'];
191
+ if (component['order_by'] != null) props.orderBy = resolveField(component['order_by']);
192
+ return new ListViewDef(customObject, props);
193
+ }
194
+
146
195
  /**
147
196
  * Returns the stable identifier for this view (e.g. `'view_gym_all_members'`).
148
197
  */
@@ -165,7 +214,7 @@ export class ListViewDef {
165
214
  */
166
215
  toDict(): Record<string, any> {
167
216
  const d: Record<string, any> = {
168
- type: 'CUSTOM_OBJECT_LIST_VIEW',
217
+ type: ListViewDef.componentType,
169
218
  object_rql_name: this._customObjectApiName,
170
219
  view_id: this._viewId,
171
220
  name: this._name,
@@ -1,3 +1,5 @@
1
+ import { ExistingManifestContext, type ExistingManifestSource } from './existing-manifest-context';
2
+
1
3
  /**
2
4
  * Any object that can be included in a manifest.
3
5
  *
@@ -8,6 +10,39 @@ export interface ManifestComponent {
8
10
  toDict(): Record<string, any>;
9
11
  }
10
12
 
13
+ /**
14
+ * Wire-level component types supported by the manifest builder.
15
+ */
16
+ export type ManifestComponentType =
17
+ | 'CUSTOM_APP'
18
+ | 'CUSTOM_CATEGORY'
19
+ | 'CUSTOM_OBJECT'
20
+ | 'CUSTOM_OBJECT_FIELD'
21
+ | 'CUSTOM_OBJECT_FIELD_SECTION'
22
+ | 'CUSTOM_OBJECT_LIST_VIEW'
23
+ | 'CUSTOM_OBJECT_PAGE_LAYOUT'
24
+ | 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW'
25
+ | 'CUSTOM_OBJECT_VALIDATION_RULE'
26
+ | 'FUNCTION'
27
+ | 'SDUI_PAGE';
28
+
29
+ /**
30
+ * Static side of a manifest component class.
31
+ */
32
+ export interface ManifestComponentClass<TIdentifierInput = unknown> {
33
+ readonly componentType: ManifestComponentType;
34
+ toDeletionIdentifier(identifier: TIdentifierInput): ManifestComponentDeletion;
35
+ new (...args: any[]): ManifestComponent;
36
+ }
37
+
38
+ /**
39
+ * A manifest component reference that should be deleted during install.
40
+ */
41
+ export interface ManifestComponentDeletion extends Record<string, any> {
42
+ /** Wire-level component type, e.g. `'CUSTOM_OBJECT'` or `'CUSTOM_OBJECT_FIELD'`. */
43
+ type: ManifestComponentType;
44
+ }
45
+
11
46
  /**
12
47
  * A scope — anything that can accept child component registrations.
13
48
  *
@@ -44,6 +79,15 @@ export interface ManifestBuilderProps {
44
79
  description?: string;
45
80
  }
46
81
 
82
+ /**
83
+ * Plain object returned by {@link ManifestBuilder.buildManifest}.
84
+ */
85
+ export interface BuiltManifest {
86
+ metadata: Record<string, any>;
87
+ components: Record<string, any>[];
88
+ components_to_delete?: ManifestComponentDeletion[];
89
+ }
90
+
47
91
  /**
48
92
  * Root container for a manifest — collects all components and serializes them to JSON.
49
93
  *
@@ -63,7 +107,7 @@ export interface ManifestBuilderProps {
63
107
  * description: 'Members, trainers, schedules, and check-in',
64
108
  * });
65
109
  *
66
- * const category = new Category(manifest, { apiName: 'gym__c', name: 'Gym' });
110
+ * const category = new Category(manifest, { apiName: 'gym__c', name: 'Gym', description: 'Gym domain' });
67
111
  * const memberObj = new CustomObject(manifest, { apiName: 'gym_member__c', name: 'Member', category });
68
112
  * // ... define fields, rules, list views, etc. ...
69
113
  *
@@ -75,6 +119,7 @@ export class ManifestBuilder implements ManifestScope {
75
119
  readonly name: string;
76
120
  readonly description: string;
77
121
  private readonly _components: ManifestComponent[] = [];
122
+ private readonly _componentsToDelete: ManifestComponentDeletion[] = [];
78
123
 
79
124
  /**
80
125
  * @param props - Initialization properties.
@@ -89,6 +134,24 @@ export class ManifestBuilder implements ManifestScope {
89
134
  this.description = props.description ?? '';
90
135
  }
91
136
 
137
+ /**
138
+ * Parses an existing manifest JSON blob, installs it as the active load context,
139
+ * hydrates every known component, and returns a normal `ManifestBuilder`.
140
+ */
141
+ static loadFromExistingJson(source: ExistingManifestSource): ManifestBuilder {
142
+ return ExistingManifestContext.fromJson(source).loadManifestBuilder();
143
+ }
144
+
145
+ /**
146
+ * Parses an existing manifest JSON blob and installs it as the active lazy-load context.
147
+ *
148
+ * After calling this, individual components can be loaded with
149
+ * `CustomObjectPageLayout.loadFromExisting(...)`, `Field.loadFromExisting(...)`, etc.
150
+ */
151
+ static useExistingJson(source: ExistingManifestSource): ExistingManifestContext {
152
+ return ExistingManifestContext.fromJson(source);
153
+ }
154
+
92
155
  /**
93
156
  * @internal Scope hook invoked by child constructors. Components enter the
94
157
  * manifest here — there is no other path.
@@ -101,11 +164,45 @@ export class ManifestBuilder implements ManifestScope {
101
164
  }
102
165
 
103
166
  /**
104
- * Returns the manifest as a plain object with `metadata` and a flat `components` array.
167
+ * Marks a component to be deleted when this manifest is installed.
105
168
  *
106
- * @returns `{ metadata: { key, name, description? }, components: Record<string, any>[] }`
169
+ * @param componentClass - Component class, e.g. `CustomObject`.
170
+ * @param identifier - Component identifier/key, such as an api_name or the component's identifier object.
171
+ * @returns This builder for chaining.
107
172
  */
108
- buildManifest(): { metadata: Record<string, any>; components: Record<string, any>[] } {
173
+ markComponentForDeletion<TIdentifierInput>(
174
+ componentClass: ManifestComponentClass<TIdentifierInput>,
175
+ identifier: TIdentifierInput,
176
+ ): this {
177
+ if (identifier == null || identifier === '') {
178
+ throw new Error('identifier is required');
179
+ }
180
+ if (!componentClass.componentType) {
181
+ throw new Error('type is required');
182
+ }
183
+ if (typeof componentClass.toDeletionIdentifier !== 'function') {
184
+ throw new TypeError('Component class must have a toDeletionIdentifier() method');
185
+ }
186
+
187
+ const deletion = componentClass.toDeletionIdentifier(identifier);
188
+ if (!deletion.type) {
189
+ throw new Error('type is required');
190
+ }
191
+ if (Object.keys(deletion).length <= 1) {
192
+ throw new Error('identifier is required');
193
+ }
194
+
195
+ this._componentsToDelete.push({ ...deletion });
196
+ return this;
197
+ }
198
+
199
+ /**
200
+ * Returns the manifest as a plain object with `metadata`, a flat `components` array,
201
+ * and a `components_to_delete` array when deletions are registered.
202
+ *
203
+ * @returns `{ metadata: { key, name, description? }, components, components_to_delete? }`
204
+ */
205
+ buildManifest(): BuiltManifest {
109
206
  const metadata: Record<string, any> = {
110
207
  key: this.key,
111
208
  name: this.name,
@@ -114,7 +211,11 @@ export class ManifestBuilder implements ManifestScope {
114
211
  metadata['description'] = this.description;
115
212
  }
116
213
  const components = this._components.map((c) => c.toDict());
117
- return { metadata, components };
214
+ const manifest: BuiltManifest = { metadata, components };
215
+ if (this._componentsToDelete.length > 0) {
216
+ manifest.components_to_delete = this.componentsToDeleteList();
217
+ }
218
+ return manifest;
118
219
  }
119
220
 
120
221
  /**
@@ -126,6 +227,15 @@ export class ManifestBuilder implements ManifestScope {
126
227
  return this._components.map((c) => c.toDict());
127
228
  }
128
229
 
230
+ /**
231
+ * Returns the flat list of component deletion references.
232
+ *
233
+ * @returns An array of component identifier objects, in registration order.
234
+ */
235
+ componentsToDeleteList(): ManifestComponentDeletion[] {
236
+ return this._componentsToDelete.map((component) => ({ ...component }));
237
+ }
238
+
129
239
  /**
130
240
  * Returns the full manifest as a pretty-printed JSON string.
131
241
  *
@@ -1,5 +1,6 @@
1
1
  import type { FunctionRuntime } from './_helpers';
2
- import type { ManifestScope } from './manifest-builder';
2
+ import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context';
3
+ import type { ManifestComponentDeletion, ManifestScope } from './manifest-builder';
3
4
 
4
5
  /**
5
6
  * Initialization properties for {@link ManifestFunction}.
@@ -68,8 +69,14 @@ export interface ManifestFunctionProps {
68
69
  * Defines a serverless function and registers it with the manifest.
69
70
  *
70
71
  * 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`.
72
+ * serverless infrastructure. The handler must export `onRipplingEvent`.
73
+ *
74
+ * Functions have two primary uses:
75
+ * 1. **Authoring SDUI for Custom Apps** — the function returns an SDUI spec
76
+ * (built with `@rippling/sdui-builder`) that renders as a page tab. Wrap the
77
+ * function in an {@link SduiPage} and reference it from an {@link App}.
78
+ * 2. **Workflow / event handlers** — functions invoked from rules, automations,
79
+ * or callbacks that do not render UI.
73
80
  *
74
81
  * This class is named `ManifestFunction` to avoid collision with the JavaScript
75
82
  * built-in `Function`. It is also re-exported as `Function` from the barrel for
@@ -94,6 +101,15 @@ export interface ManifestFunctionProps {
94
101
  * @see {@link SduiPage} — wraps a `ManifestFunction` as a navigable UI surface.
95
102
  */
96
103
  export class ManifestFunction {
104
+ static readonly componentType = 'FUNCTION' as const;
105
+
106
+ static toDeletionIdentifier(apiName: string): ManifestComponentDeletion {
107
+ return {
108
+ type: ManifestFunction.componentType,
109
+ api_name: apiName,
110
+ };
111
+ }
112
+
97
113
  private readonly _apiName: string;
98
114
  private readonly _name: string;
99
115
  private readonly _code: string;
@@ -122,6 +138,30 @@ export class ManifestFunction {
122
138
  scope._register(this);
123
139
  }
124
140
 
141
+ /**
142
+ * Loads this function from the active existing-manifest JSON context.
143
+ */
144
+ static loadFromExisting(apiName: string, options: ExistingManifestContextOptions = {}): ManifestFunction {
145
+ return getExistingManifestContext(options).loadFunction(apiName);
146
+ }
147
+
148
+ /** @internal Hydrates a function from existing manifest wire JSON. */
149
+ static _fromExistingComponent(scope: ManifestScope, component: Record<string, any>): ManifestFunction {
150
+ const props: ManifestFunctionProps = {
151
+ apiName: component['api_name'],
152
+ name: component['name'] ?? component['api_name'],
153
+ };
154
+ if (component['code_draft'] !== undefined) props.code = component['code_draft'];
155
+ if (component['description'] !== undefined) props.description = component['description'];
156
+ if (component['dependencies'] !== undefined) props.dependencies = component['dependencies'];
157
+ if (component['deployment_options'] !== undefined) {
158
+ props.deploymentOptions = component['deployment_options'];
159
+ }
160
+ if (component['allowed_domains'] !== undefined) props.allowedDomains = component['allowed_domains'];
161
+ if (component['is_async'] !== undefined) props.isAsync = component['is_async'];
162
+ return new ManifestFunction(scope, props);
163
+ }
164
+
125
165
  /**
126
166
  * Returns the api_name of this function (e.g. `'gym_today_schedule_fn'`).
127
167
  */
@@ -136,7 +176,7 @@ export class ManifestFunction {
136
176
  */
137
177
  toDict(): Record<string, any> {
138
178
  const component: Record<string, any> = {
139
- type: 'FUNCTION',
179
+ type: ManifestFunction.componentType,
140
180
  name: this._name,
141
181
  api_name: this._apiName,
142
182
  };