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 &mdash; validated sequence must contain exactly the same keys as the target sequence) and <code>subset</code> (new &mdash; 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> &mdash; 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 &mdash; 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;amp;lt;string&amp;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 &mdash; 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>&nbsp; mapping:<br>&nbsp;&nbsp;&nbsp; entityFields:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requiredKey: false<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: sequence<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; orderby: key<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; constraints:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NotBlank:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; message: "There must be &amp;gt;=1 entity reference prop; otherwise the 'entityFields' key should be omitted."<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SequenceKeysMustMatch:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; matchType: subset<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; propertyPathToSequence: props<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sequence:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: sequence<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; orderby: value<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; constraints:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NotBlank:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; message: 'There must be &amp;gt;=1 entity field expression; otherwise the entity reference prop should be deleted.'<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EntityFieldExpressionsSameTarget: ~<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sequence:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: string<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; constraints:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ValidStructuredDataPropExpression:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; choices:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - FieldPropExpression<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - FieldObjectPropsExpression<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - ReferenceFieldPropExpression<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 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 &mdash; 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>) &mdash; 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