Add `dataDependencies.entityFields` to `JavaScriptComponent` config schema
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3585298. -->
Reported by: [penyaskito](https://www.drupal.org/user/959536)
Related to !929
>>>
<h3 id="problem-motivation">Problem/Motivation</h3>
<p>Currently, <code>JavaScriptComponent</code> config entities can declare data dependencies via <code>dataDependencies.urls</code> (external URLs fetched by the component) and <code>dataDependencies.drupalSettings</code> (Canvas Drupal settings needed by the component). However, there is no way for a JS component to declare that one of its props depends on entity field data.</p>
<p>This is the first foundational step toward <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3573831" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3573831</a></span>. Before the UI, runtime evaluation, or entity reference prop type can be built, the config schema needs to support declaring which entity fields each prop depends on.</p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>Add a new optional <code>entityFields</code> key to the <code>dataDependencies</code> mapping on <code>JavaScriptComponent</code> config entities (<code>canvas.js_component.*</code>).</p>
<p><strong>Schema structure:</strong></p>
<ul>
<li><code>entityFields</code> is optional (<code>requiredKey: false</code>), a <code>sequence</code> ordered by key</li>
<li>Each key is a prop name from the component's <code>props</code> (validated via <code>SequenceKeysMustMatch</code> in <code>subset</code> mode)</li>
<li>Each value is an array of field prop expression strings, validated by <code>ValidStructuredDataPropExpression</code> with allowed types:
<ul>
<li><code>FieldPropExpression</code></li>
<li><code>FieldObjectPropsExpression</code></li>
<li><code>ReferenceFieldPropExpression</code></li>
</ul>
</li>
</ul>
<p><strong>Validation constraints:</strong></p>
<ul>
<li><code>SequenceKeysMustMatch</code> (extended): gained a <code>matchType</code> parameter with values <code>same-set</code> (default, pre-existing behavior — validated sequence must contain exactly the same keys as the target sequence) and <code>subset</code> (new — validated sequence's keys must all exist in the target sequence, but missing keys are allowed). Used here in <code>subset</code> mode so <code>entityFields</code> keys must be a subset of the component's <code>props</code> — only entity-reference props will have <code>entityFields</code> entries. Preferred over introducing a separate <code>SequenceKeysAreSubsetOf</code> constraint to avoid duplicating the sequence-comparison logic.</li>
<li><code>EntityFieldExpressionsSameTarget</code> (new): ensures all expressions within a single prop target the same entity type and bundle.</li>
<li><code>ExpressionTargetEntityBundleExists</code> (new): validates entity type existence and bundle existence for each expression string. (Originally scoped as <code>ValidEntityFieldExpressionTarget</code>; renamed for clarity — the constraint is reusable beyond the <code>entityFields</code> context.)</li>
<li>Entity reference props cannot be required: props with <code>entityFields</code> entries must not appear in <code>required</code> (validated in <code>JsComponentHasValidAndSupportedSdcMetadata</code>). A referenced entity may disappear, so the component must tolerate <code>NULL</code>.</li>
</ul>
<p><strong>Config dependency calculation:</strong></p>
<p>Override <code>JavaScriptComponent::calculateDependencies()</code> so each expression in <code>dataDependencies.entityFields</code> contributes its <code>module</code> and <code>config</code> dependencies (field configs, bundle configs, entity type provider modules) to the saved config entity. Follows the same <code>addDependencies()</code> pattern already used by <code>ContentTemplate</code>, <code>Pattern</code>, and <code>PageRegion</code> for component-tree expressions. Without this, <code>ConfigDependencyManager</code> would not know about the field/bundle/module references, breaking cascading deletes, import ordering, and cache invalidation.</p>
<p><strong>OpenAPI:</strong><br>
The <code>CodeComponent</code> schema in <code>openapi.yml</code> is updated to document <code>entityFields</code> as an object with <code>additionalProperties</code> of type <code>array&amp;lt;string&amp;gt;</code>.</p>
<h3 id="remaining-tasks">Remaining tasks</h3>
<p>Review and commit.</p>
<h3 id="user-interface-changes">User interface changes</h3>
<p>None.</p>
<h3 id="api-changes">API changes</h3>
<p>The <code>dataDependencies</code> object in API responses for code components (<code>GET /canvas/api/v0/config/js_component/*</code>) may now include an <code>entityFields</code> key. This is a backwards-compatible addition — existing responses without <code>entityFields</code> remain valid.</p>
<h3 id="data-model-changes">Data model changes</h3>
<p>New optional key in <code>canvas.js_component.*</code> config schema:</p>
<pre class="codeblock"><pre>dataDependencies:<br> mapping:<br> entityFields:<br> requiredKey: false<br> type: sequence<br> orderby: key<br> constraints:<br> NotBlank:<br> message: "There must be &gt;=1 entity reference prop; otherwise the 'entityFields' key should be omitted."<br> SequenceKeysMustMatch:<br> matchType: subset<br> propertyPathToSequence: props<br> sequence:<br> type: sequence<br> orderby: value<br> constraints:<br> NotBlank:<br> message: 'There must be &gt;=1 entity field expression; otherwise the entity reference prop should be deleted.'<br> EntityFieldExpressionsSameTarget: ~<br> sequence:<br> type: string<br> constraints:<br> ValidStructuredDataPropExpression:<br> choices:<br> - FieldPropExpression<br> - FieldObjectPropsExpression<br> - ReferenceFieldPropExpression<br> ExpressionTargetEntityBundleExists: ~</pre></pre><p>No database changes. No config migration needed (<code>requiredKey: false</code> means existing JS components pass validation without changes).</p>
<p>The dependencies array on <code>canvas.js_component.*</code> exports may now include new module/config entries derived from <code>entityFields</code>.</p>
<h3 id="deferred">Deferred to follow-ups</h3>
<ul>
<li>Filter <code>SequenceKeysMustMatch</code> (in <code>subset</code> mode) to only entity reference props, rather than all props — requires the entity reference prop type from <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3573831" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3573831</a></span>.</li>
<li><code>x-allowed-bundle</code> validation for bundled entity types (e.g., require <code>entity:node:article</code> instead of <code>entity:node</code>) — also requires the entity reference prop type. Test coverage exists as a skipped test.</li>
</ul>
> Related issue: [Issue #3585319](https://www.drupal.org/node/3585319)
> Related issue: [Issue #3573831](https://www.drupal.org/node/3573831)
issue