@rippling/rippling-sdk 0.2.0-alpha.38 → 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 (132) hide show
  1. package/lib/manifest/app.d.mts +11 -1
  2. package/lib/manifest/app.d.mts.map +1 -1
  3. package/lib/manifest/app.d.ts +11 -1
  4. package/lib/manifest/app.d.ts.map +1 -1
  5. package/lib/manifest/app.js +42 -1
  6. package/lib/manifest/app.js.map +1 -1
  7. package/lib/manifest/app.mjs +42 -1
  8. package/lib/manifest/app.mjs.map +1 -1
  9. package/lib/manifest/category.d.mts +10 -1
  10. package/lib/manifest/category.d.mts.map +1 -1
  11. package/lib/manifest/category.d.ts +10 -1
  12. package/lib/manifest/category.d.ts.map +1 -1
  13. package/lib/manifest/category.js +23 -1
  14. package/lib/manifest/category.js.map +1 -1
  15. package/lib/manifest/category.mjs +23 -1
  16. package/lib/manifest/category.mjs.map +1 -1
  17. package/lib/manifest/custom-object-field-section.d.mts +13 -0
  18. package/lib/manifest/custom-object-field-section.d.mts.map +1 -1
  19. package/lib/manifest/custom-object-field-section.d.ts +13 -0
  20. package/lib/manifest/custom-object-field-section.d.ts.map +1 -1
  21. package/lib/manifest/custom-object-field-section.js +22 -1
  22. package/lib/manifest/custom-object-field-section.js.map +1 -1
  23. package/lib/manifest/custom-object-field-section.mjs +22 -1
  24. package/lib/manifest/custom-object-field-section.mjs.map +1 -1
  25. package/lib/manifest/custom-object-page-layout.d.mts +21 -0
  26. package/lib/manifest/custom-object-page-layout.d.mts.map +1 -1
  27. package/lib/manifest/custom-object-page-layout.d.ts +21 -0
  28. package/lib/manifest/custom-object-page-layout.d.ts.map +1 -1
  29. package/lib/manifest/custom-object-page-layout.js +41 -1
  30. package/lib/manifest/custom-object-page-layout.js.map +1 -1
  31. package/lib/manifest/custom-object-page-layout.mjs +41 -1
  32. package/lib/manifest/custom-object-page-layout.mjs.map +1 -1
  33. package/lib/manifest/custom-object.d.mts +11 -1
  34. package/lib/manifest/custom-object.d.mts.map +1 -1
  35. package/lib/manifest/custom-object.d.ts +11 -1
  36. package/lib/manifest/custom-object.d.ts.map +1 -1
  37. package/lib/manifest/custom-object.js +55 -2
  38. package/lib/manifest/custom-object.js.map +1 -1
  39. package/lib/manifest/custom-object.mjs +55 -2
  40. package/lib/manifest/custom-object.mjs.map +1 -1
  41. package/lib/manifest/existing-manifest-context.d.mts +87 -0
  42. package/lib/manifest/existing-manifest-context.d.mts.map +1 -0
  43. package/lib/manifest/existing-manifest-context.d.ts +87 -0
  44. package/lib/manifest/existing-manifest-context.d.ts.map +1 -0
  45. package/lib/manifest/existing-manifest-context.js +511 -0
  46. package/lib/manifest/existing-manifest-context.js.map +1 -0
  47. package/lib/manifest/existing-manifest-context.mjs +506 -0
  48. package/lib/manifest/existing-manifest-context.mjs.map +1 -0
  49. package/lib/manifest/field.d.mts +24 -0
  50. package/lib/manifest/field.d.mts.map +1 -1
  51. package/lib/manifest/field.d.ts +24 -0
  52. package/lib/manifest/field.d.ts.map +1 -1
  53. package/lib/manifest/field.js +114 -1
  54. package/lib/manifest/field.js.map +1 -1
  55. package/lib/manifest/field.mjs +114 -1
  56. package/lib/manifest/field.mjs.map +1 -1
  57. package/lib/manifest/index.d.mts +8 -7
  58. package/lib/manifest/index.d.mts.map +1 -1
  59. package/lib/manifest/index.d.ts +8 -7
  60. package/lib/manifest/index.d.ts.map +1 -1
  61. package/lib/manifest/index.js +3 -1
  62. package/lib/manifest/index.js.map +1 -1
  63. package/lib/manifest/index.mjs +5 -4
  64. package/lib/manifest/index.mjs.map +1 -1
  65. package/lib/manifest/list-view.d.mts +17 -0
  66. package/lib/manifest/list-view.d.mts.map +1 -1
  67. package/lib/manifest/list-view.d.ts +17 -0
  68. package/lib/manifest/list-view.d.ts.map +1 -1
  69. package/lib/manifest/list-view.js +38 -1
  70. package/lib/manifest/list-view.js.map +1 -1
  71. package/lib/manifest/list-view.mjs +38 -1
  72. package/lib/manifest/list-view.mjs.map +1 -1
  73. package/lib/manifest/manifest-builder.d.mts +59 -6
  74. package/lib/manifest/manifest-builder.d.mts.map +1 -1
  75. package/lib/manifest/manifest-builder.d.ts +59 -6
  76. package/lib/manifest/manifest-builder.d.ts.map +1 -1
  77. package/lib/manifest/manifest-builder.js +61 -3
  78. package/lib/manifest/manifest-builder.js.map +1 -1
  79. package/lib/manifest/manifest-builder.mjs +61 -3
  80. package/lib/manifest/manifest-builder.mjs.map +1 -1
  81. package/lib/manifest/manifest-function.d.mts +10 -1
  82. package/lib/manifest/manifest-function.d.mts.map +1 -1
  83. package/lib/manifest/manifest-function.d.ts +10 -1
  84. package/lib/manifest/manifest-function.d.ts.map +1 -1
  85. package/lib/manifest/manifest-function.js +36 -1
  86. package/lib/manifest/manifest-function.js.map +1 -1
  87. package/lib/manifest/manifest-function.mjs +36 -1
  88. package/lib/manifest/manifest-function.mjs.map +1 -1
  89. package/lib/manifest/rule.d.mts +17 -0
  90. package/lib/manifest/rule.d.mts.map +1 -1
  91. package/lib/manifest/rule.d.ts +17 -0
  92. package/lib/manifest/rule.d.ts.map +1 -1
  93. package/lib/manifest/rule.js +45 -1
  94. package/lib/manifest/rule.js.map +1 -1
  95. package/lib/manifest/rule.mjs +45 -1
  96. package/lib/manifest/rule.mjs.map +1 -1
  97. package/lib/manifest/sdui-page.d.mts +10 -1
  98. package/lib/manifest/sdui-page.d.mts.map +1 -1
  99. package/lib/manifest/sdui-page.d.ts +10 -1
  100. package/lib/manifest/sdui-page.d.ts.map +1 -1
  101. package/lib/manifest/sdui-page.js +28 -1
  102. package/lib/manifest/sdui-page.js.map +1 -1
  103. package/lib/manifest/sdui-page.mjs +28 -1
  104. package/lib/manifest/sdui-page.mjs.map +1 -1
  105. package/lib/manifest/validation.d.mts +17 -0
  106. package/lib/manifest/validation.d.mts.map +1 -1
  107. package/lib/manifest/validation.d.ts +17 -0
  108. package/lib/manifest/validation.d.ts.map +1 -1
  109. package/lib/manifest/validation.js +31 -1
  110. package/lib/manifest/validation.js.map +1 -1
  111. package/lib/manifest/validation.mjs +31 -1
  112. package/lib/manifest/validation.mjs.map +1 -1
  113. package/package.json +1 -1
  114. package/src/lib/manifest/app.ts +46 -2
  115. package/src/lib/manifest/category.ts +28 -2
  116. package/src/lib/manifest/custom-object-field-section.ts +37 -1
  117. package/src/lib/manifest/custom-object-page-layout.ts +61 -1
  118. package/src/lib/manifest/custom-object.ts +58 -3
  119. package/src/lib/manifest/existing-manifest-context.ts +673 -0
  120. package/src/lib/manifest/field.ts +140 -1
  121. package/src/lib/manifest/index.ts +39 -4
  122. package/src/lib/manifest/list-view.ts +50 -1
  123. package/src/lib/manifest/manifest-builder.ts +114 -4
  124. package/src/lib/manifest/manifest-function.ts +36 -2
  125. package/src/lib/manifest/rule.ts +60 -1
  126. package/src/lib/manifest/sdui-page.ts +37 -2
  127. package/src/lib/manifest/validation.ts +48 -1
  128. package/src/version.ts +1 -1
  129. package/version.d.mts +1 -1
  130. package/version.d.ts +1 -1
  131. package/version.js +1 -1
  132. 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,
@@ -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
  *
@@ -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}.
@@ -100,6 +101,15 @@ export interface ManifestFunctionProps {
100
101
  * @see {@link SduiPage} — wraps a `ManifestFunction` as a navigable UI surface.
101
102
  */
102
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
+
103
113
  private readonly _apiName: string;
104
114
  private readonly _name: string;
105
115
  private readonly _code: string;
@@ -128,6 +138,30 @@ export class ManifestFunction {
128
138
  scope._register(this);
129
139
  }
130
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
+
131
165
  /**
132
166
  * Returns the api_name of this function (e.g. `'gym_today_schedule_fn'`).
133
167
  */
@@ -142,7 +176,7 @@ export class ManifestFunction {
142
176
  */
143
177
  toDict(): Record<string, any> {
144
178
  const component: Record<string, any> = {
145
- type: 'FUNCTION',
179
+ type: ManifestFunction.componentType,
146
180
  name: this._name,
147
181
  api_name: this._apiName,
148
182
  };
@@ -1,6 +1,13 @@
1
1
  import { generateId, type TriggerType, type UpdateTriggerType } 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 RuleDeletionIdentifier {
8
+ flowId: string;
9
+ modelRqlName: string;
10
+ }
4
11
 
5
12
  /**
6
13
  * A single action performed when a {@link Rule} fires.
@@ -79,6 +86,10 @@ export interface RuleProps {
79
86
  actions?: RuleAction[];
80
87
  }
81
88
 
89
+ export interface RuleLoadFromExistingOptions extends ExistingManifestContextOptions {
90
+ customObjectApiName?: string;
91
+ }
92
+
82
93
  /** SELECT / RADIO rule actions use list `fixed_value` (Rippling rule builder). */
83
94
  function fixedValueToWire(targetField: Field, value: any): any {
84
95
  const ft = targetField.getFieldType();
@@ -103,6 +114,10 @@ function actionToDict(action: RuleAction): Record<string, any> {
103
114
  return out;
104
115
  }
105
116
 
117
+ function hasOwn(value: Record<string, any>, key: string): boolean {
118
+ return Object.prototype.hasOwnProperty.call(value, key);
119
+ }
120
+
106
121
  /**
107
122
  * Defines a record-triggered automation rule and registers it with the manifest.
108
123
  *
@@ -122,6 +137,16 @@ function actionToDict(action: RuleAction): Record<string, any> {
122
137
  * ```
123
138
  */
124
139
  export class Rule {
140
+ static readonly componentType = 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW' as const;
141
+
142
+ static toDeletionIdentifier(identifier: RuleDeletionIdentifier): ManifestComponentDeletion {
143
+ return {
144
+ type: Rule.componentType,
145
+ flow_id: identifier.flowId,
146
+ model_rql_name: identifier.modelRqlName,
147
+ };
148
+ }
149
+
125
150
  private readonly _flowId: string;
126
151
  private readonly _customObjectApiName: string;
127
152
  private readonly _flowName: string;
@@ -154,6 +179,40 @@ export class Rule {
154
179
  customObject._register(this);
155
180
  }
156
181
 
182
+ /**
183
+ * Loads this record-triggered rule from the active existing-manifest JSON context.
184
+ */
185
+ static loadFromExisting(flowId: string, options: RuleLoadFromExistingOptions = {}): Rule {
186
+ return getExistingManifestContext(options).loadRule(flowId, options);
187
+ }
188
+
189
+ /** @internal Hydrates a record-triggered rule from existing manifest wire JSON. */
190
+ static _fromExistingComponent(
191
+ customObject: CustomObject,
192
+ component: Record<string, any>,
193
+ resolveField: (apiName: string) => Field,
194
+ ): Rule {
195
+ const props: RuleProps = {
196
+ flowId: component['flow_id'],
197
+ flowName: component['flow_name'] ?? component['flow_id'],
198
+ triggerType: component['trigger_type'],
199
+ actions: (component['actions'] ?? []).map((action: Record<string, any>): RuleAction => {
200
+ const out: RuleAction = {
201
+ targetField: resolveField(action['target_field']),
202
+ };
203
+ if (hasOwn(action, 'fixed_value')) out.fixedValue = action['fixed_value'];
204
+ if (hasOwn(action, 'rql_formula')) out.rqlFormula = action['rql_formula'];
205
+ return out;
206
+ }),
207
+ };
208
+ if (component['description'] !== undefined) props.description = component['description'];
209
+ if (component['rql_condition'] !== undefined) props.rqlCondition = component['rql_condition'];
210
+ if (component['update_trigger_type'] !== undefined) {
211
+ props.updateTriggerType = component['update_trigger_type'];
212
+ }
213
+ return new Rule(customObject, props);
214
+ }
215
+
157
216
  /**
158
217
  * Returns the stable identifier for this rule (e.g. `'gym_default_enrollment_status'`).
159
218
  */
@@ -176,7 +235,7 @@ export class Rule {
176
235
  */
177
236
  toDict(): Record<string, any> {
178
237
  const component: Record<string, any> = {
179
- type: 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW',
238
+ type: Rule.componentType,
180
239
  model_rql_name: this._customObjectApiName,
181
240
  flow_id: this._flowId,
182
241
  flow_name: this._flowName,