import { App } from './app'; import { Category } from './category'; import { CustomObject } from './custom-object'; import { CustomObjectFieldSection } from './custom-object-field-section'; import { CustomObjectPageLayout } from './custom-object-page-layout'; import { Field } from './field'; import { ListViewDef } from './list-view'; import { ManifestBuilder, type ManifestComponent } from './manifest-builder'; import { ManifestFunction } from './manifest-function'; import { Rule } from './rule'; import { SduiPage } from './sdui-page'; import { Validation } from './validation'; import { optionalStringComponentField, requireObjectComponentField, requireStringComponentField, } from './_existing-component-fields'; export interface ExistingManifest { metadata?: Record; components: Record[]; } export type ExistingManifestSource = string | ExistingManifest | Record[]; export interface ExistingManifestContextOptions { context?: ExistingManifestContext; } interface ExistingManifestObjectOptions { customObjectApiName?: string; } class ExistingRawComponent implements ManifestComponent { constructor(private readonly component: Record) {} toDict(): Record { return cloneJson(this.component); } } function cloneJson(value: T): T { if (value == null) return value; return JSON.parse(JSON.stringify(value)) as T; } function parseExistingManifest(source: ExistingManifestSource): ExistingManifest { const parsed = typeof source === 'string' ? JSON.parse(source) : source; if (Array.isArray(parsed)) { return { components: parsed }; } if (parsed == null || typeof parsed !== 'object') { throw new TypeError('Existing manifest JSON must be an object with a components array.'); } const components = (parsed as ExistingManifest).components; if (!Array.isArray(components)) { throw new TypeError('Existing manifest JSON must include a components array.'); } const manifest: ExistingManifest = { components }; const metadata = (parsed as ExistingManifest).metadata; if (metadata !== undefined) manifest.metadata = metadata; return manifest; } function splitQualifiedId(value: string | undefined): { parentId?: string; childId?: string } { if (value == null) return {}; const separatorIndex = value.indexOf('.'); if (separatorIndex < 0) return { childId: value }; return { parentId: value.slice(0, separatorIndex), childId: value.slice(separatorIndex + 1), }; } function componentLabel(component: Record): string { const type = component['type'] ?? 'UNKNOWN'; const id = component['api_name'] ?? component['layout_id'] ?? component['view_id'] ?? component['rule_id'] ?? component['flow_id'] ?? component['sdui_page_id'] ?? component['field_api_name'] ?? component['section_id'] ?? 'unknown'; return `${type}:${id}`; } export function getExistingManifestContext( options?: ExistingManifestContextOptions, ): ExistingManifestContext { if (options?.context != null) { return options.context; } return ExistingManifestContext.active(); } export class ExistingManifestContext { private static _active: ExistingManifestContext | undefined; private readonly manifest: ExistingManifest; private readonly components: Record[]; private readonly componentsByType = new Map[]>(); private readonly componentIndexes = new WeakMap, number>(); private readonly cache = new Map(); private readonly loading = new Set(); private builder: ManifestBuilder | undefined; constructor(source: ExistingManifestSource) { this.manifest = parseExistingManifest(source); this.components = this.manifest.components; this.components.forEach((component, index) => { this.componentIndexes.set(component, index); const type = component['type']; if (typeof type !== 'string') return; const bucket = this.componentsByType.get(type) ?? []; bucket.push(component); this.componentsByType.set(type, bucket); }); } static fromJson(source: ExistingManifestSource): ExistingManifestContext { const context = new ExistingManifestContext(source); ExistingManifestContext._active = context; return context; } static active(): ExistingManifestContext { if (ExistingManifestContext._active == null) { throw new Error( 'No existing manifest JSON has been loaded. Call ManifestBuilder.useExistingJson(json) first.', ); } return ExistingManifestContext._active; } static clearActive(): void { ExistingManifestContext._active = undefined; } getManifestBuilder(): ManifestBuilder { if (this.builder == null) { const metadata = this.manifest.metadata ?? {}; const key = metadata['key']; if (typeof key !== 'string' || key.length === 0) { throw new Error('Existing manifest metadata.key is required to create a ManifestBuilder.'); } const props = { key, name: typeof metadata['name'] === 'string' ? metadata['name'] : key, description: optionalStringComponentField(metadata, 'description'), }; this.builder = new ManifestBuilder(props); } return this.builder; } loadManifestBuilder(): ManifestBuilder { const builder = this.getManifestBuilder(); this.loadAll(); return builder; } loadAll(): void { this.getManifestBuilder(); for (const component of this.components) { this.loadComponent(component); } } loadCategory(apiName: string): Category { const component = this.findUnique( 'CUSTOM_CATEGORY', (candidate) => candidate['api_name'] === apiName, `CUSTOM_CATEGORY ${apiName}`, ); return this.loadCategoryComponent(component); } loadCustomObject(apiName: string): CustomObject { const component = this.findUnique( 'CUSTOM_OBJECT', (candidate) => candidate['api_name'] === apiName, `CUSTOM_OBJECT ${apiName}`, ); return this.loadCustomObjectComponent(component); } loadFieldSection( sectionIdOrQualifiedId: string, options: ExistingManifestObjectOptions = {}, ): CustomObjectFieldSection { const qualified = splitQualifiedId(sectionIdOrQualifiedId); const sectionId = qualified.childId ?? sectionIdOrQualifiedId; const modelApiName = options.customObjectApiName ?? qualified.parentId; const component = this.findUnique( 'CUSTOM_OBJECT_FIELD_SECTION', (candidate) => candidate['section_id'] === sectionId && (modelApiName == null || candidate['model_name'] === modelApiName), `CUSTOM_OBJECT_FIELD_SECTION ${modelApiName != null ? `${modelApiName}.` : ''}${sectionId}`, ); return this.loadFieldSectionComponent(component); } loadField(fieldApiNameOrQualifiedId: string, options: ExistingManifestObjectOptions = {}): Field { const qualified = splitQualifiedId(fieldApiNameOrQualifiedId); const fieldApiName = qualified.childId ?? fieldApiNameOrQualifiedId; const customObjectApiName = options.customObjectApiName ?? qualified.parentId; return this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName); } loadListView(viewId: string, options: ExistingManifestObjectOptions = {}): ListViewDef { const objectApiName = options.customObjectApiName; const component = this.findUnique( 'CUSTOM_OBJECT_LIST_VIEW', (candidate) => candidate['view_id'] === viewId && (objectApiName == null || candidate['object_rql_name'] === objectApiName), `CUSTOM_OBJECT_LIST_VIEW ${objectApiName != null ? `${objectApiName}.` : ''}${viewId}`, ); return this.loadListViewComponent(component); } loadPageLayout( layoutIdOrApiName: string, options: ExistingManifestObjectOptions = {}, ): CustomObjectPageLayout { const objectApiName = options.customObjectApiName; const component = this.findUnique( 'CUSTOM_OBJECT_PAGE_LAYOUT', (candidate) => (candidate['layout_id'] === layoutIdOrApiName || candidate['api_name'] === layoutIdOrApiName) && (objectApiName == null || candidate['object_rql_name'] === objectApiName), `CUSTOM_OBJECT_PAGE_LAYOUT ${objectApiName != null ? `${objectApiName}.` : ''}${layoutIdOrApiName}`, ); return this.loadPageLayoutComponent(component); } loadValidation(ruleId: string, options: ExistingManifestObjectOptions = {}): Validation { const objectApiName = options.customObjectApiName; const component = this.findUnique( 'CUSTOM_OBJECT_VALIDATION_RULE', (candidate) => candidate['rule_id'] === ruleId && (objectApiName == null || candidate['model_rql_name'] === objectApiName), `CUSTOM_OBJECT_VALIDATION_RULE ${objectApiName != null ? `${objectApiName}.` : ''}${ruleId}`, ); return this.loadValidationComponent(component); } loadRule(flowId: string, options: ExistingManifestObjectOptions = {}): Rule { const objectApiName = options.customObjectApiName; const component = this.findUnique( 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW', (candidate) => candidate['flow_id'] === flowId && (objectApiName == null || candidate['model_rql_name'] === objectApiName), `CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW ${objectApiName != null ? `${objectApiName}.` : ''}${flowId}`, ); return this.loadRuleComponent(component); } loadFunction(apiName: string): ManifestFunction { const component = this.findUnique( 'FUNCTION', (candidate) => candidate['api_name'] === apiName, `FUNCTION ${apiName}`, ); return this.loadFunctionComponent(component); } loadSduiPage(sduiPageId: string): SduiPage { const component = this.findUnique( 'SDUI_PAGE', (candidate) => candidate['sdui_page_id'] === sduiPageId, `SDUI_PAGE ${sduiPageId}`, ); return this.loadSduiPageComponent(component); } loadApp(apiName: string): App { const component = this.findUnique( 'CUSTOM_APP', (candidate) => candidate['api_name'] === apiName, `CUSTOM_APP ${apiName}`, ); return this.loadAppComponent(component); } private loadComponent(component: Record): unknown { switch (component['type']) { case 'CUSTOM_CATEGORY': return this.loadCategoryComponent(component); case 'CUSTOM_OBJECT': return this.loadCustomObjectComponent(component); case 'CUSTOM_OBJECT_FIELD_SECTION': return this.loadFieldSectionComponent(component); case 'CUSTOM_OBJECT_FIELD': return this.loadFieldComponent(component); case 'CUSTOM_OBJECT_LIST_VIEW': return this.loadListViewComponent(component); case 'CUSTOM_OBJECT_PAGE_LAYOUT': return this.loadPageLayoutComponent(component); case 'CUSTOM_OBJECT_VALIDATION_RULE': return this.loadValidationComponent(component); case 'CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW': return this.loadRuleComponent(component); case 'FUNCTION': return this.loadFunctionComponent(component); case 'SDUI_PAGE': return this.loadSduiPageComponent(component); case 'CUSTOM_APP': return this.loadAppComponent(component); default: return this.loadRawComponent(component); } } private loadCategoryComponent(component: Record): Category { const apiName = requireStringComponentField(component, 'api_name', Category.componentType); const cacheKey = this.cacheKey('CUSTOM_CATEGORY', apiName); return this.getOrLoad(cacheKey, () => Category._fromExistingComponent(this.getManifestBuilder(), component), ); } private loadCustomObjectComponent(component: Record): CustomObject { const customObjectApiName = requireStringComponentField( component, 'api_name', CustomObject.componentType, ); const cacheKey = this.cacheKey('CUSTOM_OBJECT', customObjectApiName); return this.getOrLoad(cacheKey, () => { const customObject = CustomObject._fromExistingComponent( this.getManifestBuilder(), component, (categoryApiName) => this.loadRequiredCategoryForCustomObject(categoryApiName, customObjectApiName), ); customObject._setStandardFieldResolver((fieldApiName) => this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName), ); return customObject; }); } private loadFieldSectionComponent(component: Record): CustomObjectFieldSection { const modelName = requireStringComponentField( component, 'model_name', CustomObjectFieldSection.componentType, ); const sectionId = requireStringComponentField( component, 'section_id', CustomObjectFieldSection.componentType, ); const cacheKey = this.cacheKey('CUSTOM_OBJECT_FIELD_SECTION', `${modelName}.${sectionId}`); return this.getOrLoad(cacheKey, () => { const customObject = this.loadCustomObject(modelName); return CustomObjectFieldSection._fromExistingComponent(customObject, component); }); } private loadFieldComponent(component: Record): Field { const customObjectApiName = requireStringComponentField( component, 'custom_object_api_name', Field.componentType, ); const fieldApiName = requireStringComponentField(component, 'field_api_name', Field.componentType); if (component['is_standard'] === true) { this.loadCustomObject(customObjectApiName); return this.loadFieldReferenceComponent(component); } const cacheKey = this.cacheKey('CUSTOM_OBJECT_FIELD', `${customObjectApiName}.${fieldApiName}`); return this.getOrLoad(cacheKey, () => { const customObject = this.loadCustomObject(customObjectApiName); const sectionId = component['section']; if (typeof sectionId === 'string') { this.loadRequiredFieldSection( customObjectApiName, sectionId, `CUSTOM_OBJECT_FIELD ${customObjectApiName}.${fieldApiName}`, ); } this.loadFieldRelationshipDependencies(component); return Field._fromExistingComponent(customObject, component); }); } private loadListViewComponent(component: Record): ListViewDef { const customObjectApiName = requireStringComponentField( component, 'object_rql_name', ListViewDef.componentType, ); const viewId = requireStringComponentField(component, 'view_id', ListViewDef.componentType); const cacheKey = this.cacheKey('CUSTOM_OBJECT_LIST_VIEW', `${customObjectApiName}.${viewId}`); return this.getOrLoad(cacheKey, () => { const customObject = this.loadCustomObject(customObjectApiName); return ListViewDef._fromExistingComponent(customObject, component, (fieldApiName) => this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName), ); }); } private loadPageLayoutComponent(component: Record): CustomObjectPageLayout { const layoutId = requireStringComponentField( component, 'layout_id', CustomObjectPageLayout.componentType, ); const customObjectApiName = requireStringComponentField( component, 'object_rql_name', CustomObjectPageLayout.componentType, ); const cacheKey = this.cacheKey('CUSTOM_OBJECT_PAGE_LAYOUT', layoutId); return this.getOrLoad(cacheKey, () => { const customObject = this.loadCustomObject(customObjectApiName); this.loadPageLayoutDependencies(customObjectApiName, component); return CustomObjectPageLayout._fromExistingComponent(customObject, component, (fieldApiName) => this.fieldSectionIdForField(customObjectApiName, fieldApiName), ); }); } private loadValidationComponent(component: Record): Validation { const customObjectApiName = requireStringComponentField( component, 'model_rql_name', Validation.componentType, ); const ruleId = requireStringComponentField(component, 'rule_id', Validation.componentType); const cacheKey = this.cacheKey('CUSTOM_OBJECT_VALIDATION_RULE', `${customObjectApiName}.${ruleId}`); return this.getOrLoad(cacheKey, () => { const customObject = this.loadCustomObject(customObjectApiName); return Validation._fromExistingComponent(customObject, component, (fieldApiName) => this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName), ); }); } private loadRuleComponent(component: Record): Rule { const customObjectApiName = requireStringComponentField(component, 'model_rql_name', Rule.componentType); const flowId = requireStringComponentField(component, 'flow_id', Rule.componentType); const cacheKey = this.cacheKey('CUSTOM_OBJECT_RECORD_TRIGGERED_FLOW', `${customObjectApiName}.${flowId}`); return this.getOrLoad(cacheKey, () => { const customObject = this.loadCustomObject(customObjectApiName); return Rule._fromExistingComponent(customObject, component, (fieldApiName) => this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName), ); }); } private loadFunctionComponent(component: Record): ManifestFunction { const apiName = requireStringComponentField(component, 'api_name', ManifestFunction.componentType); const cacheKey = this.cacheKey('FUNCTION', apiName); return this.getOrLoad(cacheKey, () => ManifestFunction._fromExistingComponent(this.getManifestBuilder(), component), ); } private loadSduiPageComponent(component: Record): SduiPage { const sduiPageId = requireStringComponentField(component, 'sdui_page_id', SduiPage.componentType); const cacheKey = this.cacheKey('SDUI_PAGE', sduiPageId); return this.getOrLoad(cacheKey, () => { return SduiPage._fromExistingComponent(this.getManifestBuilder(), component, (functionApiName) => this.loadRequiredFunctionForSduiPage(functionApiName, component), ); }); } private loadAppComponent(component: Record): App { const apiName = requireStringComponentField(component, 'api_name', App.componentType); const cacheKey = this.cacheKey('CUSTOM_APP', apiName); return this.getOrLoad(cacheKey, () => { this.loadAppDependencies(component); return App._fromExistingComponent(this.getManifestBuilder(), component); }); } private loadRawComponent(component: Record): ExistingRawComponent { const index = this.componentIndexes.get(component) ?? -1; const cacheKey = this.cacheKey('RAW', `${index}:${componentLabel(component)}`); return this.getOrLoad(cacheKey, () => { const raw = new ExistingRawComponent(component); this.getManifestBuilder()._register(raw); return raw; }); } private loadFieldByObjectAndApi(customObjectApiName: string | undefined, fieldApiName: string): Field { const component = this.findFieldComponent(customObjectApiName, fieldApiName); if (component != null) { return this.loadFieldComponent(component); } if (customObjectApiName != null) { this.loadCustomObject(customObjectApiName); } if (fieldApiName.endsWith('__c')) { const prefix = customObjectApiName != null ? `${customObjectApiName}.` : ''; throw new Error(`Could not find CUSTOM_OBJECT_FIELD ${prefix}${fieldApiName}.`); } return this.loadSyntheticFieldReference(customObjectApiName ?? '', fieldApiName); } private findFieldComponent( customObjectApiName: string | undefined, fieldApiName: string, ): Record | undefined { const matches = this.ofType('CUSTOM_OBJECT_FIELD').filter( (candidate) => candidate['field_api_name'] === fieldApiName && (customObjectApiName == null || candidate['custom_object_api_name'] === customObjectApiName), ); if (matches.length > 1) { throw new Error( `Ambiguous CUSTOM_OBJECT_FIELD ${fieldApiName}; pass customObjectApiName to choose one.`, ); } return matches[0] as Record; } private fieldSectionIdForField(customObjectApiName: string, fieldApiName: string): string | undefined { const field = this.findFieldComponent(customObjectApiName, fieldApiName); const section = field?.['section']; return typeof section === 'string' && section.length > 0 ? section : undefined; } private loadFieldReferenceComponent(component: Record): Field { const cacheKey = this.cacheKey( 'CUSTOM_OBJECT_FIELD_REF', `${component['custom_object_api_name']}.${component['field_api_name']}`, ); return this.getOrLoad(cacheKey, () => Field._referenceFromExistingComponent(component)); } private loadRequiredCategoryForCustomObject( categoryApiName: string, customObjectApiName: string, ): Category { const component = this.ofType('CUSTOM_CATEGORY').find( (candidate) => candidate['api_name'] === categoryApiName, ); if (component == null) { throw new Error( `CUSTOM_OBJECT ${customObjectApiName} references CUSTOM_CATEGORY ${categoryApiName}, ` + 'but that category component is not present. Pass the full existing component extraction ' + 'to ManifestBuilder.loadFromExistingJson(...).', ); } return this.loadCategoryComponent(component); } private loadRequiredFunctionForSduiPage( functionApiName: string, component: Record, ): ManifestFunction { const sduiPageId = component['sdui_page_id'] ?? 'unknown'; const functionComponent = this.ofType('FUNCTION').find( (candidate) => candidate['api_name'] === functionApiName, ); if (functionComponent == null) { throw new Error( `SDUI_PAGE ${sduiPageId} references FUNCTION ${functionApiName}, ` + 'but that function component is not present. Pass the full existing component extraction ' + 'to ManifestBuilder.loadFromExistingJson(...).', ); } return this.loadFunctionComponent(functionComponent); } private loadSyntheticFieldReference(customObjectApiName: string, fieldApiName: string): Field { const cacheKey = this.cacheKey('CUSTOM_OBJECT_FIELD_REF', `${customObjectApiName}.${fieldApiName}`); return this.getOrLoad(cacheKey, () => Field._syntheticReference(customObjectApiName, fieldApiName)); } private loadFieldRelationshipDependencies(component: Record): void { const dataType = requireObjectComponentField(component, 'data_type', Field.componentType); const targetApiName = dataType['og_model_rql_name']; if ( typeof targetApiName === 'string' && this.hasComponent('CUSTOM_OBJECT', (c) => c['api_name'] === targetApiName) ) { this.loadCustomObject(targetApiName); } const summary = component['derived_aggregated_field']; if (summary == null || typeof summary !== 'object') return; const childModel = summary['child_model_name']; if ( typeof childModel === 'string' && this.hasComponent('CUSTOM_OBJECT', (c) => c['api_name'] === childModel) ) { this.loadCustomObject(childModel); this.loadSummaryFieldDependency(childModel, summary['field_to_aggregate']); this.loadSummaryFieldDependency(childModel, summary['lookup_field_api_name']); for (const filter of summary['filters'] ?? []) { this.loadSummaryFieldDependency(childModel, filter['field_name']); } } } private loadSummaryFieldDependency(childModel: string, fieldApiName: unknown): void { if (typeof fieldApiName === 'string') { this.loadFieldByObjectAndApi(childModel, fieldApiName); } } private loadPageLayoutDependencies(customObjectApiName: string, component: Record): void { this.loadHeaderDependencies(customObjectApiName, component['header_edits']); this.loadTabDependencies(customObjectApiName, component['tab_edits']); } private loadHeaderDependencies(customObjectApiName: string, headerEdits: unknown): void { if (headerEdits == null || typeof headerEdits !== 'object') return; const edits = headerEdits as Record; this.loadFieldDependencyIfString(customObjectApiName, edits['newTitleField']); this.loadFieldDependencyIfString(customObjectApiName, edits['newDescriptionField']); for (const field of edits['newFields'] ?? []) { this.loadFieldDependencyIfString(customObjectApiName, field['rqlName']); } for (const systemEdit of Object.values(edits['systemFieldEdits'] ?? {})) { if (systemEdit != null && typeof systemEdit === 'object') { this.loadFieldDependencyIfString( customObjectApiName, (systemEdit as Record)['newRqlField'], ); } } } private loadTabDependencies(customObjectApiName: string, tabEdits: unknown): void { if (tabEdits == null || typeof tabEdits !== 'object') return; const edits = tabEdits as Record; for (const tab of edits['newTabs'] ?? []) { this.loadTabEntryDependencies(customObjectApiName, tab); } for (const tabEdit of Object.values(edits['systemTabEdits'] ?? {})) { if (tabEdit == null || typeof tabEdit !== 'object') continue; const edit = tabEdit as Record; for (const section of edit['newSections'] ?? []) { this.loadSectionDependencies(customObjectApiName, section); } for (const sectionEdit of Object.values(edit['systemSectionEdits'] ?? {})) { if (sectionEdit == null || typeof sectionEdit !== 'object') continue; for (const field of (sectionEdit as Record)['newFields'] ?? []) { this.loadSerializedFieldDependency(customObjectApiName, field); } } } } private loadTabEntryDependencies(customObjectApiName: string, tab: unknown): void { if (tab == null || typeof tab !== 'object') return; if ((tab as Record)['type'] !== 'tab_with_sections') return; for (const section of (tab as Record)['sections'] ?? []) { this.loadSectionDependencies(customObjectApiName, section); } } private loadSectionDependencies(customObjectApiName: string, section: unknown): void { if (section == null || typeof section !== 'object') return; const sectionRecord = section as Record; for (const field of sectionRecord['fields'] ?? []) { this.loadSerializedFieldDependency(customObjectApiName, field); } } private loadSerializedFieldDependency(customObjectApiName: string, field: unknown): void { if (typeof field === 'string') { this.loadFieldByObjectAndApi(customObjectApiName, field); return; } if (field == null || typeof field !== 'object') return; const record = field as Record; const fieldSection = record['fieldSection'] ?? record['field_section']; const fieldApiName = record['fieldRqlName'] ?? record['field_rql_name'] ?? record['apiName'] ?? record['api_name']; if (typeof fieldSection === 'string') { const fieldLabel = typeof fieldApiName === 'string' ? ` ${fieldApiName}` : ''; this.loadRequiredFieldSection( customObjectApiName, fieldSection, `CUSTOM_OBJECT_PAGE_LAYOUT field${fieldLabel} on ${customObjectApiName}`, ); } this.loadFieldDependencyIfString(customObjectApiName, fieldApiName); } private loadFieldDependencyIfString(customObjectApiName: string, fieldApiName: unknown): void { if (typeof fieldApiName === 'string' && fieldApiName.length > 0) { this.loadFieldByObjectAndApi(customObjectApiName, fieldApiName); } } private loadRequiredFieldSection(customObjectApiName: string, sectionId: string, ownerLabel: string): void { const component = this.ofType('CUSTOM_OBJECT_FIELD_SECTION').find( (candidate) => candidate['model_name'] === customObjectApiName && candidate['section_id'] === sectionId, ); if (component == null) { throw new Error( `${ownerLabel} references CUSTOM_OBJECT_FIELD_SECTION ${sectionId}, ` + 'but that field section component is not present. Pass the full existing component extraction ' + 'to ManifestBuilder.loadFromExistingJson(...).', ); } this.loadFieldSectionComponent(component); } private loadAppDependencies(component: Record): void { for (const page of component['pages'] ?? []) { if (page == null || typeof page !== 'object') continue; const pageRecord = page as Record; if (pageRecord['page_type'] === 'object_page' && typeof pageRecord['object_rql_name'] === 'string') { this.loadCustomObject(pageRecord['object_rql_name']); } if (pageRecord['page_type'] === 'sdui_page' && typeof pageRecord['sdui_page_id'] === 'string') { this.loadSduiPage(pageRecord['sdui_page_id']); } } } private ofType(type: string): Record[] { return this.componentsByType.get(type) ?? []; } private hasComponent(type: string, predicate: (component: Record) => boolean): boolean { return this.ofType(type).some(predicate); } private findUnique( type: string, predicate: (component: Record) => boolean, label: string, ): Record { const matches = this.ofType(type).filter(predicate); if (matches.length === 0) throw new Error(`Could not find ${label}.`); if (matches.length > 1) throw new Error(`Ambiguous ${label}; pass a more specific identifier.`); return matches[0] as Record; } private cacheKey(type: string, id: string): string { return `${type}:${id}`; } private getOrLoad(cacheKey: string, load: () => T): T { const cached = this.cache.get(cacheKey); if (cached != null) return cached as T; if (this.loading.has(cacheKey)) { throw new Error(`Circular dependency while loading ${cacheKey}.`); } this.loading.add(cacheKey); try { const value = load(); this.cache.set(cacheKey, value); return value; } finally { this.loading.delete(cacheKey); } } }