@mondaydotcomorg/monday-authorization 3.5.0-upgrade-observability-kit-2ebbd01 → 3.5.1-feat-shaime-support-entity-attributes-in-authorization-sdk-59162a3

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 (121) hide show
  1. package/dist/attributions-service.d.ts.map +1 -1
  2. package/dist/attributions-service.js +1 -0
  3. package/dist/authorization-attributes-ms-service.d.ts +67 -0
  4. package/dist/authorization-attributes-ms-service.d.ts.map +1 -0
  5. package/dist/authorization-attributes-ms-service.js +256 -0
  6. package/dist/authorization-attributes-sns-service.d.ts +85 -0
  7. package/dist/authorization-attributes-sns-service.d.ts.map +1 -0
  8. package/dist/authorization-attributes-sns-service.js +203 -0
  9. package/dist/base-attribute-assignment.d.ts +18 -0
  10. package/dist/base-attribute-assignment.d.ts.map +1 -0
  11. package/dist/base-attribute-assignment.js +43 -0
  12. package/dist/clients/graph-api.d.ts.map +1 -1
  13. package/dist/clients/graph-api.js +1 -0
  14. package/dist/constants/sns.d.ts +12 -2
  15. package/dist/constants/sns.d.ts.map +1 -1
  16. package/dist/constants/sns.js +22 -2
  17. package/dist/constants.d.ts +3 -0
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/constants.js +4 -0
  20. package/dist/entity-attribute-assignment.d.ts +24 -0
  21. package/dist/entity-attribute-assignment.d.ts.map +1 -0
  22. package/dist/entity-attribute-assignment.js +36 -0
  23. package/dist/entity-attributes-constants.d.ts +7 -0
  24. package/dist/entity-attributes-constants.d.ts.map +1 -0
  25. package/dist/entity-attributes-constants.js +9 -0
  26. package/dist/errors/argument-error.d.ts +4 -0
  27. package/dist/errors/argument-error.d.ts.map +1 -0
  28. package/dist/errors/argument-error.js +11 -0
  29. package/dist/esm/attributions-service.d.ts.map +1 -1
  30. package/dist/esm/attributions-service.mjs +1 -0
  31. package/dist/esm/authorization-attributes-ms-service.d.ts +67 -0
  32. package/dist/esm/authorization-attributes-ms-service.d.ts.map +1 -0
  33. package/dist/esm/authorization-attributes-ms-service.mjs +254 -0
  34. package/dist/esm/authorization-attributes-sns-service.d.ts +85 -0
  35. package/dist/esm/authorization-attributes-sns-service.d.ts.map +1 -0
  36. package/dist/esm/authorization-attributes-sns-service.mjs +197 -0
  37. package/dist/esm/base-attribute-assignment.d.ts +18 -0
  38. package/dist/esm/base-attribute-assignment.d.ts.map +1 -0
  39. package/dist/esm/base-attribute-assignment.mjs +41 -0
  40. package/dist/esm/clients/graph-api.d.ts.map +1 -1
  41. package/dist/esm/clients/graph-api.mjs +2 -1
  42. package/dist/esm/constants/sns.d.ts +12 -2
  43. package/dist/esm/constants/sns.d.ts.map +1 -1
  44. package/dist/esm/constants/sns.mjs +17 -3
  45. package/dist/esm/constants.d.ts +3 -0
  46. package/dist/esm/constants.d.ts.map +1 -1
  47. package/dist/esm/constants.mjs +5 -1
  48. package/dist/esm/entity-attribute-assignment.d.ts +24 -0
  49. package/dist/esm/entity-attribute-assignment.d.ts.map +1 -0
  50. package/dist/esm/entity-attribute-assignment.mjs +34 -0
  51. package/dist/esm/entity-attributes-constants.d.ts +7 -0
  52. package/dist/esm/entity-attributes-constants.d.ts.map +1 -0
  53. package/dist/esm/entity-attributes-constants.mjs +7 -0
  54. package/dist/esm/errors/argument-error.d.ts +4 -0
  55. package/dist/esm/errors/argument-error.d.ts.map +1 -0
  56. package/dist/esm/errors/argument-error.mjs +9 -0
  57. package/dist/esm/index.d.ts +9 -1
  58. package/dist/esm/index.d.ts.map +1 -1
  59. package/dist/esm/index.mjs +7 -1
  60. package/dist/esm/resource-attribute-assignment.d.ts +24 -0
  61. package/dist/esm/resource-attribute-assignment.d.ts.map +1 -0
  62. package/dist/esm/resource-attribute-assignment.mjs +34 -0
  63. package/dist/esm/resource-attributes-constants.d.ts +25 -0
  64. package/dist/esm/resource-attributes-constants.d.ts.map +1 -0
  65. package/dist/esm/resource-attributes-constants.mjs +25 -0
  66. package/dist/esm/roles-service.d.ts +1 -1
  67. package/dist/esm/roles-service.mjs +1 -1
  68. package/dist/esm/types/authorization-attributes-contracts.d.ts +36 -4
  69. package/dist/esm/types/authorization-attributes-contracts.d.ts.map +1 -1
  70. package/dist/esm/types/authorization-attributes-contracts.mjs +6 -1
  71. package/dist/esm/utils/path-utils.d.ts +8 -0
  72. package/dist/esm/utils/path-utils.d.ts.map +1 -0
  73. package/dist/esm/utils/path-utils.mjs +15 -0
  74. package/dist/esm/utils/validation.d.ts +45 -0
  75. package/dist/esm/utils/validation.d.ts.map +1 -0
  76. package/dist/esm/utils/validation.mjs +75 -0
  77. package/dist/index.d.ts +9 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +15 -2
  80. package/dist/resource-attribute-assignment.d.ts +24 -0
  81. package/dist/resource-attribute-assignment.d.ts.map +1 -0
  82. package/dist/resource-attribute-assignment.js +36 -0
  83. package/dist/resource-attributes-constants.d.ts +25 -0
  84. package/dist/resource-attributes-constants.d.ts.map +1 -0
  85. package/dist/resource-attributes-constants.js +28 -0
  86. package/dist/roles-service.d.ts +1 -1
  87. package/dist/roles-service.js +1 -1
  88. package/dist/types/authorization-attributes-contracts.d.ts +36 -4
  89. package/dist/types/authorization-attributes-contracts.d.ts.map +1 -1
  90. package/dist/types/authorization-attributes-contracts.js +5 -0
  91. package/dist/utils/path-utils.d.ts +8 -0
  92. package/dist/utils/path-utils.d.ts.map +1 -0
  93. package/dist/utils/path-utils.js +17 -0
  94. package/dist/utils/validation.d.ts +45 -0
  95. package/dist/utils/validation.d.ts.map +1 -0
  96. package/dist/utils/validation.js +77 -0
  97. package/package.json +2 -2
  98. package/src/attributions-service.ts +4 -0
  99. package/src/authorization-attributes-ms-service.ts +400 -0
  100. package/src/authorization-attributes-sns-service.ts +290 -0
  101. package/src/base-attribute-assignment.ts +56 -0
  102. package/src/clients/graph-api.ts +2 -1
  103. package/src/constants/sns.ts +19 -2
  104. package/src/constants.ts +4 -0
  105. package/src/entity-attribute-assignment.ts +43 -0
  106. package/src/entity-attributes-constants.ts +7 -0
  107. package/src/errors/argument-error.ts +7 -0
  108. package/src/index.ts +18 -1
  109. package/src/resource-attribute-assignment.ts +43 -0
  110. package/src/resource-attributes-constants.ts +26 -0
  111. package/src/roles-service.ts +1 -1
  112. package/src/types/authorization-attributes-contracts.ts +56 -3
  113. package/src/utils/path-utils.ts +14 -0
  114. package/src/utils/validation.ts +84 -0
  115. package/dist/authorization-attributes-service.d.ts +0 -54
  116. package/dist/authorization-attributes-service.d.ts.map +0 -1
  117. package/dist/authorization-attributes-service.js +0 -186
  118. package/dist/esm/authorization-attributes-service.d.ts +0 -54
  119. package/dist/esm/authorization-attributes-service.d.ts.map +0 -1
  120. package/dist/esm/authorization-attributes-service.mjs +0 -180
  121. package/src/authorization-attributes-service.ts +0 -234
@@ -5,3 +5,8 @@ exports.ResourceAttributeOperationEnum = void 0;
5
5
  ResourceAttributeOperationEnum["UPSERT"] = "upsert";
6
6
  ResourceAttributeOperationEnum["DELETE"] = "delete";
7
7
  })(exports.ResourceAttributeOperationEnum || (exports.ResourceAttributeOperationEnum = {}));
8
+ exports.EntityAttributeOperationEnum = void 0;
9
+ (function (EntityAttributeOperationEnum) {
10
+ EntityAttributeOperationEnum["UPSERT"] = "upsert";
11
+ EntityAttributeOperationEnum["DELETE"] = "delete";
12
+ })(exports.EntityAttributeOperationEnum || (exports.EntityAttributeOperationEnum = {}));
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Replaces path template parameters with actual values
3
+ * @param template Path template with placeholders like {accountId}
4
+ * @param params Object with parameter names and values
5
+ * @returns Path with all placeholders replaced
6
+ */
7
+ export declare function replacePathParams(template: string, params: Record<string, string | number>): string;
8
+ //# sourceMappingURL=path-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAMnG"}
@@ -0,0 +1,17 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ /**
4
+ * Replaces path template parameters with actual values
5
+ * @param template Path template with placeholders like {accountId}
6
+ * @param params Object with parameter names and values
7
+ * @returns Path with all placeholders replaced
8
+ */
9
+ function replacePathParams(template, params) {
10
+ let path = template;
11
+ for (const [key, value] of Object.entries(params)) {
12
+ path = path.replace(`{${key}}`, String(value));
13
+ }
14
+ return path;
15
+ }
16
+
17
+ exports.replacePathParams = replacePathParams;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Utility class for common validation operations
3
+ */
4
+ export declare class ValidationUtils {
5
+ /**
6
+ * Validates that a value is an integer
7
+ * @param value The value to validate
8
+ * @param fieldName The name of the field for error messages
9
+ * @throws ArgumentError if value is not an integer
10
+ */
11
+ static validateInteger(value: any, fieldName: string): void;
12
+ /**
13
+ * Validates that a value is a string
14
+ * @param value The value to validate
15
+ * @param fieldName The name of the field for error messages
16
+ * @throws ArgumentError if value is not a string
17
+ */
18
+ static validateString(value: any, fieldName: string): void;
19
+ /**
20
+ * Validates that a value is an array and optionally checks minimum length
21
+ * @param value The value to validate
22
+ * @param fieldName The name of the field for error messages
23
+ * @param minLength Minimum required length (default: 0)
24
+ * @returns The validated array
25
+ * @throws ArgumentError if value is not an array or doesn't meet minimum length
26
+ */
27
+ static validateArray<T>(value: any, fieldName: string, minLength?: number): T[];
28
+ /**
29
+ * Validates that a value is one of the allowed enum values
30
+ * @param value The value to validate
31
+ * @param validValues Array of valid values
32
+ * @param fieldName The name of the field for error messages
33
+ * @returns The validated value as the enum type
34
+ * @throws ArgumentError if value is not in validValues
35
+ */
36
+ static validateEnum<T extends string>(value: string, validValues: readonly T[], fieldName: string): T;
37
+ /**
38
+ * Validates that all items in an array are strings
39
+ * @param value Array to validate
40
+ * @param fieldName The name of the field for error messages
41
+ * @throws ArgumentError if any item is not a string
42
+ */
43
+ static validateStringArray(value: any[], fieldName: string): void;
44
+ }
45
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,eAAe;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAM3D;;;;;OAKG;IACH,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAM1D;;;;;;;OAOG;IACH,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,CAAC,EAAE;IAU1E;;;;;;;OAOG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,MAAM,EAClC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,SAAS,CAAC,EAAE,EACzB,SAAS,EAAE,MAAM,GAChB,CAAC;IASJ;;;;;OAKG;IACH,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;CAOlE"}
@@ -0,0 +1,77 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const errors_argumentError = require('../errors/argument-error.js');
4
+
5
+ /**
6
+ * Utility class for common validation operations
7
+ */
8
+ class ValidationUtils {
9
+ /**
10
+ * Validates that a value is an integer
11
+ * @param value The value to validate
12
+ * @param fieldName The name of the field for error messages
13
+ * @throws ArgumentError if value is not an integer
14
+ */
15
+ static validateInteger(value, fieldName) {
16
+ if (!Number.isInteger(value)) {
17
+ throw new errors_argumentError.ArgumentError(`${fieldName} must be an integer, got: ${value}`);
18
+ }
19
+ }
20
+ /**
21
+ * Validates that a value is a string
22
+ * @param value The value to validate
23
+ * @param fieldName The name of the field for error messages
24
+ * @throws ArgumentError if value is not a string
25
+ */
26
+ static validateString(value, fieldName) {
27
+ if (typeof value !== 'string') {
28
+ throw new errors_argumentError.ArgumentError(`${fieldName} must be a string, got: ${typeof value}`);
29
+ }
30
+ }
31
+ /**
32
+ * Validates that a value is an array and optionally checks minimum length
33
+ * @param value The value to validate
34
+ * @param fieldName The name of the field for error messages
35
+ * @param minLength Minimum required length (default: 0)
36
+ * @returns The validated array
37
+ * @throws ArgumentError if value is not an array or doesn't meet minimum length
38
+ */
39
+ static validateArray(value, fieldName, minLength = 0) {
40
+ if (!Array.isArray(value)) {
41
+ throw new errors_argumentError.ArgumentError(`${fieldName} must be an array`);
42
+ }
43
+ if (value.length < minLength) {
44
+ throw new errors_argumentError.ArgumentError(`${fieldName} must have at least ${minLength} items`);
45
+ }
46
+ return value;
47
+ }
48
+ /**
49
+ * Validates that a value is one of the allowed enum values
50
+ * @param value The value to validate
51
+ * @param validValues Array of valid values
52
+ * @param fieldName The name of the field for error messages
53
+ * @returns The validated value as the enum type
54
+ * @throws ArgumentError if value is not in validValues
55
+ */
56
+ static validateEnum(value, validValues, fieldName) {
57
+ if (!validValues.includes(value)) {
58
+ throw new errors_argumentError.ArgumentError(`${fieldName} must be one of [${validValues.join(', ')}], got: ${value}`);
59
+ }
60
+ return value;
61
+ }
62
+ /**
63
+ * Validates that all items in an array are strings
64
+ * @param value Array to validate
65
+ * @param fieldName The name of the field for error messages
66
+ * @throws ArgumentError if any item is not a string
67
+ */
68
+ static validateStringArray(value, fieldName) {
69
+ for (let i = 0; i < value.length; i++) {
70
+ if (typeof value[i] !== 'string') {
71
+ throw new errors_argumentError.ArgumentError(`All ${fieldName} must be strings, but item at index ${i} is not`);
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ exports.ValidationUtils = ValidationUtils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/monday-authorization",
3
- "version": "3.5.0-upgrade-observability-kit-2ebbd01",
3
+ "version": "3.5.1-feat-shaime-support-entity-attributes-in-authorization-sdk-59162a3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "BSD-3-Clause",
@@ -25,7 +25,7 @@
25
25
  "@mondaydotcomorg/monday-fetch-api": "^1.0.2",
26
26
  "@mondaydotcomorg/monday-jwt": "^3.0.14",
27
27
  "@mondaydotcomorg/monday-logger": "^4.0.11",
28
- "@mondaydotcomorg/monday-observability-kit": "^1.8.1",
28
+ "@mondaydotcomorg/monday-observability-kit": "^1.5.3",
29
29
  "@mondaydotcomorg/monday-sns": "^1.2.1",
30
30
  "@mondaydotcomorg/trident-backend-api": "^0.24.3",
31
31
  "lodash": "^4.17.21",
@@ -53,6 +53,10 @@ export function getAttributionsFromApi(): { [key: string]: string } {
53
53
  const runtimeAttributionsOutgoingHeaders = runtimeAttributions?.buildOutgoingHeaders('HTTP_INTERNAL');
54
54
 
55
55
  if (!runtimeAttributionsOutgoingHeaders) {
56
+ logger.info(
57
+ { tag: 'authorization-service', runtimeAttributionsOutgoingHeaders },
58
+ 'No runtime attributions outgoing headers'
59
+ );
56
60
  return callerAppNameFromSdk;
57
61
  }
58
62
 
@@ -0,0 +1,400 @@
1
+ import { Api } from '@mondaydotcomorg/trident-backend-api';
2
+ import { signAuthorizationHeader } from '@mondaydotcomorg/monday-jwt';
3
+ import { HttpFetcherError } from '@mondaydotcomorg/monday-fetch-api';
4
+ import { ResourceAttributeAssignment } from './resource-attribute-assignment';
5
+ import { EntityAttributeAssignment } from './entity-attribute-assignment';
6
+ import { EntityType, EntityAttributeKeyType } from './types/authorization-attributes-contracts';
7
+ import { ArgumentError } from './errors/argument-error';
8
+ import { AuthorizationInternalService, logger } from './authorization-internal-service';
9
+ import { getAttributionsFromApi } from './attributions-service';
10
+ import { APP_NAME } from './constants';
11
+ import { ValidationUtils } from './utils/validation';
12
+ import { replacePathParams } from './utils/path-utils';
13
+
14
+ const INTERNAL_APP_NAME = 'internal_ms';
15
+ const UPSERT_RESOURCE_ATTRIBUTES_PATH = '/attributes/{accountId}/resource';
16
+ const DELETE_RESOURCE_ATTRIBUTES_PATH = '/attributes/{accountId}/resource/{resourceType}/{resourceId}';
17
+ const UPSERT_ENTITY_ATTRIBUTES_PATH = '/attributes/{accountId}/entity';
18
+ const DELETE_ENTITY_ATTRIBUTES_PATH = '/attributes/{accountId}/entity/{entityType}/{entityId}';
19
+
20
+ interface Resource {
21
+ resourceType: string;
22
+ resourceId: number;
23
+ }
24
+
25
+ interface UpsertRequestBody {
26
+ resourceAttributeAssignments: {
27
+ resourceId: number;
28
+ resourceType: string;
29
+ key: string;
30
+ value: string;
31
+ }[];
32
+ }
33
+
34
+ interface UpsertEntityRequestBody {
35
+ entityAttributeAssignments: {
36
+ entityId: number;
37
+ entityType: string;
38
+ key: string;
39
+ value: string;
40
+ }[];
41
+ }
42
+
43
+ interface DeleteRequestBody {
44
+ keys: string[];
45
+ }
46
+
47
+ /**
48
+ * Service class for managing resource attributes in the authorization microservice.
49
+ * Provides synchronous HTTP operations to create/update and delete attributes on resources.
50
+ */
51
+ export class AuthorizationAttributesMsService {
52
+ private static LOG_TAG = 'authorization_attributes_ms';
53
+
54
+ /**
55
+ * Gets request headers including Authorization, Content-Type, and optional attribution headers
56
+ */
57
+ private static getRequestHeaders(accountId: number, userId?: number): Record<string, string> {
58
+ const headers: Record<string, string> = {
59
+ 'Content-Type': 'application/json',
60
+ };
61
+
62
+ // Generate Authorization token
63
+ const authToken = signAuthorizationHeader({
64
+ appName: INTERNAL_APP_NAME,
65
+ accountId,
66
+ userId,
67
+ });
68
+ headers.Authorization = authToken;
69
+
70
+ // Add attribution headers if available
71
+ const attributionHeaders = getAttributionsFromApi();
72
+ for (const key in attributionHeaders) {
73
+ if (Object.prototype.hasOwnProperty.call(attributionHeaders, key)) {
74
+ headers[key] = attributionHeaders[key];
75
+ }
76
+ }
77
+
78
+ // Add X-REQUEST-ID if available from context
79
+ try {
80
+ const tridentContext = Api.getPart('context');
81
+ if (tridentContext?.runtimeAttributions) {
82
+ const outgoingHeaders = tridentContext.runtimeAttributions.buildOutgoingHeaders('HTTP_INTERNAL');
83
+ if (outgoingHeaders) {
84
+ const attributionHeadersMap: Record<string, string> = {};
85
+ for (const [key, value] of outgoingHeaders) {
86
+ attributionHeadersMap[key] = value;
87
+ }
88
+ if (attributionHeadersMap['x-request-id']) {
89
+ headers['X-REQUEST-ID'] = attributionHeadersMap['x-request-id'];
90
+ }
91
+ }
92
+ }
93
+ } catch (error) {
94
+ // Silently fail if context is not available
95
+ logger.debug({ tag: this.LOG_TAG, error }, 'Failed to get request ID from context');
96
+ }
97
+
98
+ // Add X-REQUEST-START timestamp
99
+ headers['X-REQUEST-START'] = Math.floor(Date.now() / 1000).toString();
100
+
101
+ return headers;
102
+ }
103
+
104
+ /**
105
+ * Validates that all messages are instances of the specified message class
106
+ */
107
+ private static validateMessages<T>(attributesMessages: T[], messageClass: abstract new (...args: any[]) => T): void {
108
+ if (typeof messageClass !== 'function') {
109
+ throw new ArgumentError('messageClass must be a class/constructor function');
110
+ }
111
+
112
+ ValidationUtils.validateArray(attributesMessages, 'attributesMessages');
113
+
114
+ for (let i = 0; i < attributesMessages.length; i++) {
115
+ if (!(attributesMessages[i] instanceof messageClass)) {
116
+ const className = (messageClass as { name?: string }).name || 'ResourceAttributeAssignment';
117
+ throw new ArgumentError(
118
+ `All attributesMessages must be instances of ${className}, but item at index ${i} is not`
119
+ );
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Handles request errors with consistent logging and error formatting
126
+ */
127
+ private static handleRequestError(err: unknown, methodName: string, context: Record<string, any>): never {
128
+ logger.error(
129
+ {
130
+ tag: this.LOG_TAG,
131
+ method: methodName,
132
+ ...context,
133
+ error: err instanceof Error ? err.message : String(err),
134
+ },
135
+ `Failed in ${methodName}`
136
+ );
137
+
138
+ if (err instanceof HttpFetcherError) {
139
+ throw new Error(
140
+ `AuthorizationAttributesMsService: [${methodName}] request failed with status ${err.status}: ${err.message}`
141
+ );
142
+ }
143
+ throw err;
144
+ }
145
+
146
+ /**
147
+ * Generic helper for executing upsert requests
148
+ */
149
+ private static async executeUpsertRequest<T extends ResourceAttributeAssignment | EntityAttributeAssignment>(
150
+ accountId: number,
151
+ assignments: T[],
152
+ pathTemplate: string,
153
+ requestBodyKey: 'resourceAttributeAssignments' | 'entityAttributeAssignments',
154
+ assignmentClass: abstract new (...args: any[]) => T,
155
+ logPrefix: string,
156
+ methodName: string
157
+ ): Promise<void> {
158
+ // Skip HTTP requests in test environment
159
+ if (process.env.NODE_ENV === 'test') {
160
+ logger.debug(
161
+ { tag: this.LOG_TAG, accountId, count: assignments.length },
162
+ `Skipping ${methodName} in test environment`
163
+ );
164
+ return;
165
+ }
166
+
167
+ // Validate inputs
168
+ ValidationUtils.validateInteger(accountId, 'accountId');
169
+ ValidationUtils.validateArray(assignments, 'assignments');
170
+
171
+ if (assignments.length === 0) {
172
+ logger.warn({ tag: this.LOG_TAG, accountId }, `${methodName} called with empty array`);
173
+ return;
174
+ }
175
+
176
+ // Validate all assignments are instances of the correct class
177
+ this.validateMessages(assignments, assignmentClass);
178
+
179
+ // Convert assignments to hash format
180
+ const assignmentsHash = assignments.map(assignment => assignment.toH());
181
+
182
+ // Build request body
183
+ const requestBody =
184
+ requestBodyKey === 'resourceAttributeAssignments'
185
+ ? ({ resourceAttributeAssignments: assignmentsHash } as UpsertRequestBody)
186
+ : ({ entityAttributeAssignments: assignmentsHash } as UpsertEntityRequestBody);
187
+
188
+ const httpClient = Api.getPart('httpClient');
189
+ if (!httpClient) {
190
+ throw new Error('AuthorizationAttributesMsService: HTTP client is not initialized');
191
+ }
192
+ const path = replacePathParams(pathTemplate, { accountId });
193
+ const headers = this.getRequestHeaders(accountId);
194
+
195
+ try {
196
+ logger.info({ tag: this.LOG_TAG, accountId, count: assignments.length }, `Upserting ${logPrefix} attributes`);
197
+
198
+ await httpClient.fetch(
199
+ {
200
+ url: {
201
+ appName: APP_NAME,
202
+ path,
203
+ },
204
+ method: 'POST',
205
+ headers,
206
+ body: JSON.stringify(requestBody),
207
+ },
208
+ {
209
+ timeout: AuthorizationInternalService.getRequestTimeout(),
210
+ retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
211
+ }
212
+ );
213
+
214
+ logger.info(
215
+ { tag: this.LOG_TAG, accountId, count: assignments.length },
216
+ `Successfully upserted ${logPrefix} attributes`
217
+ );
218
+ } catch (err) {
219
+ this.handleRequestError(err, methodName, { accountId });
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Generic helper for executing delete requests
225
+ */
226
+ private static async executeDeleteRequest(
227
+ accountId: number,
228
+ pathTemplate: string,
229
+ pathParams: Record<string, string | number>,
230
+ keys: string[],
231
+ logPrefix: string,
232
+ methodName: string,
233
+ context: Record<string, any> = {}
234
+ ): Promise<void> {
235
+ // Skip HTTP requests in test environment
236
+ if (process.env.NODE_ENV === 'test') {
237
+ logger.debug({ tag: this.LOG_TAG, accountId, ...pathParams, keys }, `Skipping ${methodName} in test environment`);
238
+ return;
239
+ }
240
+
241
+ // Validate inputs
242
+ ValidationUtils.validateInteger(accountId, 'accountId');
243
+ ValidationUtils.validateArray(keys, 'attributeKeys');
244
+
245
+ if (keys.length === 0) {
246
+ logger.warn({ tag: this.LOG_TAG, accountId, ...pathParams }, `${methodName} called with empty keys array`);
247
+ return;
248
+ }
249
+
250
+ // Validate all keys are strings
251
+ ValidationUtils.validateStringArray(keys, 'attributeKeys');
252
+
253
+ // Build request body
254
+ const requestBody: DeleteRequestBody = {
255
+ keys,
256
+ };
257
+
258
+ const httpClient = Api.getPart('httpClient');
259
+ if (!httpClient) {
260
+ throw new Error('AuthorizationAttributesMsService: HTTP client is not initialized');
261
+ }
262
+ const path = replacePathParams(pathTemplate, { accountId, ...pathParams });
263
+ const headers = this.getRequestHeaders(accountId);
264
+
265
+ try {
266
+ logger.info({ tag: this.LOG_TAG, accountId, ...pathParams, keys }, `Deleting ${logPrefix} attributes`);
267
+
268
+ await httpClient.fetch(
269
+ {
270
+ url: {
271
+ appName: APP_NAME,
272
+ path,
273
+ },
274
+ method: 'DELETE',
275
+ headers,
276
+ body: JSON.stringify(requestBody),
277
+ },
278
+ {
279
+ timeout: AuthorizationInternalService.getRequestTimeout(),
280
+ retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
281
+ }
282
+ );
283
+
284
+ logger.info(
285
+ { tag: this.LOG_TAG, accountId, ...pathParams, keys },
286
+ `Successfully deleted ${logPrefix} attributes`
287
+ );
288
+ } catch (err) {
289
+ this.handleRequestError(err, methodName, { accountId, ...pathParams, ...context });
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Creates or updates resource attributes synchronously.
295
+ * @param accountId The account ID
296
+ * @param resourceAttributeAssignments Array of ResourceAttributeAssignment objects
297
+ * @returns Promise<void>
298
+ */
299
+ static async upsertResourceAttributesSync(
300
+ accountId: number,
301
+ resourceAttributeAssignments: ResourceAttributeAssignment[]
302
+ ): Promise<void> {
303
+ return this.executeUpsertRequest(
304
+ accountId,
305
+ resourceAttributeAssignments,
306
+ UPSERT_RESOURCE_ATTRIBUTES_PATH,
307
+ 'resourceAttributeAssignments',
308
+ ResourceAttributeAssignment,
309
+ 'resource',
310
+ 'upsertResourceAttributesSync'
311
+ );
312
+ }
313
+
314
+ /**
315
+ * Deletes specific attributes from a resource synchronously.
316
+ * @param accountId The account ID
317
+ * @param resource Object with resourceType (string) and resourceId (number)
318
+ * @param attributeKeys Array of attribute key strings to delete
319
+ * @returns Promise<void>
320
+ */
321
+ static async deleteResourceAttributesSync(
322
+ accountId: number,
323
+ resource: Resource,
324
+ attributeKeys: string[]
325
+ ): Promise<void> {
326
+ // Validate resource object
327
+ if (!resource || typeof resource !== 'object') {
328
+ throw new ArgumentError('resource must be an object');
329
+ }
330
+ ValidationUtils.validateInteger(resource.resourceId, 'resource.resourceId');
331
+ ValidationUtils.validateString(resource.resourceType, 'resource.resourceType');
332
+
333
+ return this.executeDeleteRequest(
334
+ accountId,
335
+ DELETE_RESOURCE_ATTRIBUTES_PATH,
336
+ {
337
+ resourceType: resource.resourceType,
338
+ resourceId: resource.resourceId,
339
+ },
340
+ attributeKeys,
341
+ 'resource',
342
+ 'deleteResourceAttributesSync',
343
+ { resource }
344
+ );
345
+ }
346
+
347
+ /**
348
+ * Creates or updates entity attributes synchronously.
349
+ * @param accountId The account ID
350
+ * @param entityAttributeAssignments Array of EntityAttributeAssignment objects
351
+ * @returns Promise<void>
352
+ */
353
+ static async upsertEntityAttributesSync(
354
+ accountId: number,
355
+ entityAttributeAssignments: EntityAttributeAssignment[]
356
+ ): Promise<void> {
357
+ return this.executeUpsertRequest(
358
+ accountId,
359
+ entityAttributeAssignments,
360
+ UPSERT_ENTITY_ATTRIBUTES_PATH,
361
+ 'entityAttributeAssignments',
362
+ EntityAttributeAssignment,
363
+ 'entity',
364
+ 'upsertEntityAttributesSync'
365
+ );
366
+ }
367
+
368
+ /**
369
+ * Deletes specific attributes from an entity synchronously.
370
+ * @param accountId The account ID
371
+ * @param entityType The entity type
372
+ * @param entityId The entity ID
373
+ * @param attributeKeys Array of attribute key strings to delete
374
+ * @returns Promise<void>
375
+ */
376
+ static async deleteEntityAttributesSync(
377
+ accountId: number,
378
+ entityType: EntityType,
379
+ entityId: number,
380
+ attributeKeys: EntityAttributeKeyType[]
381
+ ): Promise<void> {
382
+ if (!entityType || typeof entityType !== 'string' || entityType.trim() === '') {
383
+ throw new ArgumentError(`entityType must be a non-empty string, got: ${entityType}`);
384
+ }
385
+ ValidationUtils.validateInteger(entityId, 'entityId');
386
+
387
+ return this.executeDeleteRequest(
388
+ accountId,
389
+ DELETE_ENTITY_ATTRIBUTES_PATH,
390
+ {
391
+ entityType,
392
+ entityId,
393
+ },
394
+ attributeKeys,
395
+ 'entity',
396
+ 'deleteEntityAttributesSync',
397
+ { entityType, entityId }
398
+ );
399
+ }
400
+ }