@contember/engine-actions 2.1.0-alpha.23 → 2.1.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/development/src/ActionsExecutionContainerHookFactory.cjs +3 -3
  2. package/dist/development/src/ActionsExecutionContainerHookFactory.cjs.map +1 -1
  3. package/dist/development/src/ActionsExecutionContainerHookFactory.js +3 -3
  4. package/dist/development/src/ActionsExecutionContainerHookFactory.js.map +1 -1
  5. package/dist/development/src/dispatch/WebhookTargetHandler.cjs +3 -0
  6. package/dist/development/src/dispatch/WebhookTargetHandler.cjs.map +1 -1
  7. package/dist/development/src/dispatch/WebhookTargetHandler.js +3 -0
  8. package/dist/development/src/dispatch/WebhookTargetHandler.js.map +1 -1
  9. package/dist/development/src/graphql/resolvers/query/EventsQueryResolver.cjs +4 -1
  10. package/dist/development/src/graphql/resolvers/query/EventsQueryResolver.cjs.map +1 -1
  11. package/dist/development/src/graphql/resolvers/query/EventsQueryResolver.js +4 -1
  12. package/dist/development/src/graphql/resolvers/query/EventsQueryResolver.js.map +1 -1
  13. package/dist/development/src/graphql/schema/actions.graphql.cjs +9 -6
  14. package/dist/development/src/graphql/schema/actions.graphql.cjs.map +1 -1
  15. package/dist/development/src/graphql/schema/actions.graphql.js +9 -6
  16. package/dist/development/src/graphql/schema/actions.graphql.js.map +1 -1
  17. package/dist/development/src/migrations/2025-05-28-160000-action-event-user.cjs +16 -0
  18. package/dist/development/src/migrations/2025-05-28-160000-action-event-user.cjs.map +1 -0
  19. package/dist/development/src/migrations/2025-05-28-160000-action-event-user.js +17 -0
  20. package/dist/development/src/migrations/2025-05-28-160000-action-event-user.js.map +1 -0
  21. package/dist/development/src/migrations/index.cjs +3 -1
  22. package/dist/development/src/migrations/index.cjs.map +1 -1
  23. package/dist/development/src/migrations/index.js +3 -1
  24. package/dist/development/src/migrations/index.js.map +1 -1
  25. package/dist/development/src/migrations/snapshot.cjs +5 -2
  26. package/dist/development/src/migrations/snapshot.cjs.map +1 -1
  27. package/dist/development/src/migrations/snapshot.js +5 -2
  28. package/dist/development/src/migrations/snapshot.js.map +1 -1
  29. package/dist/development/src/triggers/TriggerHandler.cjs +12 -11
  30. package/dist/development/src/triggers/TriggerHandler.cjs.map +1 -1
  31. package/dist/development/src/triggers/TriggerHandler.js +12 -11
  32. package/dist/development/src/triggers/TriggerHandler.js.map +1 -1
  33. package/dist/development/src/triggers/TriggerIndirectChangesFetcher.cjs +44 -2
  34. package/dist/development/src/triggers/TriggerIndirectChangesFetcher.cjs.map +1 -1
  35. package/dist/development/src/triggers/TriggerIndirectChangesFetcher.js +44 -2
  36. package/dist/development/src/triggers/TriggerIndirectChangesFetcher.js.map +1 -1
  37. package/dist/development/src/triggers/TriggerListenersStore.cjs.map +1 -1
  38. package/dist/development/src/triggers/TriggerListenersStore.js.map +1 -1
  39. package/dist/development/src/triggers/TriggerPayloadBuilder.cjs +20 -5
  40. package/dist/development/src/triggers/TriggerPayloadBuilder.cjs.map +1 -1
  41. package/dist/development/src/triggers/TriggerPayloadBuilder.js +20 -5
  42. package/dist/development/src/triggers/TriggerPayloadBuilder.js.map +1 -1
  43. package/dist/development/src/triggers/TriggerPayloadManager.cjs.map +1 -1
  44. package/dist/development/src/triggers/TriggerPayloadManager.js.map +1 -1
  45. package/dist/development/src/triggers/TriggerPayloadPersister.cjs +7 -2
  46. package/dist/development/src/triggers/TriggerPayloadPersister.cjs.map +1 -1
  47. package/dist/development/src/triggers/TriggerPayloadPersister.js +7 -2
  48. package/dist/development/src/triggers/TriggerPayloadPersister.js.map +1 -1
  49. package/dist/production/src/ActionsExecutionContainerHookFactory.cjs +3 -3
  50. package/dist/production/src/ActionsExecutionContainerHookFactory.cjs.map +1 -1
  51. package/dist/production/src/ActionsExecutionContainerHookFactory.js +3 -3
  52. package/dist/production/src/ActionsExecutionContainerHookFactory.js.map +1 -1
  53. package/dist/production/src/dispatch/WebhookTargetHandler.cjs +3 -0
  54. package/dist/production/src/dispatch/WebhookTargetHandler.cjs.map +1 -1
  55. package/dist/production/src/dispatch/WebhookTargetHandler.js +3 -0
  56. package/dist/production/src/dispatch/WebhookTargetHandler.js.map +1 -1
  57. package/dist/production/src/graphql/resolvers/query/EventsQueryResolver.cjs +4 -1
  58. package/dist/production/src/graphql/resolvers/query/EventsQueryResolver.cjs.map +1 -1
  59. package/dist/production/src/graphql/resolvers/query/EventsQueryResolver.js +4 -1
  60. package/dist/production/src/graphql/resolvers/query/EventsQueryResolver.js.map +1 -1
  61. package/dist/production/src/graphql/schema/actions.graphql.cjs +9 -6
  62. package/dist/production/src/graphql/schema/actions.graphql.cjs.map +1 -1
  63. package/dist/production/src/graphql/schema/actions.graphql.js +9 -6
  64. package/dist/production/src/graphql/schema/actions.graphql.js.map +1 -1
  65. package/dist/production/src/migrations/2025-05-28-160000-action-event-user.cjs +16 -0
  66. package/dist/production/src/migrations/2025-05-28-160000-action-event-user.cjs.map +1 -0
  67. package/dist/production/src/migrations/2025-05-28-160000-action-event-user.js +17 -0
  68. package/dist/production/src/migrations/2025-05-28-160000-action-event-user.js.map +1 -0
  69. package/dist/production/src/migrations/index.cjs +3 -1
  70. package/dist/production/src/migrations/index.cjs.map +1 -1
  71. package/dist/production/src/migrations/index.js +3 -1
  72. package/dist/production/src/migrations/index.js.map +1 -1
  73. package/dist/production/src/migrations/snapshot.cjs +5 -2
  74. package/dist/production/src/migrations/snapshot.cjs.map +1 -1
  75. package/dist/production/src/migrations/snapshot.js +5 -2
  76. package/dist/production/src/migrations/snapshot.js.map +1 -1
  77. package/dist/production/src/triggers/TriggerHandler.cjs +12 -11
  78. package/dist/production/src/triggers/TriggerHandler.cjs.map +1 -1
  79. package/dist/production/src/triggers/TriggerHandler.js +12 -11
  80. package/dist/production/src/triggers/TriggerHandler.js.map +1 -1
  81. package/dist/production/src/triggers/TriggerIndirectChangesFetcher.cjs +44 -2
  82. package/dist/production/src/triggers/TriggerIndirectChangesFetcher.cjs.map +1 -1
  83. package/dist/production/src/triggers/TriggerIndirectChangesFetcher.js +44 -2
  84. package/dist/production/src/triggers/TriggerIndirectChangesFetcher.js.map +1 -1
  85. package/dist/production/src/triggers/TriggerListenersStore.cjs.map +1 -1
  86. package/dist/production/src/triggers/TriggerListenersStore.js.map +1 -1
  87. package/dist/production/src/triggers/TriggerPayloadBuilder.cjs +20 -5
  88. package/dist/production/src/triggers/TriggerPayloadBuilder.cjs.map +1 -1
  89. package/dist/production/src/triggers/TriggerPayloadBuilder.js +20 -5
  90. package/dist/production/src/triggers/TriggerPayloadBuilder.js.map +1 -1
  91. package/dist/production/src/triggers/TriggerPayloadManager.cjs.map +1 -1
  92. package/dist/production/src/triggers/TriggerPayloadManager.js.map +1 -1
  93. package/dist/production/src/triggers/TriggerPayloadPersister.cjs +7 -2
  94. package/dist/production/src/triggers/TriggerPayloadPersister.cjs.map +1 -1
  95. package/dist/production/src/triggers/TriggerPayloadPersister.js +7 -2
  96. package/dist/production/src/triggers/TriggerPayloadPersister.js.map +1 -1
  97. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  98. package/dist/types/dispatch/WebhookTargetHandler.d.ts.map +1 -1
  99. package/dist/types/graphql/resolvers/query/EventsQueryResolver.d.ts.map +1 -1
  100. package/dist/types/graphql/schema/actions.graphql.d.ts.map +1 -1
  101. package/dist/types/graphql/schema/index.d.ts +6 -0
  102. package/dist/types/graphql/schema/index.d.ts.map +1 -1
  103. package/dist/types/migrations/2025-05-28-160000-action-event-user.d.ts +3 -0
  104. package/dist/types/migrations/2025-05-28-160000-action-event-user.d.ts.map +1 -0
  105. package/dist/types/migrations/index.d.ts.map +1 -1
  106. package/dist/types/migrations/snapshot.d.ts.map +1 -1
  107. package/dist/types/model/types.d.ts +3 -0
  108. package/dist/types/model/types.d.ts.map +1 -1
  109. package/dist/types/triggers/TriggerHandler.d.ts.map +1 -1
  110. package/dist/types/triggers/TriggerIndirectChangesFetcher.d.ts +11 -3
  111. package/dist/types/triggers/TriggerIndirectChangesFetcher.d.ts.map +1 -1
  112. package/dist/types/triggers/TriggerListenersStore.d.ts +2 -2
  113. package/dist/types/triggers/TriggerListenersStore.d.ts.map +1 -1
  114. package/dist/types/triggers/TriggerPayloadBuilder.d.ts.map +1 -1
  115. package/dist/types/triggers/TriggerPayloadManager.d.ts +2 -1
  116. package/dist/types/triggers/TriggerPayloadManager.d.ts.map +1 -1
  117. package/dist/types/triggers/TriggerPayloadPersister.d.ts +7 -2
  118. package/dist/types/triggers/TriggerPayloadPersister.d.ts.map +1 -1
  119. package/dist/types/tsconfig.tsbuildinfo +1 -1
  120. package/package.json +15 -15
  121. package/src/ActionsExecutionContainerHookFactory.ts +3 -3
  122. package/src/dispatch/WebhookTargetHandler.ts +3 -0
  123. package/src/graphql/resolvers/query/EventsQueryResolver.ts +3 -0
  124. package/src/graphql/schema/actions.graphql.ts +9 -6
  125. package/src/graphql/schema/index.ts +6 -0
  126. package/src/migrations/2025-05-28-160000-action-event-user.ts +16 -0
  127. package/src/migrations/index.ts +2 -0
  128. package/src/migrations/snapshot.ts +5 -2
  129. package/src/model/types.ts +3 -0
  130. package/src/triggers/TriggerHandler.ts +12 -11
  131. package/src/triggers/TriggerIndirectChangesFetcher.ts +62 -3
  132. package/src/triggers/TriggerListenersStore.ts +2 -2
  133. package/src/triggers/TriggerPayloadBuilder.ts +18 -1
  134. package/src/triggers/TriggerPayloadManager.ts +2 -1
  135. package/src/triggers/TriggerPayloadPersister.ts +6 -1
  136. package/tests/cases/unit/generator.test.ts +1 -1
  137. package/tests/cases/unit/webhookHandler.test.ts +9 -0
package/package.json CHANGED
@@ -22,20 +22,20 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@contember/authorization": "2.1.0-alpha.23",
26
- "@contember/database": "2.1.0-alpha.23",
27
- "@contember/database-migrations": "2.1.0-alpha.23",
28
- "@contember/engine-common": "2.1.0-alpha.23",
29
- "@contember/engine-content-api": "2.1.0-alpha.23",
30
- "@contember/engine-http": "2.1.0-alpha.23",
31
- "@contember/engine-plugins": "2.1.0-alpha.23",
32
- "@contember/engine-system-api": "2.1.0-alpha.23",
33
- "@contember/engine-tenant-api": "2.1.0-alpha.23",
34
- "@contember/graphql-utils": "2.1.0-alpha.23",
35
- "@contember/logger": "2.1.0-alpha.23",
36
- "@contember/schema": "2.1.0-alpha.23",
37
- "@contember/schema-utils": "2.1.0-alpha.23",
38
- "@contember/typesafe": "2.1.0-alpha.23",
25
+ "@contember/authorization": "2.1.0-alpha.25",
26
+ "@contember/database": "2.1.0-alpha.25",
27
+ "@contember/database-migrations": "2.1.0-alpha.25",
28
+ "@contember/engine-common": "2.1.0-alpha.25",
29
+ "@contember/engine-content-api": "2.1.0-alpha.25",
30
+ "@contember/engine-http": "2.1.0-alpha.25",
31
+ "@contember/engine-plugins": "2.1.0-alpha.25",
32
+ "@contember/engine-system-api": "2.1.0-alpha.25",
33
+ "@contember/engine-tenant-api": "2.1.0-alpha.25",
34
+ "@contember/graphql-utils": "2.1.0-alpha.25",
35
+ "@contember/logger": "2.1.0-alpha.25",
36
+ "@contember/schema": "2.1.0-alpha.25",
37
+ "@contember/schema-utils": "2.1.0-alpha.25",
38
+ "@contember/typesafe": "2.1.0-alpha.25",
39
39
  "@graphql-tools/schema": "^10.0.7",
40
40
  "graphql-tag": "^2.12.6"
41
41
  },
@@ -49,5 +49,5 @@
49
49
  "pg": "^8.5.0"
50
50
  },
51
51
  "type": "module",
52
- "version": "2.1.0-alpha.23"
52
+ "version": "2.1.0-alpha.25"
53
53
  }
@@ -12,12 +12,12 @@ export class ActionsExecutionContainerHookFactory {
12
12
 
13
13
  create(): ExecutionContainerHook {
14
14
  return builder => {
15
- return builder.setupService('mapperFactory', (mapperFactory, { whereBuilder, schema, pathFactory, systemSchema, providers, stage, project, schemaMeta }) => {
15
+ return builder.setupService('mapperFactory', (mapperFactory, { whereBuilder, schema, pathFactory, systemSchema, providers, stage, project, schemaMeta, joinBuilder, userInfo }) => {
16
16
  mapperFactory.hooks.push(mapper => {
17
- const triggerPayloadPersister = new TriggerPayloadPersister(mapper, mapper.db.forSchema(systemSchema), providers, project.slug, stage.id, schemaMeta.id)
17
+ const triggerPayloadPersister = new TriggerPayloadPersister(mapper, mapper.db.forSchema(systemSchema), providers, project.slug, stage.id, schemaMeta.id, mapper.identityId, userInfo)
18
18
  const triggerPayloadBuilder = new TriggerPayloadBuilder(mapper)
19
19
  const payloadManager = new TriggerPayloadManager(triggerPayloadBuilder, triggerPayloadPersister)
20
- const changesFetcher = new TriggerIndirectChangesFetcher(mapper, whereBuilder, pathFactory)
20
+ const changesFetcher = new TriggerIndirectChangesFetcher(schema.model, mapper, whereBuilder, joinBuilder, pathFactory)
21
21
  const listenersStore = this.listenerStoreProvider.getListenerStore(schema)
22
22
  const triggerHandler = new TriggerHandler(payloadManager, listenersStore, changesFetcher)
23
23
  triggerHandler.attach(mapper.eventManager)
@@ -174,6 +174,9 @@ export class WebhookTargetHandler implements InvokeHandler<Actions.WebhookTarget
174
174
  meta: {
175
175
  eventId: it.id,
176
176
  transactionId: it.transaction_id,
177
+ identityId: it.identity_id ?? null,
178
+ ipAddress: it.ip_address ?? null,
179
+ userAgent: it.user_agent ?? null,
177
180
  createdAt: it.created_at.toISOString(),
178
181
  lastStateChange: it.last_state_change.toISOString(),
179
182
  numRetries: it.num_retries,
@@ -68,6 +68,9 @@ export class EventsQueryResolver implements QueryResolvers<ActionsContext> {
68
68
  state: row.state,
69
69
  target: row.target,
70
70
  transactionId: row.transaction_id,
71
+ identityId: row.identity_id ?? undefined,
72
+ ipAddress: row.ip_address ?? undefined,
73
+ userAgent: row.user_agent ?? undefined,
71
74
  }))
72
75
  }
73
76
  }
@@ -35,6 +35,9 @@ export const schema: DocumentNode = gql`
35
35
  type Event {
36
36
  id: Uuid!
37
37
  transactionId: Uuid!
38
+ identityId: Uuid
39
+ ipAddress: String
40
+ userAgent: String
38
41
  createdAt: DateTime!
39
42
  lastStateChange: DateTime!
40
43
  resolvedAt: DateTime
@@ -59,11 +62,11 @@ export const schema: DocumentNode = gql`
59
62
  type ProcessBatchResponse {
60
63
  ok: Boolean!
61
64
  }
62
-
65
+
63
66
  type RetryEventResponse {
64
67
  ok: Boolean!
65
68
  }
66
-
69
+
67
70
  type StopEventResponse {
68
71
  ok: Boolean!
69
72
  }
@@ -72,12 +75,12 @@ export const schema: DocumentNode = gql`
72
75
  variables: [VariableInput!]!
73
76
  mode: SetVariablesMode
74
77
  }
75
-
78
+
76
79
  """
77
- Defines how it handles original variables.
80
+ Defines how it handles original variables.
78
81
  - MERGE merges with new values (default behaviour)
79
82
  - SET replaces all variables
80
- - APPEND_ONLY_MISSING appends values if not already exist
83
+ - APPEND_ONLY_MISSING appends values if not already exist
81
84
  """
82
85
  enum SetVariablesMode {
83
86
  MERGE
@@ -89,7 +92,7 @@ export const schema: DocumentNode = gql`
89
92
  name: String!
90
93
  value: String!
91
94
  }
92
-
95
+
93
96
  type SetVariablesResponse {
94
97
  ok: Boolean!
95
98
  }
@@ -23,6 +23,8 @@ export type Event = {
23
23
  readonly __typename?: 'Event'
24
24
  readonly createdAt: Scalars['DateTime']['output']
25
25
  readonly id: Scalars['Uuid']['output']
26
+ readonly identityId?: Maybe<Scalars['Uuid']['output']>
27
+ readonly ipAddress?: Maybe<Scalars['String']['output']>
26
28
  readonly lastStateChange: Scalars['DateTime']['output']
27
29
  readonly log: Scalars['Json']['output']
28
30
  readonly numRetries: Scalars['Int']['output']
@@ -32,6 +34,7 @@ export type Event = {
32
34
  readonly state: EventState
33
35
  readonly target: Scalars['String']['output']
34
36
  readonly transactionId: Scalars['Uuid']['output']
37
+ readonly userAgent?: Maybe<Scalars['String']['output']>
35
38
  readonly visibleAt?: Maybe<Scalars['DateTime']['output']>
36
39
  }
37
40
 
@@ -268,6 +271,8 @@ export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversT
268
271
  export type EventResolvers<ContextType = any, ParentType extends ResolversParentTypes['Event'] = ResolversParentTypes['Event']> = {
269
272
  createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>
270
273
  id?: Resolver<ResolversTypes['Uuid'], ParentType, ContextType>
274
+ identityId?: Resolver<Maybe<ResolversTypes['Uuid']>, ParentType, ContextType>
275
+ ipAddress?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>
271
276
  lastStateChange?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>
272
277
  log?: Resolver<ResolversTypes['Json'], ParentType, ContextType>
273
278
  numRetries?: Resolver<ResolversTypes['Int'], ParentType, ContextType>
@@ -277,6 +282,7 @@ export type EventResolvers<ContextType = any, ParentType extends ResolversParent
277
282
  state?: Resolver<ResolversTypes['EventState'], ParentType, ContextType>
278
283
  target?: Resolver<ResolversTypes['String'], ParentType, ContextType>
279
284
  transactionId?: Resolver<ResolversTypes['Uuid'], ParentType, ContextType>
285
+ userAgent?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>
280
286
  visibleAt?: Resolver<Maybe<ResolversTypes['DateTime']>, ParentType, ContextType>
281
287
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>
282
288
  }
@@ -0,0 +1,16 @@
1
+ import { MigrationBuilder } from '@contember/database-migrations'
2
+
3
+ const sql = `
4
+ ALTER TABLE actions_event
5
+ ADD COLUMN IF NOT EXISTS identity_id UUID DEFAULT NULL;
6
+
7
+ ALTER TABLE actions_event
8
+ ADD COLUMN IF NOT EXISTS ip_address INET DEFAULT NULL;
9
+
10
+ ALTER TABLE actions_event
11
+ ADD COLUMN IF NOT EXISTS user_agent TEXT DEFAULT NULL;
12
+ `
13
+
14
+ export default async function (builder: MigrationBuilder) {
15
+ builder.sql(sql)
16
+ }
@@ -1,7 +1,9 @@
1
1
  import snapshot from './snapshot'
2
2
  import { MigrationGroup } from '@contember/database-migrations'
3
3
  import _20230310145000actionsvariables from './2023-03-10-145000-actions-variables'
4
+ import _20250528160000actioneventuser from './2025-05-28-160000-action-event-user'
4
5
 
5
6
  export const migrationsGroup = new MigrationGroup(snapshot, {
6
7
  '2023-03-10-145000-actions-variables': _20230310145000actionsvariables,
8
+ '2025-05-28-160000-action-event-user': _20250528160000actioneventuser,
7
9
  })
@@ -30,11 +30,14 @@ export default async function (builder: MigrationBuilder) {
30
30
  trigger TEXT NOT NULL,
31
31
  priority INT NOT NULL DEFAULT 0,
32
32
  payload JSONB NOT NULL,
33
- log JSONB NOT NULL DEFAULT '[]'
33
+ log JSONB NOT NULL DEFAULT '[]',
34
+ identity_id UUID DEFAULT NULL,
35
+ ip_address INET DEFAULT NULL,
36
+ user_agent TEXT DEFAULT NULL
34
37
  );
35
38
 
36
39
  CREATE INDEX ON actions_event (priority DESC, visible_at) WHERE state IN ('created', 'retrying', 'processing');
37
-
40
+
38
41
  CREATE TABLE actions_variable
39
42
  (
40
43
  id UUID PRIMARY KEY NOT NULL,
@@ -14,6 +14,9 @@ export type EventRow = {
14
14
  target: string
15
15
  priority: number
16
16
  trigger: string
17
+ identity_id: string | null
18
+ ip_address: string | null
19
+ user_agent: string | null
17
20
  payload: ActionsPayload.AnyEventPayload
18
21
  log: any[]
19
22
  }
@@ -71,28 +71,28 @@ export class TriggerHandler {
71
71
 
72
72
  private async directUpdateHandler(event: AfterUpdateEvent) {
73
73
  const updateListeners = this.listenersStore.getUpdateListeners(event.entity.name)
74
- updateListeners.map(listener => {
74
+ await Promise.all(updateListeners.map(listener => {
75
75
  if (event.data.some(it => listener.fields.has(it.fieldName) && it.value !== it.old)) {
76
- this.payloadManager.add({
76
+ return this.payloadManager.add({
77
77
  listener,
78
78
  entity: event.entity,
79
79
  primary: event.id,
80
80
  cause: event,
81
81
  })
82
82
  }
83
- })
83
+ }))
84
84
  }
85
85
 
86
86
  private async directCreateHandler(event: AfterInsertEvent) {
87
87
  const createListeners = this.listenersStore.getCreateListener(event.entity.name)
88
- createListeners.map(listener => {
89
- this.payloadManager.add({
88
+ await Promise.all(createListeners.map(listener => {
89
+ return this.payloadManager.add({
90
90
  listener,
91
91
  entity: event.entity,
92
92
  primary: event.id,
93
93
  cause: event,
94
94
  })
95
- })
95
+ }))
96
96
  }
97
97
 
98
98
 
@@ -103,7 +103,7 @@ export class TriggerHandler {
103
103
  private async indirectChangesEntityHandler(event: BeforeDeleteEvent | AfterInsertEvent) {
104
104
  //
105
105
  const indirectEntityListeners = this.listenersStore.getIndirectListeners(event.entity.name)
106
- const promises = indirectEntityListeners.map(listener => this.collectDeepChanges(event, listener, {
106
+ const promises = indirectEntityListeners.map(listener => this.collectDeepChanges(event, listener, {
107
107
  [event.entity.primary]: { eq: event.id },
108
108
  }))
109
109
  await Promise.all(promises)
@@ -149,13 +149,14 @@ export class TriggerHandler {
149
149
  where: Input.Where,
150
150
  ) {
151
151
  const result = await this.changesFetcher.fetch(listener, where)
152
- for (const row of result) {
153
- this.payloadManager.add({
152
+ await Promise.all(result.map(row => {
153
+ return this.payloadManager.add({
154
154
  listener,
155
155
  entity: listener.rootEntity,
156
- primary: row,
156
+ primary: row.id,
157
157
  cause,
158
+ nodes: row.nodes,
158
159
  })
159
- }
160
+ }))
160
161
  }
161
162
  }
@@ -1,12 +1,21 @@
1
1
  import { Mapper, PathFactory, WhereBuilder } from '@contember/engine-content-api'
2
2
  import { IndirectListener, JunctionListener } from './TriggerListenersStore'
3
3
  import { SelectBuilder } from '@contember/database'
4
- import { Input } from '@contember/schema'
4
+ import { ActionsPayload, Input, Model, Schema } from '@contember/schema'
5
+ import { JoinBuilder } from '@contember/engine-content-api'
6
+ import { getTargetEntity } from '@contember/schema-utils'
7
+
8
+ export type TriggerIndirectChangesFetcherResult = {
9
+ id: Input.PrimaryValue
10
+ nodes?: ActionsPayload.EntityEventPathNode[]
11
+ }[]
5
12
 
6
13
  export class TriggerIndirectChangesFetcher {
7
14
  constructor(
15
+ private readonly model: Model.Schema,
8
16
  private readonly mapper: Mapper,
9
17
  private readonly whereBuilder: WhereBuilder,
18
+ private readonly joinBuilder: JoinBuilder,
10
19
  private readonly pathFactory: PathFactory,
11
20
  ) {
12
21
  }
@@ -14,7 +23,11 @@ export class TriggerIndirectChangesFetcher {
14
23
  public async fetch(
15
24
  listener: IndirectListener | JunctionListener,
16
25
  where: Input.Where,
17
- ): Promise<Input.PrimaryValue[]> {
26
+ ): Promise<TriggerIndirectChangesFetcherResult> {
27
+ if (listener.trigger.withNodes) {
28
+ return this.fetchWithNodes(listener, where)
29
+ }
30
+
18
31
  for (let i = listener.path.length - 1; i >= 0; i--) {
19
32
  where = { [listener.path[i]]: where }
20
33
  }
@@ -24,7 +37,53 @@ export class TriggerIndirectChangesFetcher {
24
37
  const path = this.pathFactory.create([])
25
38
  const qbWithWhere = this.whereBuilder.build(qb, listener.rootEntity, path, where)
26
39
 
27
- return (await qbWithWhere.getResult(this.mapper.db)).map(it => it.id)
40
+ return (await qbWithWhere.getResult(this.mapper.db))
28
41
  }
29
42
 
43
+ private async fetchWithNodes(
44
+ listener: IndirectListener | JunctionListener,
45
+ where: Input.Where,
46
+ ): Promise<TriggerIndirectChangesFetcherResult> {
47
+ const basePath = this.pathFactory.create([])
48
+ let path = basePath
49
+ let entity = listener.rootEntity
50
+ let qb = SelectBuilder.create()
51
+ .from(entity.tableName, path.alias)
52
+ .select([path.alias, entity.primaryColumn], path.for(entity.primary).alias)
53
+
54
+ for (let i = 0; i < listener.path.length; i++) {
55
+ const relationName = listener.path[i]
56
+ path = path.for(relationName)
57
+ const targetEntity = getTargetEntity(this.model, entity, relationName)
58
+ if (!targetEntity) {
59
+ throw new Error(`Target entity for relation ${entity.name}::${relationName} not found`)
60
+ }
61
+ qb = this.joinBuilder.join(qb, path, entity, relationName)
62
+
63
+ entity = targetEntity
64
+ qb = qb.select([path.alias, targetEntity.primaryColumn], path.for(targetEntity.primary).alias)
65
+ }
66
+
67
+ const qbWithWhere = this.whereBuilder.build(qb, entity, path, where)
68
+
69
+ const result = await qbWithWhere.getResult(this.mapper.db)
70
+
71
+ return result.map(row => {
72
+ const nodes: ActionsPayload.EntityEventPathNode[] = []
73
+ let entity = listener.rootEntity
74
+ let path = basePath
75
+ const id = row[path.for(entity.primary).alias]
76
+ for (let i = 0; i < listener.path.length; i++) {
77
+ const relationName = listener.path[i]
78
+ const targetEntity = getTargetEntity(this.model, entity, relationName)
79
+ if (!targetEntity) {
80
+ throw new Error(`Target entity for relation ${entity.name}::${relationName} not found`)
81
+ }
82
+ path = path.for(relationName)
83
+ nodes.push({ relation: relationName, entity: targetEntity.name, id: row[path.for(targetEntity.primary).alias] })
84
+ entity = targetEntity
85
+ }
86
+ return { id, nodes }
87
+ })
88
+ }
30
89
  }
@@ -3,7 +3,7 @@ import { TriggerListeners } from './TriggerListeners'
3
3
 
4
4
  export interface IndirectListener {
5
5
  type: 'indirect'
6
- trigger: Actions.AnyTrigger
6
+ trigger: Actions.WatchTrigger
7
7
  rootEntity: Model.Entity
8
8
  fields: Set<string>
9
9
  relations: Set<string>
@@ -12,7 +12,7 @@ export interface IndirectListener {
12
12
 
13
13
  export interface JunctionListener {
14
14
  type: 'junction'
15
- trigger: Actions.AnyTrigger
15
+ trigger: Actions.WatchTrigger
16
16
  rootEntity: Model.Entity
17
17
  path: string[]
18
18
  context: {
@@ -51,6 +51,19 @@ export class TriggerPayloadBuilder {
51
51
  }
52
52
  const payloads: ActionsPayload.WatchEventPayload[] = []
53
53
  for (const eventsByPrimary of Object.values(byPrimary)) {
54
+ const baseEvents: ActionsPayload.BaseEventPayload[] = []
55
+
56
+ const existingEvents = new Set<string>()
57
+ for (const event of eventsByPrimary.events) {
58
+ const baseEvent = this.buildBaseEventPayloads(event)
59
+ const stringRepresentation = JSON.stringify(baseEvent)
60
+ if (existingEvents.has(stringRepresentation)) {
61
+ continue
62
+ }
63
+ existingEvents.add(stringRepresentation)
64
+ baseEvents.push(baseEvent)
65
+ }
66
+
54
67
  payloads.push({
55
68
  operation: 'watch',
56
69
  trigger: events[0].listener.trigger.name,
@@ -58,7 +71,7 @@ export class TriggerPayloadBuilder {
58
71
  id: eventsByPrimary.primary,
59
72
  selection: eventsByPrimary.events.find(it => it.selection)?.selection
60
73
  ?? selections[eventsByPrimary.primary],
61
- events: eventsByPrimary.events.map(it => this.buildBaseEventPayloads(it)),
74
+ events: baseEvents,
62
75
  })
63
76
  }
64
77
  return payloads
@@ -74,6 +87,7 @@ export class TriggerPayloadBuilder {
74
87
  selection,
75
88
  values: Object.fromEntries(event.cause.data.map(it => [it.fieldName, it.value])),
76
89
  path: 'path' in event.listener ? event.listener.path : undefined,
90
+ nodes: event.nodes,
77
91
  }
78
92
  case 'AfterUpdateEvent':
79
93
  case 'BeforeUpdateEvent':
@@ -89,6 +103,7 @@ export class TriggerPayloadBuilder {
89
103
  values: Object.fromEntries(event.cause.data.map(it => [it.fieldName, it.value])),
90
104
  path: 'path' in event.listener ? event.listener.path : undefined,
91
105
  old: Object.fromEntries(afterEvent.data.map(it => [it.fieldName, it.old])),
106
+ nodes: event.nodes,
92
107
  }
93
108
  case 'BeforeDeleteEvent':
94
109
  return {
@@ -97,6 +112,7 @@ export class TriggerPayloadBuilder {
97
112
  id: event.cause.id,
98
113
  selection,
99
114
  path: 'path' in event.listener ? event.listener.path : undefined,
115
+ nodes: event.nodes,
100
116
  }
101
117
  case 'AfterJunctionUpdateEvent':
102
118
  if (event.listener.type !== 'junction') {
@@ -112,6 +128,7 @@ export class TriggerPayloadBuilder {
112
128
  id,
113
129
  inverseId,
114
130
  path: 'path' in event.listener ? event.listener.path : undefined,
131
+ nodes: event.nodes,
115
132
  }
116
133
  default:
117
134
  assertNever(event.cause, it => it.type)
@@ -1,4 +1,4 @@
1
- import { Actions, Input, Model } from '@contember/schema'
1
+ import { Actions, ActionsPayload, Input, Model } from '@contember/schema'
2
2
  import { AnyListener } from './TriggerListenersStore'
3
3
  import { EventCause } from './TriggerHandler'
4
4
  import { TriggerPayloadBuilder } from './TriggerPayloadBuilder'
@@ -11,6 +11,7 @@ export interface FiredEvent {
11
11
  entity: Model.Entity
12
12
  primary: Input.PrimaryValue
13
13
  selection?: any
14
+ nodes?: ActionsPayload.EntityEventPathNode[]
14
15
  }
15
16
 
16
17
  export class TriggerPayloadManager {
@@ -18,7 +18,9 @@ export class TriggerPayloadPersister {
18
18
  private readonly providers: { uuid: () => string },
19
19
  private readonly projectSlug: string,
20
20
  private readonly stageId: string,
21
- private readonly schemaId?: number,
21
+ private readonly schemaId: number | undefined,
22
+ private readonly identityId: string,
23
+ private readonly userInfo: { ipAddress: string | null; userAgent: string | null },
22
24
  ) {
23
25
  }
24
26
 
@@ -46,6 +48,9 @@ export class TriggerPayloadPersister {
46
48
  payload: it,
47
49
  resolved_at: null,
48
50
  last_state_change: 'now',
51
+ identity_id: this.identityId,
52
+ ip_address: this.userInfo.ipAddress,
53
+ user_agent: this.userInfo.userAgent,
49
54
  })))
50
55
  .execute(this.client)
51
56
  }
@@ -8,7 +8,7 @@ const assert = {
8
8
  equal: (a: any, b: any) => expect(a).toEqual(b),
9
9
  deepStrictEqual: (a: any, b: any) => expect(a).toStrictEqual(b),
10
10
  }
11
- const createTrigger = (watchNode: Actions.SelectionNode): Actions.AnyTrigger => ({
11
+ const createTrigger = (watchNode: Actions.SelectionNode): Actions.WatchTrigger => ({
12
12
  type: 'watch',
13
13
  entity: 'Author',
14
14
  name: 'author_trigger',
@@ -29,6 +29,9 @@ const createTestEvent = (i = 0, row: Partial<EventRow> = {}): EventRow => ({
29
29
  priority: 1,
30
30
  schema_id: 1,
31
31
  state: 'created',
32
+ identity_id: testUuid(i * 10 + 4),
33
+ ip_address: '127.0.0.1',
34
+ user_agent: 'test-agent',
32
35
  ...row,
33
36
  })
34
37
 
@@ -56,6 +59,9 @@ describe('webhook request', () => {
56
59
  meta: {
57
60
  eventId: testUuid(1),
58
61
  transactionId: testUuid(2),
62
+ identityId: testUuid(4),
63
+ ipAddress: '127.0.0.1',
64
+ userAgent: 'test-agent',
59
65
  createdAt: now.toISOString(),
60
66
  lastStateChange: now.toISOString(),
61
67
  numRetries: 0,
@@ -104,6 +110,9 @@ describe('webhook request', () => {
104
110
  meta: {
105
111
  eventId: testUuid(1),
106
112
  transactionId: testUuid(2),
113
+ identityId: testUuid(4),
114
+ ipAddress: '127.0.0.1',
115
+ userAgent: 'test-agent',
107
116
  createdAt: now.toISOString(),
108
117
  lastStateChange: now.toISOString(),
109
118
  numRetries: 0,