reactor_sdk 0.1.0

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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +19 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +281 -0
  5. data/lib/reactor_sdk/authentication.rb +137 -0
  6. data/lib/reactor_sdk/client.rb +186 -0
  7. data/lib/reactor_sdk/configuration.rb +102 -0
  8. data/lib/reactor_sdk/connection.rb +342 -0
  9. data/lib/reactor_sdk/endpoints/app_configurations.rb +42 -0
  10. data/lib/reactor_sdk/endpoints/audit_events.rb +64 -0
  11. data/lib/reactor_sdk/endpoints/base_endpoint.rb +207 -0
  12. data/lib/reactor_sdk/endpoints/builds.rb +62 -0
  13. data/lib/reactor_sdk/endpoints/callbacks.rb +38 -0
  14. data/lib/reactor_sdk/endpoints/companies.rb +42 -0
  15. data/lib/reactor_sdk/endpoints/data_elements.rb +251 -0
  16. data/lib/reactor_sdk/endpoints/environments.rb +174 -0
  17. data/lib/reactor_sdk/endpoints/extension_package_usage_authorizations.rb +51 -0
  18. data/lib/reactor_sdk/endpoints/extension_packages.rb +63 -0
  19. data/lib/reactor_sdk/endpoints/extensions.rb +181 -0
  20. data/lib/reactor_sdk/endpoints/hosts.rb +101 -0
  21. data/lib/reactor_sdk/endpoints/libraries.rb +872 -0
  22. data/lib/reactor_sdk/endpoints/notes.rb +11 -0
  23. data/lib/reactor_sdk/endpoints/profiles.rb +14 -0
  24. data/lib/reactor_sdk/endpoints/properties.rb +123 -0
  25. data/lib/reactor_sdk/endpoints/revisions.rb +102 -0
  26. data/lib/reactor_sdk/endpoints/rule_components.rb +218 -0
  27. data/lib/reactor_sdk/endpoints/rules.rb +240 -0
  28. data/lib/reactor_sdk/endpoints/search.rb +23 -0
  29. data/lib/reactor_sdk/endpoints/secrets.rb +76 -0
  30. data/lib/reactor_sdk/error.rb +115 -0
  31. data/lib/reactor_sdk/library_comparison_builder.rb +74 -0
  32. data/lib/reactor_sdk/library_snapshot_builder.rb +66 -0
  33. data/lib/reactor_sdk/paginator.rb +92 -0
  34. data/lib/reactor_sdk/rate_limiter.rb +96 -0
  35. data/lib/reactor_sdk/reference_extractor.rb +34 -0
  36. data/lib/reactor_sdk/resource_metadata.rb +73 -0
  37. data/lib/reactor_sdk/resource_normalizer.rb +90 -0
  38. data/lib/reactor_sdk/resources/app_configuration.rb +20 -0
  39. data/lib/reactor_sdk/resources/audit_event.rb +45 -0
  40. data/lib/reactor_sdk/resources/base_resource.rb +181 -0
  41. data/lib/reactor_sdk/resources/build.rb +64 -0
  42. data/lib/reactor_sdk/resources/callback.rb +16 -0
  43. data/lib/reactor_sdk/resources/company.rb +38 -0
  44. data/lib/reactor_sdk/resources/comprehensive_data_element.rb +28 -0
  45. data/lib/reactor_sdk/resources/comprehensive_extension.rb +30 -0
  46. data/lib/reactor_sdk/resources/comprehensive_resource.rb +31 -0
  47. data/lib/reactor_sdk/resources/comprehensive_rule.rb +26 -0
  48. data/lib/reactor_sdk/resources/comprehensive_upstream_chain.rb +50 -0
  49. data/lib/reactor_sdk/resources/comprehensive_upstream_chain_entry.rb +34 -0
  50. data/lib/reactor_sdk/resources/data_element.rb +108 -0
  51. data/lib/reactor_sdk/resources/environment.rb +45 -0
  52. data/lib/reactor_sdk/resources/extension.rb +66 -0
  53. data/lib/reactor_sdk/resources/extension_package.rb +49 -0
  54. data/lib/reactor_sdk/resources/extension_package_usage_authorization.rb +26 -0
  55. data/lib/reactor_sdk/resources/host.rb +68 -0
  56. data/lib/reactor_sdk/resources/library.rb +67 -0
  57. data/lib/reactor_sdk/resources/library_comparison.rb +72 -0
  58. data/lib/reactor_sdk/resources/library_comparison_entry.rb +144 -0
  59. data/lib/reactor_sdk/resources/library_snapshot.rb +118 -0
  60. data/lib/reactor_sdk/resources/library_snapshot_extension_index.rb +70 -0
  61. data/lib/reactor_sdk/resources/library_snapshot_index.rb +169 -0
  62. data/lib/reactor_sdk/resources/library_with_resources.rb +194 -0
  63. data/lib/reactor_sdk/resources/note.rb +37 -0
  64. data/lib/reactor_sdk/resources/profile.rb +22 -0
  65. data/lib/reactor_sdk/resources/property.rb +44 -0
  66. data/lib/reactor_sdk/resources/revision.rb +156 -0
  67. data/lib/reactor_sdk/resources/rule.rb +44 -0
  68. data/lib/reactor_sdk/resources/rule_component.rb +101 -0
  69. data/lib/reactor_sdk/resources/search_results.rb +28 -0
  70. data/lib/reactor_sdk/resources/secret.rb +17 -0
  71. data/lib/reactor_sdk/resources/upstream_chain.rb +80 -0
  72. data/lib/reactor_sdk/resources/upstream_chain_entry.rb +55 -0
  73. data/lib/reactor_sdk/response_parser.rb +160 -0
  74. data/lib/reactor_sdk/version.rb +5 -0
  75. data/lib/reactor_sdk.rb +79 -0
  76. data/reactor_sdk.gemspec +70 -0
  77. data/sig/reactor_sdk.rbs +346 -0
  78. metadata +293 -0
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # @file endpoints/base_endpoint.rb
5
+ # @description Base class for all Reactor API endpoint groups.
6
+ #
7
+ # Provides three shared dependencies to every endpoint subclass:
8
+ # - connection for making authenticated HTTP calls
9
+ # - paginator for fetching all pages of list endpoints
10
+ # - parser for converting JSON:API hashes into typed resources
11
+ #
12
+ # Also provides two protected helper methods used by every endpoint:
13
+ # - build_payload builds a JSON:API write payload (POST/PATCH)
14
+ # - build_relationship_payload builds a JSON:API relationship payload
15
+ #
16
+ # Every endpoint class inherits from this and focuses only on its
17
+ # own resource domain — it never touches HTTP or pagination directly.
18
+ #
19
+ # @domain Endpoints
20
+ #
21
+
22
+ module ReactorSDK
23
+ module Endpoints
24
+ class BaseEndpoint
25
+ ##
26
+ # @param connection [ReactorSDK::Connection] Authenticated HTTP client
27
+ # @param paginator [ReactorSDK::Paginator] Handles cursor pagination
28
+ # @param parser [ReactorSDK::ResponseParser] Converts JSON:API to resources
29
+ #
30
+ def initialize(connection:, paginator:, parser:)
31
+ @connection = connection
32
+ @paginator = paginator
33
+ @parser = parser
34
+ end
35
+
36
+ protected
37
+
38
+ ##
39
+ # Builds a JSON:API compliant request payload for create and update requests.
40
+ #
41
+ # POST (create) — omit the id parameter:
42
+ # build_payload("properties", { name: "My Site", platform: "web" })
43
+ # => { data: { type: "properties", attributes: { name: "My Site", platform: "web" } } }
44
+ #
45
+ # PATCH (update) — include the id parameter:
46
+ # build_payload("properties", { name: "New Name" }, id: "PR123")
47
+ # => { data: { id: "PR123", type: "properties", attributes: { name: "New Name" } } }
48
+ #
49
+ # @param type [String] JSON:API resource type (e.g. "properties")
50
+ # @param attributes [Hash] Resource attribute values to send
51
+ # @param id [String, nil] Resource ID — required for PATCH, omit for POST
52
+ # @param relationships [Hash, nil] JSON:API relationships payload
53
+ # @param meta [Hash, nil] JSON:API meta payload
54
+ # @return [Hash] Correctly structured JSON:API payload
55
+ #
56
+ def build_payload(type, attributes, id: nil, relationships: nil, meta: nil)
57
+ data = { type: type, attributes: attributes }
58
+ data[:id] = id if id
59
+ data[:relationships] = relationships if relationships
60
+ data[:meta] = meta if meta
61
+ { data: data }
62
+ end
63
+
64
+ ##
65
+ # Builds a JSON:API relationship payload for associating resources.
66
+ # Used when adding rules, data elements, or extensions to a library.
67
+ #
68
+ # @example Add two rules to a library
69
+ # build_relationship_payload("rules", ["RL123", "RL456"])
70
+ # => { data: [{ id: "RL123", type: "rules" }, { id: "RL456", type: "rules" }] }
71
+ #
72
+ # @param type [String] JSON:API resource type
73
+ # @param ids [Array<String>] Array of Adobe resource IDs to associate
74
+ # @return [Hash] JSON:API relationship payload
75
+ #
76
+ def build_relationship_payload(type, ids)
77
+ {
78
+ data: Array(ids).map { |id| { id: id, type: type } }
79
+ }
80
+ end
81
+
82
+ ##
83
+ # Fetches and parses a single related resource.
84
+ #
85
+ # @param path [String]
86
+ # @param resource_class [Class]
87
+ # @param params [Hash]
88
+ # @return [Object]
89
+ #
90
+ def fetch_resource(path, resource_class, params: {})
91
+ response = @connection.get(path, params: params)
92
+ @parser.parse(response['data'], resource_class, response: response)
93
+ end
94
+
95
+ ##
96
+ # Fetches and parses a related resource collection.
97
+ #
98
+ # @param path [String]
99
+ # @param resource_class [Class]
100
+ # @param params [Hash]
101
+ # @return [Array<Object>]
102
+ #
103
+ def list_resources(path, resource_class, params: {})
104
+ records = @paginator.all(path, params: params)
105
+ @parser.parse_many(records, resource_class)
106
+ end
107
+
108
+ ##
109
+ # Fetches and parses a heterogeneous resource collection using
110
+ # each record's JSON:API type to choose the SDK class.
111
+ #
112
+ # @param path [String]
113
+ # @param params [Hash]
114
+ # @return [Array<ReactorSDK::Resources::BaseResource>]
115
+ #
116
+ def list_resources_auto(path, params: {})
117
+ records = @paginator.all(path, params: params)
118
+ @parser.parse_many_auto(records)
119
+ end
120
+
121
+ ##
122
+ # Creates and parses a single resource.
123
+ #
124
+ # @param path [String]
125
+ # @param type [String]
126
+ # @param resource_class [Class]
127
+ # @param attributes [Hash]
128
+ # @param relationships [Hash, nil]
129
+ # @param meta [Hash, nil]
130
+ # @return [Object]
131
+ #
132
+ def create_resource(path, type, resource_class, attributes:, relationships: nil, meta: nil)
133
+ payload = build_payload(type, attributes, relationships: relationships, meta: meta)
134
+ response = @connection.post(path, payload)
135
+ @parser.parse(response['data'], resource_class, response: response)
136
+ end
137
+
138
+ ##
139
+ # Updates and parses a single resource.
140
+ #
141
+ # @param path [String]
142
+ # @param id [String]
143
+ # @param type [String]
144
+ # @param resource_class [Class]
145
+ # @param attributes [Hash]
146
+ # @param relationships [Hash, nil]
147
+ # @param meta [Hash, nil]
148
+ # @return [Object]
149
+ #
150
+ def update_resource(path, id, type, resource_class, attributes:, relationships: nil, meta: nil)
151
+ payload = build_payload(
152
+ type,
153
+ attributes,
154
+ id: id,
155
+ relationships: relationships,
156
+ meta: meta
157
+ )
158
+ response = @connection.patch(path, payload)
159
+ @parser.parse(response['data'], resource_class, response: response)
160
+ end
161
+
162
+ ##
163
+ # Deletes a resource and normalizes the nil return value.
164
+ #
165
+ # @param path [String]
166
+ # @return [nil]
167
+ #
168
+ def delete_resource(path)
169
+ @connection.delete(path)
170
+ nil
171
+ end
172
+
173
+ ##
174
+ # Fetches raw relationship linkage data.
175
+ #
176
+ # @param path [String]
177
+ # @param params [Hash]
178
+ # @return [Hash, Array<Hash>, nil]
179
+ #
180
+ def fetch_relationship(path, params: {})
181
+ response = @connection.get(path, params: params)
182
+ response['data']
183
+ end
184
+
185
+ ##
186
+ # Creates a note under a notable resource.
187
+ #
188
+ # @param path [String]
189
+ # @param text [String]
190
+ # @return [ReactorSDK::Resources::Note]
191
+ #
192
+ def create_note_for_path(path, text)
193
+ create_resource(path, 'notes', Resources::Note, attributes: { text: text })
194
+ end
195
+
196
+ ##
197
+ # Lists note resources for a notable resource path.
198
+ #
199
+ # @param path [String]
200
+ # @return [Array<ReactorSDK::Resources::Note>]
201
+ #
202
+ def list_notes_for_path(path)
203
+ list_resources(path, Resources::Note)
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # @file endpoints/builds.rb
5
+ # @description Endpoint group for Adobe Launch Build resources.
6
+ #
7
+ # Builds are the compiled output of a library — the JavaScript bundle
8
+ # deployed to an environment. LaunchGuard polls build status after
9
+ # triggering a build to detect success or failure before notifying users.
10
+ #
11
+ # @domain Endpoints
12
+ # @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/builds/
13
+ #
14
+
15
+ module ReactorSDK
16
+ module Endpoints
17
+ class Builds < BaseEndpoint
18
+ ##
19
+ # Retrieves a single build by its Adobe ID.
20
+ # Used to poll build status after a build is triggered.
21
+ #
22
+ # @param build_id [String] Adobe build ID (format: "BL" + hex string)
23
+ # @return [ReactorSDK::Resources::Build]
24
+ # @raise [ReactorSDK::ResourceNotFoundError] if the build does not exist
25
+ #
26
+ def find(build_id)
27
+ response = @connection.get("/builds/#{build_id}")
28
+ @parser.parse(response['data'], Resources::Build)
29
+ end
30
+
31
+ ##
32
+ # Lists all builds for a given library.
33
+ # Follows pagination automatically — returns all builds.
34
+ #
35
+ # @param library_id [String] Adobe library ID
36
+ # @return [Array<ReactorSDK::Resources::Build>]
37
+ # @raise [ReactorSDK::ResourceNotFoundError] if the library does not exist
38
+ #
39
+ def list_for_library(library_id)
40
+ records = @paginator.all("/libraries/#{library_id}/builds")
41
+ records.map { |r| @parser.parse(r, Resources::Build) }
42
+ end
43
+
44
+ ##
45
+ # Republishes an existing build.
46
+ #
47
+ # @param build_id [String]
48
+ # @return [ReactorSDK::Resources::Build]
49
+ #
50
+ def republish(build_id)
51
+ update_resource(
52
+ "/builds/#{build_id}",
53
+ build_id,
54
+ 'builds',
55
+ Resources::Build,
56
+ attributes: {},
57
+ meta: { action: 'republish' }
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ReactorSDK
4
+ module Endpoints
5
+ class Callbacks < BaseEndpoint
6
+ def list_for_property(property_id)
7
+ list_resources("/properties/#{property_id}/callbacks", Resources::Callback)
8
+ end
9
+
10
+ def find(callback_id)
11
+ fetch_resource("/callbacks/#{callback_id}", Resources::Callback)
12
+ end
13
+
14
+ def create(property_id:, attributes:)
15
+ create_resource(
16
+ "/properties/#{property_id}/callbacks",
17
+ 'callbacks',
18
+ Resources::Callback,
19
+ attributes: attributes
20
+ )
21
+ end
22
+
23
+ def update(callback_id, attributes)
24
+ update_resource(
25
+ "/callbacks/#{callback_id}",
26
+ callback_id,
27
+ 'callbacks',
28
+ Resources::Callback,
29
+ attributes: attributes
30
+ )
31
+ end
32
+
33
+ def delete(callback_id)
34
+ delete_resource("/callbacks/#{callback_id}")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # @file endpoints/companies.rb
5
+ # @description Endpoint group for Adobe Launch Company resources.
6
+ #
7
+ # A Company maps to an Adobe IMS Organisation and is the top-level
8
+ # container for all properties. Most orgs have exactly one company.
9
+ #
10
+ # @domain Endpoints
11
+ # @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/companies/
12
+ #
13
+
14
+ module ReactorSDK
15
+ module Endpoints
16
+ class Companies < BaseEndpoint
17
+ ##
18
+ # Lists all companies accessible to the authenticated token.
19
+ # Follows pagination automatically — returns all companies.
20
+ #
21
+ # @return [Array<ReactorSDK::Resources::Company>]
22
+ # @raise [ReactorSDK::AuthorizationError] if the token lacks access
23
+ #
24
+ def list
25
+ records = @paginator.all('/companies')
26
+ records.map { |r| @parser.parse(r, Resources::Company) }
27
+ end
28
+
29
+ ##
30
+ # Retrieves a single company by its Adobe ID.
31
+ #
32
+ # @param company_id [String] Adobe company ID (format: "CO" + hex string)
33
+ # @return [ReactorSDK::Resources::Company]
34
+ # @raise [ReactorSDK::ResourceNotFoundError] if the company does not exist
35
+ #
36
+ def find(company_id)
37
+ response = @connection.get("/companies/#{company_id}")
38
+ @parser.parse(response['data'], Resources::Company)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # @file endpoints/data_elements.rb
5
+ # @description Endpoint group for Adobe Launch Data Element resources.
6
+ #
7
+ # Data elements are reusable values that can be referenced throughout
8
+ # rules and other data elements. They form the data layer of Adobe Launch.
9
+ #
10
+ # Creating a data element requires an extension relationship in the payload.
11
+ # Fetch the property's extensions first to obtain the extension_id.
12
+ #
13
+ # Important: data elements must be revised before they can be added to
14
+ # a library. Call revise(data_element_id) before libraries.add_data_elements.
15
+ #
16
+ # @domain Endpoints
17
+ # @see https://developer.adobe.com/experience-platform/documentation/tags/api/endpoints/data-elements/
18
+ #
19
+
20
+ module ReactorSDK
21
+ module Endpoints
22
+ class DataElements < BaseEndpoint
23
+ ##
24
+ # Lists all data elements for a given property.
25
+ # Follows pagination automatically — returns all data elements.
26
+ #
27
+ # @param property_id [String] Adobe property ID
28
+ # @return [Array<ReactorSDK::Resources::DataElement>]
29
+ # @raise [ReactorSDK::ResourceNotFoundError] if the property does not exist
30
+ #
31
+ def list_for_property(property_id)
32
+ records = @paginator.all("/properties/#{property_id}/data_elements")
33
+ records.map { |r| @parser.parse(r, Resources::DataElement) }
34
+ end
35
+
36
+ ##
37
+ # Retrieves a single data element by its Adobe ID.
38
+ #
39
+ # @param data_element_id [String] Adobe data element ID (format: "DE" + hex)
40
+ # @return [ReactorSDK::Resources::DataElement]
41
+ # @raise [ReactorSDK::ResourceNotFoundError] if the data element does not exist
42
+ #
43
+ def find(data_element_id)
44
+ response = @connection.get("/data_elements/#{data_element_id}")
45
+ @parser.parse(response['data'], Resources::DataElement)
46
+ end
47
+
48
+ ##
49
+ # Creates a new data element within a property.
50
+ #
51
+ # Requires an extension_id — fetch the property's extensions first:
52
+ # extensions = client.extensions.list_for_property(property_id)
53
+ # extension_id = extensions.find { |e| e.delegate_descriptor_id.start_with?("core::") }.id
54
+ #
55
+ # The settings field must be a JSON-encoded string matching the delegate
56
+ # schema. For Core custom code the settings schema only allows "source" —
57
+ # do NOT include "language":
58
+ # settings: JSON.generate({ source: "return document.title;" })
59
+ #
60
+ # @param property_id [String] Adobe property ID
61
+ # @param name [String] Display name
62
+ # @param delegate_descriptor_id [String] Extension delegate identifier
63
+ # @param settings [String] JSON-encoded settings string
64
+ # @param extension_id [String] Adobe extension ID
65
+ # @param enabled [Boolean] Whether enabled (default: true)
66
+ # @return [ReactorSDK::Resources::DataElement]
67
+ # @raise [ReactorSDK::UnprocessableEntityError] if attributes are invalid
68
+ #
69
+ def create(
70
+ property_id:,
71
+ name:,
72
+ delegate_descriptor_id:,
73
+ settings:,
74
+ extension_id:,
75
+ enabled: true
76
+ )
77
+ payload = build_data_element_payload(
78
+ name, delegate_descriptor_id, settings, extension_id, enabled
79
+ )
80
+ response = @connection.post("/properties/#{property_id}/data_elements", payload)
81
+ @parser.parse(response['data'], Resources::DataElement)
82
+ end
83
+
84
+ ##
85
+ # Updates an existing data element.
86
+ #
87
+ # @param data_element_id [String] Adobe data element ID
88
+ # @param attributes [Hash] Fields to update
89
+ # @return [ReactorSDK::Resources::DataElement]
90
+ # @raise [ReactorSDK::ResourceNotFoundError] if the data element does not exist
91
+ #
92
+ def update(data_element_id, attributes)
93
+ payload = build_payload('data_elements', attributes, id: data_element_id)
94
+ response = @connection.patch("/data_elements/#{data_element_id}", payload)
95
+ @parser.parse(response['data'], Resources::DataElement)
96
+ end
97
+
98
+ ##
99
+ # Revises a data element so it can be added to a library.
100
+ #
101
+ # Adobe Launch requires every resource to be explicitly revised before
102
+ # it can be added to a library.
103
+ #
104
+ # Always call revise after create or update, before libraries.add_data_elements.
105
+ #
106
+ # @param data_element_id [String] Adobe data element ID
107
+ # @return [ReactorSDK::Resources::DataElement] The revised data element
108
+ # @raise [ReactorSDK::ResourceNotFoundError] if the data element does not exist
109
+ #
110
+ def revise(data_element_id)
111
+ payload = {
112
+ data: {
113
+ id: data_element_id,
114
+ type: 'data_elements',
115
+ meta: { action: 'revise' }
116
+ }
117
+ }
118
+ response = @connection.patch("/data_elements/#{data_element_id}", payload)
119
+ @parser.parse(response['data'], Resources::DataElement)
120
+ end
121
+
122
+ ##
123
+ # Deletes a data element permanently.
124
+ #
125
+ # @param data_element_id [String] Adobe data element ID
126
+ # @return [nil]
127
+ # @raise [ReactorSDK::ResourceNotFoundError] if the data element does not exist
128
+ #
129
+ def delete(data_element_id)
130
+ @connection.delete("/data_elements/#{data_element_id}")
131
+ nil
132
+ end
133
+
134
+ ##
135
+ # Resolves the data element across the ordered upstream library chain.
136
+ #
137
+ # @param data_element_or_id [String, ReactorSDK::Resources::DataElement]
138
+ # @param library_id [String] Adobe library ID used as the comparison root
139
+ # @param property_id [String] Adobe property ID containing the library chain
140
+ # @return [ReactorSDK::Resources::UpstreamChain]
141
+ #
142
+ def upstream_chain(data_element_or_id, library_id:, property_id:)
143
+ libraries_endpoint.upstream_chain_for_resource(
144
+ data_element_or_id,
145
+ library_id: library_id,
146
+ property_id: property_id,
147
+ resource_type: 'data_elements'
148
+ )
149
+ end
150
+
151
+ ##
152
+ # Fetches the data element from a library-context review snapshot
153
+ # together with impact analysis and normalized review payload.
154
+ #
155
+ # @param data_element_id [String]
156
+ # @param library_id [String]
157
+ # @param property_id [String]
158
+ # @return [ReactorSDK::Resources::ComprehensiveDataElement]
159
+ #
160
+ def find_comprehensive(data_element_id, library_id:, property_id:)
161
+ snapshot = libraries_endpoint.find_snapshot(library_id, property_id: property_id)
162
+ comprehensive = snapshot.comprehensive_resource(data_element_id, resource_type: 'data_elements')
163
+ unless comprehensive
164
+ raise ReactorSDK::ResourceNotFoundError,
165
+ "Data element #{data_element_id} was not found in library #{library_id}"
166
+ end
167
+
168
+ comprehensive
169
+ end
170
+
171
+ ##
172
+ # Resolves the data element across the ordered upstream chain using
173
+ # snapshot-aware comprehensive review objects.
174
+ #
175
+ # @param data_element_or_id [String, ReactorSDK::Resources::DataElement]
176
+ # @param library_id [String]
177
+ # @param property_id [String]
178
+ # @return [ReactorSDK::Resources::ComprehensiveUpstreamChain]
179
+ #
180
+ def comprehensive_upstream_chain(data_element_or_id, library_id:, property_id:)
181
+ libraries_endpoint.comprehensive_upstream_chain_for_resource(
182
+ data_element_or_id,
183
+ library_id: library_id,
184
+ property_id: property_id,
185
+ resource_type: 'data_elements'
186
+ )
187
+ end
188
+
189
+ ##
190
+ # Lists notes attached to a data element.
191
+ #
192
+ # @param data_element_id [String]
193
+ # @return [Array<ReactorSDK::Resources::Note>]
194
+ #
195
+ def list_notes(data_element_id)
196
+ list_notes_for_path("/data_elements/#{data_element_id}/notes")
197
+ end
198
+
199
+ ##
200
+ # Creates a note on a data element.
201
+ #
202
+ # @param data_element_id [String]
203
+ # @param text [String]
204
+ # @return [ReactorSDK::Resources::Note]
205
+ #
206
+ def create_note(data_element_id, text)
207
+ create_note_for_path("/data_elements/#{data_element_id}/notes", text)
208
+ end
209
+
210
+ private
211
+
212
+ ##
213
+ # Builds the JSON:API payload for data element creation.
214
+ # Includes the required extension relationship.
215
+ #
216
+ # @param name [String] Display name
217
+ # @param delegate_descriptor_id [String] Delegate identifier
218
+ # @param settings [String] JSON-encoded settings
219
+ # @param extension_id [String] Extension providing the delegate
220
+ # @param enabled [Boolean] Whether enabled
221
+ # @return [Hash] JSON:API compliant payload
222
+ #
223
+ def build_data_element_payload(name, delegate_descriptor_id, settings, extension_id, enabled)
224
+ {
225
+ data: {
226
+ type: 'data_elements',
227
+ attributes: {
228
+ name: name,
229
+ delegate_descriptor_id: delegate_descriptor_id,
230
+ settings: settings,
231
+ enabled: enabled
232
+ },
233
+ relationships: {
234
+ extension: {
235
+ data: { id: extension_id, type: 'extensions' }
236
+ }
237
+ }
238
+ }
239
+ }
240
+ end
241
+
242
+ def libraries_endpoint
243
+ @libraries_endpoint ||= Endpoints::Libraries.new(
244
+ connection: @connection,
245
+ paginator: @paginator,
246
+ parser: @parser
247
+ )
248
+ end
249
+ end
250
+ end
251
+ end