@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
@@ -0,0 +1,673 @@
1
+ import { App } from './app';
2
+ import { Category } from './category';
3
+ import { CustomObject } from './custom-object';
4
+ import { CustomObjectFieldSection } from './custom-object-field-section';
5
+ import { CustomObjectPageLayout } from './custom-object-page-layout';
6
+ import { Field } from './field';
7
+ import { ListViewDef } from './list-view';
8
+ import { ManifestBuilder, type ManifestComponent } from './manifest-builder';
9
+ import { ManifestFunction } from './manifest-function';
10
+ import { Rule } from './rule';
11
+ import { SduiPage } from './sdui-page';
12
+ import { Validation } from './validation';
13
+
14
+ export interface ExistingManifest {
15
+ metadata?: Record<string, any>;
16
+ components: Record<string, any>[];
17
+ }
18
+
19
+ export type ExistingManifestSource = string | ExistingManifest | Record<string, any>[];
20
+
21
+ export interface ExistingManifestContextOptions {
22
+ context?: ExistingManifestContext;
23
+ }
24
+
25
+ interface ExistingManifestObjectOptions {
26
+ customObjectApiName?: string;
27
+ }
28
+
29
+ class ExistingRawComponent implements ManifestComponent {
30
+ constructor(private readonly component: Record<string, any>) {}
31
+
32
+ toDict(): Record<string, any> {
33
+ return cloneJson(this.component);
34
+ }
35
+ }
36
+
37
+ function cloneJson<T>(value: T): T {
38
+ if (value == null) return value;
39
+ return JSON.parse(JSON.stringify(value)) as T;
40
+ }
41
+
42
+ function parseExistingManifest(source: ExistingManifestSource): ExistingManifest {
43
+ const parsed = typeof source === 'string' ? JSON.parse(source) : source;
44
+ if (Array.isArray(parsed)) {
45
+ return { components: parsed };
46
+ }
47
+ if (parsed == null || typeof parsed !== 'object') {
48
+ throw new TypeError('Existing manifest JSON must be an object with a components array.');
49
+ }
50
+ const components = (parsed as ExistingManifest).components;
51
+ if (!Array.isArray(components)) {
52
+ throw new TypeError('Existing manifest JSON must include a components array.');
53
+ }
54
+ const manifest: ExistingManifest = { components };
55
+ const metadata = (parsed as ExistingManifest).metadata;
56
+ if (metadata !== undefined) manifest.metadata = metadata;
57
+ return manifest;
58
+ }
59
+
60
+ function splitQualifiedId(value: string | undefined): { parentId?: string; childId?: string } {
61
+ if (value == null) return {};
62
+ const separatorIndex = value.indexOf('.');
63
+ if (separatorIndex < 0) return { childId: value };
64
+ return {
65
+ parentId: value.slice(0, separatorIndex),
66
+ childId: value.slice(separatorIndex + 1),
67
+ };
68
+ }
69
+
70
+ function componentLabel(component: Record<string, any>): string {
71
+ const type = component['type'] ?? 'UNKNOWN';
72
+ const id =
73
+ component['api_name'] ??
74
+ component['layout_id'] ??
75
+ component['view_id'] ??
76
+ component['rule_id'] ??
77
+ component['flow_id'] ??
78
+ component['sdui_page_id'] ??
79
+ component['field_api_name'] ??
80
+ component['section_id'] ??
81
+ 'unknown';
82
+ return `${type}:${id}`;
83
+ }
84
+
85
+ export function getExistingManifestContext(
86
+ options?: ExistingManifestContextOptions,
87
+ ): ExistingManifestContext {
88
+ if (options?.context != null) {
89
+ return options.context;
90
+ }
91
+ return ExistingManifestContext.active();
92
+ }
93
+
94
+ export class ExistingManifestContext {
95
+ private static _active: ExistingManifestContext | undefined;
96
+
97
+ private readonly manifest: ExistingManifest;
98
+ private readonly components: Record<string, any>[];
99
+ private readonly componentsByType = new Map<string, Record<string, any>[]>();
100
+ private readonly componentIndexes = new WeakMap<Record<string, any>, number>();
101
+ private readonly cache = new Map<string, unknown>();
102
+ private readonly loading = new Set<string>();
103
+ private builder: ManifestBuilder | undefined;
104
+
105
+ constructor(source: ExistingManifestSource) {
106
+ this.manifest = parseExistingManifest(source);
107
+ this.components = this.manifest.components;
108
+ this.components.forEach((component, index) => {
109
+ this.componentIndexes.set(component, index);
110
+ const type = component['type'];
111
+ if (typeof type !== 'string') return;
112
+ const bucket = this.componentsByType.get(type) ?? [];
113
+ bucket.push(component);
114
+ this.componentsByType.set(type, bucket);
115
+ });
116
+ }
117
+
118
+ static fromJson(source: ExistingManifestSource): ExistingManifestContext {
119
+ const context = new ExistingManifestContext(source);
120
+ ExistingManifestContext._active = context;
121
+ return context;
122
+ }
123
+
124
+ static active(): ExistingManifestContext {
125
+ if (ExistingManifestContext._active == null) {
126
+ throw new Error(
127
+ 'No existing manifest JSON has been loaded. Call ManifestBuilder.useExistingJson(json) first.',
128
+ );
129
+ }
130
+ return ExistingManifestContext._active;
131
+ }
132
+
133
+ static clearActive(): void {
134
+ ExistingManifestContext._active = undefined;
135
+ }
136
+
137
+ getManifestBuilder(): ManifestBuilder {
138
+ if (this.builder == null) {
139
+ const metadata = this.manifest.metadata ?? {};
140
+ const key = metadata['key'];
141
+ if (typeof key !== 'string' || key.length === 0) {
142
+ throw new Error('Existing manifest metadata.key is required to create a ManifestBuilder.');
143
+ }
144
+ const props = {
145
+ key,
146
+ name: typeof metadata['name'] === 'string' ? metadata['name'] : key,
147
+ };
148
+ if (typeof metadata['description'] === 'string') {
149
+ (props as Record<string, any>)['description'] = metadata['description'];
150
+ }
151
+ this.builder = new ManifestBuilder(props);
152
+ }
153
+ return this.builder;
154
+ }
155
+
156
+ loadManifestBuilder(): ManifestBuilder {
157
+ const builder = this.getManifestBuilder();
158
+ this.loadAll();
159
+ return builder;
160
+ }
161
+
162
+ loadAll(): void {
163
+ this.getManifestBuilder();
164
+ for (const component of this.components) {
165
+ this.loadComponent(component);
166
+ }
167
+ }
168
+
169
+ loadCategory(apiName: string): Category {
170
+ const component = this.findUnique(
171
+ 'CUSTOM_CATEGORY',
172
+ (candidate) => candidate['api_name'] === apiName,
173
+ `CUSTOM_CATEGORY ${apiName}`,
174
+ );
175
+ return this.loadCategoryComponent(component);
176
+ }
177
+
178
+ loadCustomObject(apiName: string): CustomObject {
179
+ const component = this.findUnique(
180
+ 'CUSTOM_OBJECT',
181
+ (candidate) => candidate['api_name'] === apiName,
182
+ `CUSTOM_OBJECT ${apiName}`,
183
+ );
184
+ return this.loadCustomObjectComponent(component);
185
+ }
186
+
187
+ loadFieldSection(
188
+ sectionIdOrQualifiedId: string,
189
+ options: ExistingManifestObjectOptions = {},
190
+ ): CustomObjectFieldSection {
191
+ const qualified = splitQualifiedId(sectionIdOrQualifiedId);
192
+ const sectionId = qualified.childId ?? sectionIdOrQualifiedId;
193
+ const modelApiName = options.customObjectApiName ?? qualified.parentId;
194
+ const component = this.findUnique(
195
+ 'CUSTOM_OBJECT_FIELD_SECTION',
196
+ (candidate) =>
197
+ candidate['section_id'] === sectionId &&
198
+ (modelApiName == null || candidate['model_name'] === modelApiName),
199
+ `CUSTOM_OBJECT_FIELD_SECTION ${modelApiName != null ? `${modelApiName}.` : ''}${sectionId}`,
200
+ );
201
+ return this.loadFieldSectionComponent(component);
202
+ }
203
+
204
+ loadField(fieldApiNameOrQualifiedId: string, options: ExistingManifestObjectOptions = {}): Field {
205
+ const qualified = splitQualifiedId(fieldApiNameOrQualifiedId);
206
+ const fieldApiName = qualified.childId ?? fieldApiNameOrQualifiedId;
207
+ const customObjectApiName = options.customObjectApiName ?? qualified.parentId;
208
+ return this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName);
209
+ }
210
+
211
+ loadListView(viewId: string, options: ExistingManifestObjectOptions = {}): ListViewDef {
212
+ const objectApiName = options.customObjectApiName;
213
+ const component = this.findUnique(
214
+ 'CUSTOM_OBJECT_LIST_VIEW',
215
+ (candidate) =>
216
+ candidate['view_id'] === viewId &&
217
+ (objectApiName == null || candidate['object_rql_name'] === objectApiName),
218
+ `CUSTOM_OBJECT_LIST_VIEW ${objectApiName != null ? `${objectApiName}.` : ''}${viewId}`,
219
+ );
220
+ return this.loadListViewComponent(component);
221
+ }
222
+
223
+ loadPageLayout(
224
+ layoutIdOrApiName: string,
225
+ options: ExistingManifestObjectOptions = {},
226
+ ): CustomObjectPageLayout {
227
+ const objectApiName = options.customObjectApiName;
228
+ const component = this.findUnique(
229
+ 'CUSTOM_OBJECT_PAGE_LAYOUT',
230
+ (candidate) =>
231
+ (candidate['layout_id'] === layoutIdOrApiName || candidate['api_name'] === layoutIdOrApiName) &&
232
+ (objectApiName == null || candidate['object_rql_name'] === objectApiName),
233
+ `CUSTOM_OBJECT_PAGE_LAYOUT ${objectApiName != null ? `${objectApiName}.` : ''}${layoutIdOrApiName}`,
234
+ );
235
+ return this.loadPageLayoutComponent(component);
236
+ }
237
+
238
+ loadValidation(ruleId: string, options: ExistingManifestObjectOptions = {}): Validation {
239
+ const objectApiName = options.customObjectApiName;
240
+ const component = this.findUnique(
241
+ 'CUSTOM_OBJECT_VALIDATION_RULE',
242
+ (candidate) =>
243
+ candidate['rule_id'] === ruleId &&
244
+ (objectApiName == null || candidate['model_rql_name'] === objectApiName),
245
+ `CUSTOM_OBJECT_VALIDATION_RULE ${objectApiName != null ? `${objectApiName}.` : ''}${ruleId}`,
246
+ );
247
+ return this.loadValidationComponent(component);
248
+ }
249
+
250
+ loadRule(flowId: string, options: ExistingManifestObjectOptions = {}): Rule {
251
+ const objectApiName = options.customObjectApiName;
252
+ const component = this.findUnique(
253
+ 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW',
254
+ (candidate) =>
255
+ candidate['flow_id'] === flowId &&
256
+ (objectApiName == null || candidate['model_rql_name'] === objectApiName),
257
+ `CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW ${objectApiName != null ? `${objectApiName}.` : ''}${flowId}`,
258
+ );
259
+ return this.loadRuleComponent(component);
260
+ }
261
+
262
+ loadFunction(apiName: string): ManifestFunction {
263
+ const component = this.findUnique(
264
+ 'FUNCTION',
265
+ (candidate) => candidate['api_name'] === apiName,
266
+ `FUNCTION ${apiName}`,
267
+ );
268
+ return this.loadFunctionComponent(component);
269
+ }
270
+
271
+ loadSduiPage(sduiPageId: string): SduiPage {
272
+ const component = this.findUnique(
273
+ 'SDUI_PAGE',
274
+ (candidate) => candidate['sdui_page_id'] === sduiPageId,
275
+ `SDUI_PAGE ${sduiPageId}`,
276
+ );
277
+ return this.loadSduiPageComponent(component);
278
+ }
279
+
280
+ loadApp(apiName: string): App {
281
+ const component = this.findUnique(
282
+ 'CUSTOM_APP',
283
+ (candidate) => candidate['api_name'] === apiName,
284
+ `CUSTOM_APP ${apiName}`,
285
+ );
286
+ return this.loadAppComponent(component);
287
+ }
288
+
289
+ private loadComponent(component: Record<string, any>): unknown {
290
+ switch (component['type']) {
291
+ case 'CUSTOM_CATEGORY':
292
+ return this.loadCategoryComponent(component);
293
+ case 'CUSTOM_OBJECT':
294
+ return this.loadCustomObjectComponent(component);
295
+ case 'CUSTOM_OBJECT_FIELD_SECTION':
296
+ return this.loadFieldSectionComponent(component);
297
+ case 'CUSTOM_OBJECT_FIELD':
298
+ return this.loadFieldComponent(component);
299
+ case 'CUSTOM_OBJECT_LIST_VIEW':
300
+ return this.loadListViewComponent(component);
301
+ case 'CUSTOM_OBJECT_PAGE_LAYOUT':
302
+ return this.loadPageLayoutComponent(component);
303
+ case 'CUSTOM_OBJECT_VALIDATION_RULE':
304
+ return this.loadValidationComponent(component);
305
+ case 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW':
306
+ return this.loadRuleComponent(component);
307
+ case 'FUNCTION':
308
+ return this.loadFunctionComponent(component);
309
+ case 'SDUI_PAGE':
310
+ return this.loadSduiPageComponent(component);
311
+ case 'CUSTOM_APP':
312
+ return this.loadAppComponent(component);
313
+ default:
314
+ return this.loadRawComponent(component);
315
+ }
316
+ }
317
+
318
+ private loadCategoryComponent(component: Record<string, any>): Category {
319
+ const cacheKey = this.cacheKey('CUSTOM_CATEGORY', component['api_name']);
320
+ return this.getOrLoad(cacheKey, () =>
321
+ Category._fromExistingComponent(this.getManifestBuilder(), component),
322
+ );
323
+ }
324
+
325
+ private loadCustomObjectComponent(component: Record<string, any>): CustomObject {
326
+ const cacheKey = this.cacheKey('CUSTOM_OBJECT', component['api_name']);
327
+ return this.getOrLoad(cacheKey, () => {
328
+ return CustomObject._fromExistingComponent(this.getManifestBuilder(), component, (categoryApiName) =>
329
+ this.hasComponent('CUSTOM_CATEGORY', (c) => c['api_name'] === categoryApiName) ?
330
+ this.loadCategory(categoryApiName)
331
+ : categoryApiName,
332
+ );
333
+ });
334
+ }
335
+
336
+ private loadFieldSectionComponent(component: Record<string, any>): CustomObjectFieldSection {
337
+ const cacheKey = this.cacheKey(
338
+ 'CUSTOM_OBJECT_FIELD_SECTION',
339
+ `${component['model_name']}.${component['section_id']}`,
340
+ );
341
+ return this.getOrLoad(cacheKey, () => {
342
+ const customObject = this.loadCustomObject(component['model_name']);
343
+ return CustomObjectFieldSection._fromExistingComponent(customObject, component);
344
+ });
345
+ }
346
+
347
+ private loadFieldComponent(component: Record<string, any>): Field {
348
+ const customObjectApiName = component['custom_object_api_name'];
349
+ const fieldApiName = component['field_api_name'];
350
+ if (component['is_standard'] === true) {
351
+ this.loadCustomObject(customObjectApiName);
352
+ return this.loadFieldReferenceComponent(component);
353
+ }
354
+
355
+ const cacheKey = this.cacheKey('CUSTOM_OBJECT_FIELD', `${customObjectApiName}.${fieldApiName}`);
356
+ return this.getOrLoad(cacheKey, () => {
357
+ const customObject = this.loadCustomObject(customObjectApiName);
358
+ const sectionId = component['section'];
359
+ if (typeof sectionId === 'string') {
360
+ this.loadFieldSectionIfPresent(customObjectApiName, sectionId);
361
+ }
362
+ this.loadFieldRelationshipDependencies(component);
363
+ return Field._fromExistingComponent(customObject, component);
364
+ });
365
+ }
366
+
367
+ private loadListViewComponent(component: Record<string, any>): ListViewDef {
368
+ const cacheKey = this.cacheKey(
369
+ 'CUSTOM_OBJECT_LIST_VIEW',
370
+ `${component['object_rql_name']}.${component['view_id']}`,
371
+ );
372
+ return this.getOrLoad(cacheKey, () => {
373
+ const customObjectApiName = component['object_rql_name'];
374
+ const customObject = this.loadCustomObject(customObjectApiName);
375
+ return ListViewDef._fromExistingComponent(customObject, component, (fieldApiName) =>
376
+ this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName),
377
+ );
378
+ });
379
+ }
380
+
381
+ private loadPageLayoutComponent(component: Record<string, any>): CustomObjectPageLayout {
382
+ const cacheKey = this.cacheKey(
383
+ 'CUSTOM_OBJECT_PAGE_LAYOUT',
384
+ component['layout_id'] ?? `${component['object_rql_name']}.${component['api_name']}`,
385
+ );
386
+ return this.getOrLoad(cacheKey, () => {
387
+ const customObjectApiName = component['object_rql_name'];
388
+ const customObject = this.loadCustomObject(customObjectApiName);
389
+ this.loadPageLayoutDependencies(customObjectApiName, component);
390
+ return CustomObjectPageLayout._fromExistingComponent(customObject, component);
391
+ });
392
+ }
393
+
394
+ private loadValidationComponent(component: Record<string, any>): Validation {
395
+ const cacheKey = this.cacheKey(
396
+ 'CUSTOM_OBJECT_VALIDATION_RULE',
397
+ `${component['model_rql_name']}.${component['rule_id']}`,
398
+ );
399
+ return this.getOrLoad(cacheKey, () => {
400
+ const customObjectApiName = component['model_rql_name'];
401
+ const customObject = this.loadCustomObject(customObjectApiName);
402
+ return Validation._fromExistingComponent(customObject, component, (fieldApiName) =>
403
+ this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName),
404
+ );
405
+ });
406
+ }
407
+
408
+ private loadRuleComponent(component: Record<string, any>): Rule {
409
+ const cacheKey = this.cacheKey(
410
+ 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW',
411
+ `${component['model_rql_name']}.${component['flow_id']}`,
412
+ );
413
+ return this.getOrLoad(cacheKey, () => {
414
+ const customObjectApiName = component['model_rql_name'];
415
+ const customObject = this.loadCustomObject(customObjectApiName);
416
+ return Rule._fromExistingComponent(customObject, component, (fieldApiName) =>
417
+ this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName),
418
+ );
419
+ });
420
+ }
421
+
422
+ private loadFunctionComponent(component: Record<string, any>): ManifestFunction {
423
+ const cacheKey = this.cacheKey('FUNCTION', component['api_name']);
424
+ return this.getOrLoad(cacheKey, () =>
425
+ ManifestFunction._fromExistingComponent(this.getManifestBuilder(), component),
426
+ );
427
+ }
428
+
429
+ private loadSduiPageComponent(component: Record<string, any>): SduiPage {
430
+ const cacheKey = this.cacheKey('SDUI_PAGE', component['sdui_page_id']);
431
+ return this.getOrLoad(cacheKey, () => {
432
+ return SduiPage._fromExistingComponent(this.getManifestBuilder(), component, (functionApiName) =>
433
+ this.hasComponent('FUNCTION', (c) => c['api_name'] === functionApiName) ?
434
+ this.loadFunction(functionApiName)
435
+ : functionApiName,
436
+ );
437
+ });
438
+ }
439
+
440
+ private loadAppComponent(component: Record<string, any>): App {
441
+ const cacheKey = this.cacheKey('CUSTOM_APP', component['api_name']);
442
+ return this.getOrLoad(cacheKey, () => {
443
+ this.loadAppDependencies(component);
444
+ return App._fromExistingComponent(this.getManifestBuilder(), component);
445
+ });
446
+ }
447
+
448
+ private loadRawComponent(component: Record<string, any>): ExistingRawComponent {
449
+ const index = this.componentIndexes.get(component) ?? -1;
450
+ const cacheKey = this.cacheKey('RAW', `${index}:${componentLabel(component)}`);
451
+ return this.getOrLoad(cacheKey, () => {
452
+ const raw = new ExistingRawComponent(component);
453
+ this.getManifestBuilder()._register(raw);
454
+ return raw;
455
+ });
456
+ }
457
+
458
+ private loadFieldByObjectAndApi(customObjectApiName: string | undefined, fieldApiName: string): Field {
459
+ const component = this.findFieldComponent(customObjectApiName, fieldApiName);
460
+ if (component != null) {
461
+ return this.loadFieldComponent(component);
462
+ }
463
+ if (customObjectApiName != null) {
464
+ this.loadCustomObject(customObjectApiName);
465
+ }
466
+ if (fieldApiName.endsWith('__c')) {
467
+ const prefix = customObjectApiName != null ? `${customObjectApiName}.` : '';
468
+ throw new Error(`Could not find CUSTOM_OBJECT_FIELD ${prefix}${fieldApiName}.`);
469
+ }
470
+ return this.loadSyntheticFieldReference(customObjectApiName ?? '', fieldApiName);
471
+ }
472
+
473
+ private findFieldComponent(
474
+ customObjectApiName: string | undefined,
475
+ fieldApiName: string,
476
+ ): Record<string, any> | undefined {
477
+ const matches = this.ofType('CUSTOM_OBJECT_FIELD').filter(
478
+ (candidate) =>
479
+ candidate['field_api_name'] === fieldApiName &&
480
+ (customObjectApiName == null || candidate['custom_object_api_name'] === customObjectApiName),
481
+ );
482
+ if (matches.length > 1) {
483
+ throw new Error(
484
+ `Ambiguous CUSTOM_OBJECT_FIELD ${fieldApiName}; pass customObjectApiName to choose one.`,
485
+ );
486
+ }
487
+ return matches[0] as Record<string, any>;
488
+ }
489
+
490
+ private loadFieldReferenceComponent(component: Record<string, any>): Field {
491
+ const cacheKey = this.cacheKey(
492
+ 'CUSTOM_OBJECT_FIELD_REF',
493
+ `${component['custom_object_api_name']}.${component['field_api_name']}`,
494
+ );
495
+ return this.getOrLoad(cacheKey, () => Field._referenceFromExistingComponent(component));
496
+ }
497
+
498
+ private loadSyntheticFieldReference(customObjectApiName: string, fieldApiName: string): Field {
499
+ const cacheKey = this.cacheKey('CUSTOM_OBJECT_FIELD_REF', `${customObjectApiName}.${fieldApiName}`);
500
+ return this.getOrLoad(cacheKey, () => Field._syntheticReference(customObjectApiName, fieldApiName));
501
+ }
502
+
503
+ private loadFieldRelationshipDependencies(component: Record<string, any>): void {
504
+ const dataType = component['data_type'] ?? {};
505
+ const targetApiName = dataType['og_model_rql_name'];
506
+ if (
507
+ typeof targetApiName === 'string' &&
508
+ this.hasComponent('CUSTOM_OBJECT', (c) => c['api_name'] === targetApiName)
509
+ ) {
510
+ this.loadCustomObject(targetApiName);
511
+ }
512
+
513
+ const summary = component['derived_aggregated_field'];
514
+ if (summary == null || typeof summary !== 'object') return;
515
+ const childModel = summary['child_model_name'];
516
+ if (
517
+ typeof childModel === 'string' &&
518
+ this.hasComponent('CUSTOM_OBJECT', (c) => c['api_name'] === childModel)
519
+ ) {
520
+ this.loadCustomObject(childModel);
521
+ this.loadSummaryFieldDependency(childModel, summary['field_to_aggregate']);
522
+ this.loadSummaryFieldDependency(childModel, summary['lookup_field_api_name']);
523
+ for (const filter of summary['filters'] ?? []) {
524
+ this.loadSummaryFieldDependency(childModel, filter['field_name']);
525
+ }
526
+ }
527
+ }
528
+
529
+ private loadSummaryFieldDependency(childModel: string, fieldApiName: unknown): void {
530
+ if (typeof fieldApiName === 'string') {
531
+ this.loadFieldByObjectAndApi(childModel, fieldApiName);
532
+ }
533
+ }
534
+
535
+ private loadPageLayoutDependencies(customObjectApiName: string, component: Record<string, any>): void {
536
+ this.loadHeaderDependencies(customObjectApiName, component['header_edits']);
537
+ this.loadTabDependencies(customObjectApiName, component['tab_edits']);
538
+ }
539
+
540
+ private loadHeaderDependencies(customObjectApiName: string, headerEdits: unknown): void {
541
+ if (headerEdits == null || typeof headerEdits !== 'object') return;
542
+ const edits = headerEdits as Record<string, any>;
543
+ this.loadFieldDependencyIfString(customObjectApiName, edits['newTitleField']);
544
+ this.loadFieldDependencyIfString(customObjectApiName, edits['newDescriptionField']);
545
+ for (const field of edits['newFields'] ?? []) {
546
+ this.loadFieldDependencyIfString(customObjectApiName, field['rqlName']);
547
+ }
548
+ for (const systemEdit of Object.values(edits['systemFieldEdits'] ?? {})) {
549
+ if (systemEdit != null && typeof systemEdit === 'object') {
550
+ this.loadFieldDependencyIfString(
551
+ customObjectApiName,
552
+ (systemEdit as Record<string, any>)['newRqlField'],
553
+ );
554
+ }
555
+ }
556
+ }
557
+
558
+ private loadTabDependencies(customObjectApiName: string, tabEdits: unknown): void {
559
+ if (tabEdits == null || typeof tabEdits !== 'object') return;
560
+ const edits = tabEdits as Record<string, any>;
561
+ for (const tab of edits['newTabs'] ?? []) {
562
+ this.loadTabEntryDependencies(customObjectApiName, tab);
563
+ }
564
+ for (const tabEdit of Object.values(edits['systemTabEdits'] ?? {})) {
565
+ if (tabEdit == null || typeof tabEdit !== 'object') continue;
566
+ const edit = tabEdit as Record<string, any>;
567
+ for (const section of edit['newSections'] ?? []) {
568
+ this.loadSectionDependencies(customObjectApiName, section);
569
+ }
570
+ for (const sectionEdit of Object.values(edit['systemSectionEdits'] ?? {})) {
571
+ if (sectionEdit == null || typeof sectionEdit !== 'object') continue;
572
+ for (const field of (sectionEdit as Record<string, any>)['newFields'] ?? []) {
573
+ this.loadSerializedFieldDependency(customObjectApiName, field);
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ private loadTabEntryDependencies(customObjectApiName: string, tab: unknown): void {
580
+ if (tab == null || typeof tab !== 'object') return;
581
+ if ((tab as Record<string, any>)['type'] !== 'tab_with_sections') return;
582
+ for (const section of (tab as Record<string, any>)['sections'] ?? []) {
583
+ this.loadSectionDependencies(customObjectApiName, section);
584
+ }
585
+ }
586
+
587
+ private loadSectionDependencies(customObjectApiName: string, section: unknown): void {
588
+ if (section == null || typeof section !== 'object') return;
589
+ const sectionRecord = section as Record<string, any>;
590
+ this.loadFieldSectionIfPresent(customObjectApiName, sectionRecord['key']);
591
+ for (const field of sectionRecord['fields'] ?? []) {
592
+ this.loadSerializedFieldDependency(customObjectApiName, field);
593
+ }
594
+ }
595
+
596
+ private loadSerializedFieldDependency(customObjectApiName: string, field: unknown): void {
597
+ if (typeof field === 'string') {
598
+ this.loadFieldByObjectAndApi(customObjectApiName, field);
599
+ return;
600
+ }
601
+ if (field == null || typeof field !== 'object') return;
602
+ const record = field as Record<string, any>;
603
+ this.loadFieldDependencyIfString(customObjectApiName, record['fieldRqlName'] ?? record['field_rql_name']);
604
+ }
605
+
606
+ private loadFieldDependencyIfString(customObjectApiName: string, fieldApiName: unknown): void {
607
+ if (typeof fieldApiName === 'string' && fieldApiName.length > 0) {
608
+ this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName);
609
+ }
610
+ }
611
+
612
+ private loadFieldSectionIfPresent(customObjectApiName: string, sectionId: unknown): void {
613
+ if (typeof sectionId !== 'string') return;
614
+ const component = this.ofType('CUSTOM_OBJECT_FIELD_SECTION').find(
615
+ (candidate) => candidate['model_name'] === customObjectApiName && candidate['section_id'] === sectionId,
616
+ );
617
+ if (component != null) {
618
+ this.loadFieldSectionComponent(component);
619
+ }
620
+ }
621
+
622
+ private loadAppDependencies(component: Record<string, any>): void {
623
+ for (const page of component['pages'] ?? []) {
624
+ if (page == null || typeof page !== 'object') continue;
625
+ const pageRecord = page as Record<string, any>;
626
+ if (pageRecord['page_type'] === 'object_page' && typeof pageRecord['object_rql_name'] === 'string') {
627
+ this.loadCustomObject(pageRecord['object_rql_name']);
628
+ }
629
+ if (pageRecord['page_type'] === 'sdui_page' && typeof pageRecord['sdui_page_id'] === 'string') {
630
+ this.loadSduiPage(pageRecord['sdui_page_id']);
631
+ }
632
+ }
633
+ }
634
+
635
+ private ofType(type: string): Record<string, any>[] {
636
+ return this.componentsByType.get(type) ?? [];
637
+ }
638
+
639
+ private hasComponent(type: string, predicate: (component: Record<string, any>) => boolean): boolean {
640
+ return this.ofType(type).some(predicate);
641
+ }
642
+
643
+ private findUnique(
644
+ type: string,
645
+ predicate: (component: Record<string, any>) => boolean,
646
+ label: string,
647
+ ): Record<string, any> {
648
+ const matches = this.ofType(type).filter(predicate);
649
+ if (matches.length === 0) throw new Error(`Could not find ${label}.`);
650
+ if (matches.length > 1) throw new Error(`Ambiguous ${label}; pass a more specific identifier.`);
651
+ return matches[0] as Record<string, any>;
652
+ }
653
+
654
+ private cacheKey(type: string, id: string): string {
655
+ return `${type}:${id}`;
656
+ }
657
+
658
+ private getOrLoad<T>(cacheKey: string, load: () => T): T {
659
+ const cached = this.cache.get(cacheKey);
660
+ if (cached != null) return cached as T;
661
+ if (this.loading.has(cacheKey)) {
662
+ throw new Error(`Circular dependency while loading ${cacheKey}.`);
663
+ }
664
+ this.loading.add(cacheKey);
665
+ try {
666
+ const value = load();
667
+ this.cache.set(cacheKey, value);
668
+ return value;
669
+ } finally {
670
+ this.loading.delete(cacheKey);
671
+ }
672
+ }
673
+ }