Skip to content

Support single-subscription events modules when reading remote configuration - #8425

Merged
isaacroldan merged 13 commits into
mainfrom
events-config-link-shape-tolerance
Oct 1, 2026
Merged

isaacroldan merged 13 commits into
mainfrom
events-config-link-shape-tolerance

Conversation

@rezaansyed

@rezaansyed rezaansyed commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Context

As part of supporting shop-scoped event subscriptions, we now support a dual contract for the events module where it may either be a single subscription object (new) or a list of subscriptions. We want to move away from having two contracts and move towards a single contract as it brings forth a better DX with regards to subscription management and maintains consistency across shop-scoped and app-versioned events modules.

The CLI's events transforms are currently hard-typed to the list shape: transformToEventsConfig calls .map on subscription and crashes on an object, and config link would need N single-subscription modules to merge back into one [[events.subscription]] list in the local TOML.

WHAT is this pull request doing?

  • transformToEventsConfig (remote → local): accepts a single-subscription object or the legacy array. N single-subscription modules are transformed into a list in the TOML so as to maintain the existing TOML structure.
  • transformToEventsConfig also strips a subscription's api_version when it equals the module-level events.api_version. An api_version that differs from the default is a genuine per-subscription override and is kept.
  • transformFromEventsConfig (local → remote): resolves relative subscription URIs for both shapes, preserving the input shape.

The local TOML format is unchanged: [[events.subscription]] stays a list, and api_version appears on a subscription only when it overrides the section default. This is read-side tolerance only.

How to test your changes?

With an app in your local environment, test shopify app config link. It should continue working as normal.

Post-release steps

None.

Measuring impact

  • n/a: covered by existing events module metrics in Core

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

…uration

Assisted-By: devx/aa56a38c-289a-416e-8a9a-0de281e4e3e7
@rezaansyed
rezaansyed force-pushed the events-config-link-shape-tolerance branch from 33e9272 to 80e3b4b Compare September 21, 2026 12:20
…vents configuration

Assisted-By: devx/aa56a38c-289a-416e-8a9a-0de281e4e3e7
…nking config

When Core returns events modules where subscriptions have no handle, fall back to the parent module's registration title (except for the default 'events' handle) so each single-subscription module retains its identity when merged into the local configuration.

Pass the identity through the generic reverse-transform options as module.handle, matching the shared module context other specifications consume.

const subscription = eventsConfig.events.subscription
const resolved = wrapSubscriptions(subscription).map((sub) =>
typeof sub.uri === 'string' ? {...sub, uri: prependApplicationUrl(sub.uri, appUrl)} : sub,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this prepend already existed but... is it needed?

If this is true:

  • On deploy, the backend accepts relative URLs right?
  • On dev, we don't want to use real URLs (we want to use the tunnel URL)

And assuming this:

  • We don't update the toml with the tunnel URL anymore during dev, so it will always have a "real" one.

So... isn't it better to keep the relative URLs in the toml and prepend them ONLY during dev?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but this would change how link currently works as the module stores the full URI today and would return that as part of link. This isn't really a changing existing behaviour so maybe we can address this as a separate PR to unblock this one?

if (Array.isArray(subscription)) {
cleanedSubscriptions = subscription.map(clean)
} else if (subscription) {
const handle = subscription.handle ?? moduleHandle

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any risk on using the moduleHandle as default here? can it lead to duplicate handles in any way?

If yes -> maybe we need to use something random here
If not -> Can we then just use a hardcoded default to avoid passing the module handle to the transform function? It can be something like: events-handle or {subscription.identifier}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the function signature we are even saying that moduleHandle is optional, so there is even a possibility where handle is undefined here, with a hardcoded/computed value here we prevent that and also simplify the changes a lot in this PR

isaacroldan and others added 4 commits September 30, 2026 11:43
Core keeps a single-subscription handle on the module and rejects it inside the subscription. The handle is also the module uid and the seed of the subscription identifier, so a handle derived from the topic and actions changes the module identity after config link, and can collide when two subscriptions share a topic and actions. Pass the module handle to the reverse transform and keep the derived handle only as a fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@isaacroldan
isaacroldan marked this pull request as ready for review September 30, 2026 14:59
@isaacroldan
isaacroldan requested a review from a team as a code owner September 30, 2026 14:59
isaacroldan and others added 3 commits September 30, 2026 17:00
The remote side of the breakdown restores each single-subscription handle from its module. Pass the local module handle too, so matching modules don't show the events section as updated on every deploy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. and removed Area: @shopify/app @shopify/app package issues labels Sep 30, 2026
…dules

The platform derives a single-subscription module's runtime handle, uid
and identifier from the module handle and rejects a nested handle on
write, so a nested handle can only survive on older versions and must
not win over the module handle when linking.
@rezaansyed rezaansyed self-assigned this Sep 30, 2026
@rezaansyed rezaansyed closed this Sep 30, 2026
@rezaansyed rezaansyed reopened this Sep 30, 2026
@isaacroldan
isaacroldan added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 656de93 Oct 1, 2026
55 of 57 checks passed
@isaacroldan
isaacroldan deleted the events-config-link-shape-tolerance branch October 1, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants