@simplysf/simply-aep-core
v0.14.3
Published
Apex Enterprise Patterns (fflib, force-di, AT4DX) scan and resolution logic — the library layer behind @simplysf/simply-aep, published separately for direct consumption (e.g. editor tooling) without the CLI framework
Readme
@simplysf/simply-aep-core
Apex Enterprise Patterns (fflib, force-di, AT4DX) binding scan and resolution logic. This is not a Salesforce CLI plugin — it's the library layer behind @simplysf/simply-aep's sf simply aep at4dx ... commands, published separately so it can be imported directly by anything that wants the same data (an editor extension, a CI check, a script) without pulling in the CLI framework.
Unlike the other internal libraries in this monorepo (@simplysf/simply-core, @simplysf/simply-plugin-kit, @simplysf/simply-report), this package is meant to be depended on by projects outside this repo, not only by other simply-* packages.
Install
npm install @simplysf/simply-aep-coreRequires Node.js >=22 and either "type": "module" or a dynamic import() — this package ships ESM only.
API
Everything below is exported from the package root. Removing or renaming an export is a breaking change; see src/index.ts.
AT4DX Application Factory bindings
The four ApplicationFactory_{Service,Selector,Domain,UnitOfWork}Binding__mdt Custom Metadata Types — "which class implements interface/SObject X" — resolved down to which record wins per key. See simply-aep's design doc for the resolution rules this ports from AT4DX's own di_Module subclasses.
| Export | Description |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| scanOrgBindings(connection, types) | Queries a live org (plain REST, no Tooling API) for the requested binding types. |
| scanLocalBindings(sourceDirs, types) | Scans local Salesforce DX source directories for the same data, parsing CustomMetadata component XML directly. |
| scanOrgApexTriggers(connection), scanLocalApexTriggers(sourceDirs) | Scan a target org (Tooling API) or local DX source for Apex triggers, parsing each trigger's target SObject and any fflib_SObjectDomain.triggerHandler(...) calls it makes — the input validateBindings's missing-domain-trigger rule needs. See design doc 0036. |
| resolveBindings(records) | Pure function: applies AT4DX's per-type resolution rules (priority ordering, Domain ambiguity, UnitOfWork ordering). |
| validateBindings(records, diagnostics, triggers?) | Pure function: checks a scan's records/diagnostics for a missing/ambiguous/ineligible SObject reference, a duplicate To__c, a duplicate Domain SObject, and duplicate DeveloperNames (Service/Selector/Domain only — UnitOfWork is never validated). Passing triggers (from scanLocalApexTriggers/scanOrgApexTriggers) additionally checks every Domain binding for a wiring trigger (missing-domain-trigger) — omitted, that one check is skipped and nothing else changes. See design doc 0015 and 0036. |
| createBinding(input, target), updateBinding(input, target) | Create or update a Service/Selector/Domain binding record — writes local DX source, deploys to a connected org, or both — validating the result first and refusing to write a wiring problem unless force is passed. See design doc 0015. |
| buildBindingXml(record, meta) | Pure function: serializes a record to the full .md-meta.xml text createBinding/updateBinding write. |
| bindingTypeForLocalObjectName(name) | Maps a local CustomMetadata component's object half back to a BindingType. |
| ALL_BINDING_TYPES, AT4DX_BINDING_OBJECTS, AT4DX_BINDING_LOCAL_OBJECT_NAMES, BINDING_TYPE_BY_FLAG, BINDING_RULES | Constants mapping BindingType to its Custom Metadata Type API name, local object name, and CLI flag spelling, plus validateBindings's rule table. |
| ALL_WRITABLE_BINDING_TYPES, WRITABLE_BINDING_TYPE_BY_FLAG | Same, restricted to the three types createBinding/updateBinding support (no UnitOfWork). |
| BindingWriteError | The error createBinding/updateBinding throw, with a code (see BindingWriteErrorCode) instead of a message you'd have to string-match. |
| AepConnection | The minimal connection shape scanOrgBindings needs (autoFetchQuery, getUsername) — accept this instead of a full @salesforce/core Connection if you're constructing one yourself. |
| At4dxBindingRow, RawBindingRecord, At4dxBindingListResult, BindingType, BindingTypeFlag, BindingKeyField, RawApexTriggerRecord | Types for the row shapes above. |
| WritableBindingType, WritableBindingTypeFlag, BindingIssue, BindingIssueRule, BindingIssueSeverity, At4dxBindingValidateResult, MalformedBindingRecord, AmbiguousBindingRecord | Types for validateBindings's input/output. |
| BindingFieldsInput, CreateBindingInput, UpdateBindingInput, CreateBindingTarget, UpdateBindingTarget, At4dxBindingWriteResult, At4dxBindingCreateResult, At4dxBindingUpdateResult, BindingWriteErrorCode | Types for the create/update functions above. |
| BindingIssueScope, BindingRuleInfo, BindingXmlFields, LocalScanResult, OrgScanResult | Supporting types: an issue's scope (record/scan) and per-rule metadata, buildBindingXml's field subset, and the two scan-result envelopes. |
import {
scanOrgBindings,
scanOrgApexTriggers,
resolveBindings,
validateBindings,
createBinding,
ALL_BINDING_TYPES,
} from '@simplysf/simply-aep-core';
const { records, malformed, ambiguous, missingTypes } = await scanOrgBindings(connection, ALL_BINDING_TYPES);
const rows = resolveBindings(records);
// rows[n].effective tells you which record AT4DX actually resolves to for that binding key
const triggers = await scanOrgApexTriggers(connection);
const issues = validateBindings(records, { malformed, ambiguous }, triggers);
// issues.some(issue => issue.severity === 'error') tells you whether this project's Application Factory
// wiring is broken — including a Domain binding whose SObject has no trigger calling
// fflib_SObjectDomain.triggerHandler() for it (omit `triggers` to skip just that one check)
await createBinding(
{
bindingType: 'Selector',
developerName: 'Account_Selector',
sobject: 'Account',
to: 'AccountsSelector',
priority: 1,
},
{ sourceDir: 'force-app/main/default' },
);
// writes force-app/main/default/customMetadata/ApplicationFactory_SelectorBinding.Account_Selector.md-meta.xmlAT4DX domain-process (trigger routing) bindings
DomainProcessBinding__mdt — AT4DX's Trigger Action Framework metadata wiring an SObject's trigger events to ordered criteria/action Apex classes. See simply-aep's design doc for the resolution model.
| Export | Description |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| scanOrgDomainProcessBindings(connection) | Queries a live org for DomainProcessBinding__mdt records. |
| scanLocalDomainProcessBindings(sourceDirs) | Scans local DX source for the same data. |
| resolveDomainProcessBindings(records) | Pure function: orders bindings per SObject/process context by execution order. |
| validateDomainProcessBindings(records, diagnostics) | Pure function: checks a scan's records/diagnostics for order collisions, dead bindings, duplicate DeveloperNames, and SObject-reference problems. See design doc 0010. |
| ALL_TRIGGER_OPERATIONS, DOMAIN_PROCESS_BINDING_OBJECT, DOMAIN_PROCESS_BINDING_LOCAL_OBJECT_NAME, DOMAIN_PROCESS_BINDING_RULES | Constants for the Custom Metadata Type's API name, local object name, and trigger operation values, plus validateDomainProcessBindings's rule table. |
| ENTITY_DEFINITION_STANDARD_OBJECTS, isCustomObjectApiName(apiName) | A best-effort allowlist of standard objects known to support EntityDefinition metadata relationships, and a helper for detecting a custom (or namespaced-custom) object API name — back both validateDomainProcessBindings's and validateBindings's unsupported-entity-definition-object/unnecessary-entity-definition-alternate rules. See design doc 0014 and 0015. |
| DomainProcessBindingRow, RawDomainProcessBindingRecord, At4dxDomainProcessBindingListResult, DomainProcessType, ProcessContext, TriggerOperation | Types for the row shapes above. |
| DomainProcessBindingIssue, DomainProcessBindingIssueRule, DomainProcessBindingIssueSeverity, At4dxDomainProcessBindingValidateResult | Types for validateDomainProcessBindings's output. |
| MalformedDomainProcessBindingRecord, AmbiguousDomainProcessBindingRecord | The diagnostics a scan reports alongside records — pass both to validateDomainProcessBindings. |
| filterDomainProcessBindingIssues(issues, filter) | Pure function: projects a whole-scan validateDomainProcessBindings result onto the SObjects you're currently showing, without ever silently dropping a scan-scoped issue. See design doc 0011. |
| createDomainProcessBinding(input, target), updateDomainProcessBinding(input, target) | Create or update a DomainProcessBinding__mdt record — writes local DX source, deploys to a connected org, or both — validating the result first and refusing to write a wiring problem unless force is passed. See design doc 0012. |
| buildDomainProcessBindingXml(record, meta) | Pure function: serializes a record to the full .md-meta.xml text createDomainProcessBinding/updateDomainProcessBinding write. |
| deployMetadataFile(connection, filePath, wait) | Deploys a single metadata file to an org and polls to a terminal state. |
| DomainProcessBindingWriteError | The error createDomainProcessBinding/updateDomainProcessBinding throw, with a code (see DomainProcessBindingWriteErrorCode) instead of a message you'd have to string-match. |
| DomainProcessBindingSObjectField, DomainProcessBindingFieldsInput, CreateDomainProcessBindingInput, UpdateDomainProcessBindingInput, CreateDomainProcessBindingTarget, UpdateDomainProcessBindingTarget, At4dxDomainProcessBindingWriteResult, At4dxDomainProcessBindingCreateResult, At4dxDomainProcessBindingUpdateResult, DomainProcessBindingWriteErrorCode | Types for the create/update functions above. |
| DomainProcessBindingIssueScope, DomainProcessBindingRuleInfo, DomainProcessBindingXmlFields, DomainProcessLocalScanResult, DomainProcessOrgScanResult, DeployMetadataFileResult, DeployComponentFailure | Supporting types: issue scope and per-rule metadata, buildDomainProcessBindingXml's field subset, the two scan-result envelopes, and deployMetadataFile's result/per-component failure shapes. |
import {
scanLocalDomainProcessBindings,
resolveDomainProcessBindings,
validateDomainProcessBindings,
createDomainProcessBinding,
} from '@simplysf/simply-aep-core';
const { records, malformed, ambiguous } = scanLocalDomainProcessBindings(['force-app/main/default']);
const rows = resolveDomainProcessBindings(records);
// rows filtered/sorted by sobject give you Account's Before_Insert handler order, for example
const issues = validateDomainProcessBindings(records, { malformed, ambiguous });
// issues.some(issue => issue.severity === 'error') tells you whether this project's AT4DX wiring is broken
await createDomainProcessBinding(
{
developerName: 'Account_Before_Insert_Assign_Owner',
sobject: 'Account',
processContext: 'TriggerExecution',
triggerOperation: 'Before_Insert',
type: 'Action',
classToInject: 'AccountAssignOwnerAction',
order: 10,
},
{ sourceDir: 'force-app/main/default' },
);
// writes force-app/main/default/customMetadata/DomainProcessBinding.Account_Before_Insert_Assign_Owner.md-meta.xmlAT4DX Selector field set inclusions
SelectorConfig_FieldSetInclusion__mdt — tells a selector to add a named field set's items to its queried field list. Unlike the bindings above, there's no priority/winner concept: every IsActive__c: true record for a SObject contributes its field set simultaneously. See design doc 0016.
| Export | Description |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| scanOrgFieldSetInclusions(connection) | Queries a live org for SelectorConfig_FieldSetInclusion__mdt records. |
| scanLocalFieldSetInclusions(sourceDirs) | Scans local DX source for the same data. |
| validateFieldSetInclusions(records, diagnostics) | Pure function: checks a scan's records/diagnostics for a missing/ambiguous/ineligible SObject reference, a duplicate FieldsetName__c (unique org-wide, not per-SObject), and duplicate DeveloperNames. |
| createFieldSetInclusion(input, target), updateFieldSetInclusion(input, target) | Create or update a SelectorConfig_FieldSetInclusion__mdt record — writes local DX source, deploys to a connected org, or both — validating the result first and refusing to write a wiring problem unless force is passed. |
| buildFieldSetInclusionXml(record, meta) | Pure function: serializes a record to the full .md-meta.xml text createFieldSetInclusion/updateFieldSetInclusion write. |
| FIELD_SET_INCLUSION_OBJECT, FIELD_SET_INCLUSION_LOCAL_OBJECT_NAME, FIELD_SET_INCLUSION_RULES | Constants for the Custom Metadata Type's API name, local object name, and validateFieldSetInclusions's rule table. |
| FieldSetInclusionWriteError | The error createFieldSetInclusion/updateFieldSetInclusion throw, with a code (see FieldSetInclusionWriteErrorCode) instead of a message you'd have to string-match. |
| RawFieldSetInclusionRecord, At4dxFieldSetInclusionListResult, FieldSetInclusionSObjectField | Types for the row shape above. |
| FieldSetInclusionIssue, FieldSetInclusionIssueRule, FieldSetInclusionIssueSeverity, FieldSetInclusionIssueScope, FieldSetInclusionRuleInfo, At4dxFieldSetInclusionValidateResult, MalformedFieldSetInclusionRecord, AmbiguousFieldSetInclusionRecord | Types for validateFieldSetInclusions's input/output. |
| FieldSetInclusionFieldsInput, CreateFieldSetInclusionInput, UpdateFieldSetInclusionInput, CreateFieldSetInclusionTarget, UpdateFieldSetInclusionTarget, At4dxFieldSetInclusionWriteResult, At4dxFieldSetInclusionCreateResult, At4dxFieldSetInclusionUpdateResult, FieldSetInclusionWriteErrorCode | Types for the create/update functions above. |
| FieldSetInclusionXmlFields, FieldSetInclusionLocalScanResult, FieldSetInclusionOrgScanResult | buildFieldSetInclusionXml's field subset and the two scan-result envelopes. |
import {
scanLocalFieldSetInclusions,
validateFieldSetInclusions,
createFieldSetInclusion,
} from '@simplysf/simply-aep-core';
const { records, malformed, ambiguous } = scanLocalFieldSetInclusions(['force-app/main/default']);
const issues = validateFieldSetInclusions(records, { malformed, ambiguous });
// issues.some(issue => issue.severity === 'error') tells you whether this project's field set wiring is broken
await createFieldSetInclusion(
{ developerName: 'Account_Contact_Fields', sobject: 'Account', fieldsetName: 'ContactRelatedFields' },
{ sourceDir: 'force-app/main/default' },
);
// writes force-app/main/default/customMetadata/SelectorConfig_FieldSetInclusion.Account_Contact_Fields.md-meta.xmlAT4DX Platform Event subscriptions
PlatformEvents_Subscription__mdt — routes a platform event to a consumer class via AT4DX's PlatformEventDistributor, matched on the event's bus and its Category__c/EventName__c values per the record's MatcherRule__c. There's no priority/sequence: every active, matching subscription on a bus receives the event. See design doc 0025.
| Export | Description |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| scanOrgPlatformEventSubscriptions(connection) | Queries a live org for PlatformEvents_Subscription__mdt records, and whether the Custom Metadata Type exists there at all. |
| scanLocalPlatformEventSubscriptions(sourceDirs) | Scans local DX source for the same data. |
| validatePlatformEventSubscriptions(scan, eventBusFields?) | Pure function: checks a scan for a blank/unrecognized bus, consumer, or matcher rule; a matcher rule that dereferences a blank match field (a runtime NullPointerException hazard); a subscription the distributor's pre-filter can never admit; an event bus missing the fields the distributor reads (when eventBusFields is given); a duplicate Consumer__c; and duplicate DeveloperNames. |
| resolvePlatformEventDistribution(input, records) | Pure function: simulates one event ({ eventBus, category?, eventName? }) through the distributor's control flow and returns every subscription that would receive it, plus every one on that bus that wouldn't and the structured reason (inactive, prefiltered, matcher-rule-missing-field, no-match). |
| createPlatformEventSubscription(input, target), updatePlatformEventSubscription(input, target) | Create or update a PlatformEvents_Subscription__mdt record — writes local DX source, deploys to a connected org, or both — validating the result first and refusing to write a wiring problem unless force is passed. |
| buildPlatformEventSubscriptionXml(record, meta) | Pure function: serializes a record to the full .md-meta.xml text the write functions produce. |
| ALL_MATCHER_RULES, PLATFORM_EVENT_SUBSCRIPTION_OBJECT, PLATFORM_EVENT_SUBSCRIPTION_LOCAL_OBJECT_NAME, PLATFORM_EVENT_SUBSCRIPTION_RULES | Constants for the recognized MatcherRule__c values, the Custom Metadata Type's API name, its local object name, and validatePlatformEventSubscriptions's rule table. |
| PlatformEventSubscriptionWriteError | The error the write functions throw, with a code (see PlatformEventSubscriptionWriteErrorCode) instead of a message you'd have to string-match. |
| MatcherRule, RawPlatformEventSubscriptionRecord, MalformedPlatformEventSubscriptionRecord, At4dxPlatformEventSubscriptionListResult, PlatformEventSubscriptionLocalScanResult, PlatformEventSubscriptionOrgScanResult | Types for the row shape above and the two scan-result envelopes. |
| PlatformEventSubscriptionIssue, PlatformEventSubscriptionIssueRule, PlatformEventSubscriptionIssueSeverity, PlatformEventSubscriptionIssueScope, PlatformEventSubscriptionRuleInfo, At4dxPlatformEventSubscriptionValidateResult, EventBusFields | Types for validatePlatformEventSubscriptions's input/output. |
| PlatformEventDistributionInput, PlatformEventDistributionMatch, PlatformEventDistributionMiss, PlatformEventDistributionMissReason, PlatformEventDistributionResult | Types for resolvePlatformEventDistribution's input/output. |
| PlatformEventSubscriptionFieldsInput, CreatePlatformEventSubscriptionInput, UpdatePlatformEventSubscriptionInput, CreatePlatformEventSubscriptionTarget, UpdatePlatformEventSubscriptionTarget, At4dxPlatformEventSubscriptionWriteResult, At4dxPlatformEventSubscriptionCreateResult, At4dxPlatformEventSubscriptionUpdateResult, PlatformEventSubscriptionWriteErrorCode, PlatformEventSubscriptionXmlFields | Types for the create/update and XML functions above. |
import {
scanLocalPlatformEventSubscriptions,
validatePlatformEventSubscriptions,
resolvePlatformEventDistribution,
createPlatformEventSubscription,
} from '@simplysf/simply-aep-core';
const scan = scanLocalPlatformEventSubscriptions(['force-app/main/default']);
const issues = validatePlatformEventSubscriptions(scan);
// issues.some(issue => issue.severity === 'error') tells you whether this project's event routing is broken
const { matches, misses } = resolvePlatformEventDistribution(
{ eventBus: 'Order_Event__e', category: 'Fulfillment', eventName: 'OrderShipped' },
scan.records,
);
// matches lists the consumers PlatformEventDistributor would invoke for that event; misses says why the rest wouldn't run
await createPlatformEventSubscription(
{
developerName: 'Order_Shipped_Notify',
eventBus: 'Order_Event__e',
consumer: 'OrderShippedNotifier',
matcherRule: 'MatchEventBusAndCategoryAndEventName',
eventCategory: 'Fulfillment',
event: 'OrderShipped',
},
{ sourceDir: 'force-app/main/default' },
);
// writes force-app/main/default/customMetadata/PlatformEvents_Subscription.Order_Shipped_Notify.md-meta.xmlIssues
Please report any issues at https://github.com/SimplySF/simply-plugins-core/issues
Contributing
This package is part of the @simplysf/simply monorepo. See CONTRIBUTING.md for what's specific to this package, and the repo's root CONTRIBUTING.md for repo structure, setup, commit conventions, and how to submit a pull request. Please also read our Code of Conduct.
License
Licensed under the Apache-2.0 license.
