Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/events-single-subscription-readback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

Read events modules stored as one module per subscription back into the TOML subscription list
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {transformToEventsConfig, transformFromEventsConfig} from './app_config_events.js'
import {deepMergeObjects} from '@shopify/cli-kit/common/object'
import {describe, expect, test} from 'vitest'

describe('transformFromEventsConfig', () => {
Expand Down Expand Up @@ -192,4 +193,79 @@ describe('transformToEventsConfig', () => {
},
})
})

test('wraps a single-subscription object into a one-element subscription list and strips identifier', () => {
const remoteContent = {
events: {
api_version: '2024-01',
subscription: {
topic: 'Product',
actions: ['update'],
uri: 'https://example.com/webhook',
handle: 'product-updates',
identifier: 'id-1',
},
},
}

const result = transformToEventsConfig(remoteContent)

expect(result).toEqual({
events: {
api_version: '2024-01',
subscription: [
{
topic: 'Product',
actions: ['update'],
uri: 'https://example.com/webhook',
handle: 'product-updates',
},
],
},
})
})

test('multiple single-subscription modules deep-merge into one subscription list', () => {
const moduleConfigs = [
{
events: {
api_version: '2024-01',
subscription: {
topic: 'Product',
actions: ['update'],
uri: 'https://example.com/a',
handle: 'a',
identifier: 'id-a',
},
},
},
{
events: {
api_version: '2024-01',
subscription: {
topic: 'Order',
actions: ['create'],
uri: 'https://example.com/b',
handle: 'b',
identifier: 'id-b',
},
},
},
]

const merged = moduleConfigs.reduce(
(accumulator, moduleConfig) => deepMergeObjects(accumulator, transformToEventsConfig(moduleConfig)),
{},
)

expect(merged).toEqual({
events: {
api_version: '2024-01',
subscription: [
{topic: 'Product', actions: ['update'], uri: 'https://example.com/a', handle: 'a'},
{topic: 'Order', actions: ['create'], uri: 'https://example.com/b', handle: 'b'},
],
},
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,20 @@ export function transformFromEventsConfig(content: object, appConfiguration?: ob
/**
* Transforms the events config from remote to local format.
* Strips the server-managed 'identifier' field from subscriptions.
*
* The server stores events modules in two shapes: a legacy aggregate module
* whose subscription is a list, and one module per subscription whose
* subscription is a single object. Both are normalized to a list here so that
* multiple single-subscription modules deep-merge back into the TOML
* subscription array.
*/
export function transformToEventsConfig(content: object) {
const eventsConfig = getPathValue(content, 'events') as {api_version: string; subscription: object[]}
const eventsConfig = getPathValue(content, 'events') as {
api_version: string
subscription: object[] | object
}
const apiVersion = getPathValue(eventsConfig, 'api_version')
const subscription = getPathValue(eventsConfig, 'subscription') as {identifier: string}[]
const subscription = normalizeSubscriptions(getPathValue(eventsConfig, 'subscription'))

// Server always includes identifier - strip it for local TOML
const cleanedSubscriptions = subscription?.map((sub) => {
Expand All @@ -59,3 +68,9 @@ export function transformToEventsConfig(content: object) {

return {events}
}

function normalizeSubscriptions(subscription: unknown): {identifier?: string}[] | undefined {
if (subscription === undefined) return undefined
if (Array.isArray(subscription)) return subscription as {identifier?: string}[]
return [subscription as {identifier?: string}]
}
Loading