@intellegens/cornerstone-client 0.0.0-experimental-upgrade-20260302-1

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 (251) hide show
  1. package/README.md +256 -0
  2. package/demo/index.ts +29 -0
  3. package/demo/public_html/favicon.ico +0 -0
  4. package/demo/public_html/index.html +106 -0
  5. package/demo/public_html/websettings.json +3 -0
  6. package/dist/adapters/CollectionViewAdapter/index.d.ts +198 -0
  7. package/dist/adapters/CollectionViewAdapter/index.integration.test.d.ts +1 -0
  8. package/dist/adapters/CollectionViewAdapter/index.integration.test.js +163 -0
  9. package/dist/adapters/CollectionViewAdapter/index.js +381 -0
  10. package/dist/adapters/SearchAdapter/index.d.ts +55 -0
  11. package/dist/adapters/SearchAdapter/index.js +233 -0
  12. package/dist/adapters/index.d.ts +2 -0
  13. package/dist/adapters/index.js +2 -0
  14. package/dist/data/api/dto/PropertyPathDto.d.ts +4 -0
  15. package/dist/data/api/dto/PropertyPathDto.js +1 -0
  16. package/dist/data/api/dto/ReadOptionsDto.d.ts +8 -0
  17. package/dist/data/api/dto/ReadOptionsDto.js +1 -0
  18. package/dist/data/api/dto/ReadResultDto.d.ts +12 -0
  19. package/dist/data/api/dto/ReadResultDto.js +1 -0
  20. package/dist/data/api/dto/ReadResultMetadataDto.d.ts +8 -0
  21. package/dist/data/api/dto/ReadResultMetadataDto.js +1 -0
  22. package/dist/data/api/dto/crud/CrudMetadataDto.d.ts +4 -0
  23. package/dist/data/api/dto/crud/CrudMetadataDto.js +1 -0
  24. package/dist/data/api/dto/crud/index.d.ts +1 -0
  25. package/dist/data/api/dto/crud/index.js +1 -0
  26. package/dist/data/api/dto/index.d.ts +4 -0
  27. package/dist/data/api/dto/index.js +4 -0
  28. package/dist/data/api/dto/read/ReadMetadataDto.d.ts +8 -0
  29. package/dist/data/api/dto/read/ReadMetadataDto.js +1 -0
  30. package/dist/data/api/dto/read/ReadSelectedDefinitionDto.d.ts +18 -0
  31. package/dist/data/api/dto/read/ReadSelectedDefinitionDto.js +1 -0
  32. package/dist/data/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.d.ts +22 -0
  33. package/dist/data/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.js +1 -0
  34. package/dist/data/api/dto/read/ReadSelectedNestedCriteriaDto.d.ts +18 -0
  35. package/dist/data/api/dto/read/ReadSelectedNestedCriteriaDto.js +1 -0
  36. package/dist/data/api/dto/read/ReadSelectedOrderingDefinitionDto.d.ts +7 -0
  37. package/dist/data/api/dto/read/ReadSelectedOrderingDefinitionDto.js +1 -0
  38. package/dist/data/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.d.ts +14 -0
  39. package/dist/data/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.js +1 -0
  40. package/dist/data/api/dto/read/ReadSelectedPaginationDefinitionDto.d.ts +13 -0
  41. package/dist/data/api/dto/read/ReadSelectedPaginationDefinitionDto.js +1 -0
  42. package/dist/data/api/dto/read/ReadSelectedSearchDefinitionBuilder.d.ts +167 -0
  43. package/dist/data/api/dto/read/ReadSelectedSearchDefinitionBuilder.js +267 -0
  44. package/dist/data/api/dto/read/ReadSelectedSearchDefinitionDto.d.ts +33 -0
  45. package/dist/data/api/dto/read/ReadSelectedSearchDefinitionDto.js +1 -0
  46. package/dist/data/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.d.ts +114 -0
  47. package/dist/data/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.js +1 -0
  48. package/dist/data/api/dto/read/index.d.ts +10 -0
  49. package/dist/data/api/dto/read/index.js +10 -0
  50. package/dist/data/api/dto/response/ApiErrorDto.d.ts +17 -0
  51. package/dist/data/api/dto/response/ApiErrorDto.js +1 -0
  52. package/dist/data/api/dto/response/ApiErrorResponseDto.d.ts +11 -0
  53. package/dist/data/api/dto/response/ApiErrorResponseDto.js +1 -0
  54. package/dist/data/api/dto/response/ApiResponseDto.d.ts +3 -0
  55. package/dist/data/api/dto/response/ApiResponseDto.js +1 -0
  56. package/dist/data/api/dto/response/ApiSuccessResponseDto.d.ts +13 -0
  57. package/dist/data/api/dto/response/ApiSuccessResponseDto.js +1 -0
  58. package/dist/data/api/dto/response/EmptyMetadataDto.d.ts +4 -0
  59. package/dist/data/api/dto/response/EmptyMetadataDto.js +1 -0
  60. package/dist/data/api/dto/response/MetadataDto.d.ts +25 -0
  61. package/dist/data/api/dto/response/MetadataDto.js +1 -0
  62. package/dist/data/api/dto/response/index.d.ts +5 -0
  63. package/dist/data/api/dto/response/index.js +5 -0
  64. package/dist/data/api/enum/index.d.ts +2 -0
  65. package/dist/data/api/enum/index.js +2 -0
  66. package/dist/data/api/enum/read/ReadSelectedCollectionOperator.d.ts +16 -0
  67. package/dist/data/api/enum/read/ReadSelectedCollectionOperator.js +17 -0
  68. package/dist/data/api/enum/read/ReadSelectedComparisonOperator.d.ts +69 -0
  69. package/dist/data/api/enum/read/ReadSelectedComparisonOperator.js +76 -0
  70. package/dist/data/api/enum/read/ReadSelectedLogicalOperator.d.ts +15 -0
  71. package/dist/data/api/enum/read/ReadSelectedLogicalOperator.js +16 -0
  72. package/dist/data/api/enum/read/ReadSelectedOrderingDirection.d.ts +13 -0
  73. package/dist/data/api/enum/read/ReadSelectedOrderingDirection.js +14 -0
  74. package/dist/data/api/enum/read/ReadSelectedPropertyType.d.ts +66 -0
  75. package/dist/data/api/enum/read/ReadSelectedPropertyType.js +75 -0
  76. package/dist/data/api/enum/read/index.d.ts +5 -0
  77. package/dist/data/api/enum/read/index.js +5 -0
  78. package/dist/data/api/enum/response/ApiErrorCodes.d.ts +7 -0
  79. package/dist/data/api/enum/response/ApiErrorCodes.js +8 -0
  80. package/dist/data/api/enum/response/ErrorCode.d.ts +13 -0
  81. package/dist/data/api/enum/response/ErrorCode.js +14 -0
  82. package/dist/data/api/enum/response/index.d.ts +1 -0
  83. package/dist/data/api/enum/response/index.js +1 -0
  84. package/dist/data/api/index.d.ts +3 -0
  85. package/dist/data/api/index.js +3 -0
  86. package/dist/data/api/interface/ICommonIdentifiable.d.ts +7 -0
  87. package/dist/data/api/interface/ICommonIdentifiable.js +1 -0
  88. package/dist/data/api/interface/IConcurrencySafe.d.ts +9 -0
  89. package/dist/data/api/interface/IConcurrencySafe.js +2 -0
  90. package/dist/data/api/interface/IIdentifiable.d.ts +11 -0
  91. package/dist/data/api/interface/IIdentifiable.js +1 -0
  92. package/dist/data/api/interface/IIdentifiableSecondary.d.ts +9 -0
  93. package/dist/data/api/interface/IIdentifiableSecondary.js +1 -0
  94. package/dist/data/api/interface/index.d.ts +3 -0
  95. package/dist/data/api/interface/index.js +3 -0
  96. package/dist/data/auth/dto/ClaimDto.d.ts +4 -0
  97. package/dist/data/auth/dto/ClaimDto.js +1 -0
  98. package/dist/data/auth/dto/RegisterRequestDto.d.ts +4 -0
  99. package/dist/data/auth/dto/RegisterRequestDto.js +1 -0
  100. package/dist/data/auth/dto/RoleDto.d.ts +5 -0
  101. package/dist/data/auth/dto/RoleDto.js +1 -0
  102. package/dist/data/auth/dto/SignInRequestDto.d.ts +4 -0
  103. package/dist/data/auth/dto/SignInRequestDto.js +1 -0
  104. package/dist/data/auth/dto/TokensDto.d.ts +4 -0
  105. package/dist/data/auth/dto/TokensDto.js +1 -0
  106. package/dist/data/auth/dto/UserDto.d.ts +17 -0
  107. package/dist/data/auth/dto/UserDto.js +1 -0
  108. package/dist/data/auth/dto/UserInfoDto.d.ts +14 -0
  109. package/dist/data/auth/dto/UserInfoDto.js +1 -0
  110. package/dist/data/auth/dto/index.d.ts +5 -0
  111. package/dist/data/auth/dto/index.js +5 -0
  112. package/dist/data/auth/index.d.ts +2 -0
  113. package/dist/data/auth/index.js +2 -0
  114. package/dist/data/auth/policy.d.ts +52 -0
  115. package/dist/data/auth/policy.js +44 -0
  116. package/dist/data/index.d.ts +2 -0
  117. package/dist/data/index.js +2 -0
  118. package/dist/index.d.ts +4 -0
  119. package/dist/index.js +4 -0
  120. package/dist/services/api/ApiCrudControllerClient/index.d.ts +41 -0
  121. package/dist/services/api/ApiCrudControllerClient/index.integration.test.d.ts +1 -0
  122. package/dist/services/api/ApiCrudControllerClient/index.integration.test.js +34 -0
  123. package/dist/services/api/ApiCrudControllerClient/index.js +116 -0
  124. package/dist/services/api/ApiInitializationService/index.d.ts +106 -0
  125. package/dist/services/api/ApiInitializationService/index.js +208 -0
  126. package/dist/services/api/ApiReadControllerClient/index.d.ts +40 -0
  127. package/dist/services/api/ApiReadControllerClient/index.integration.test.d.ts +1 -0
  128. package/dist/services/api/ApiReadControllerClient/index.integration.test.js +59 -0
  129. package/dist/services/api/ApiReadControllerClient/index.js +111 -0
  130. package/dist/services/api/HttpService/FetchHttpService.d.ts +7 -0
  131. package/dist/services/api/HttpService/FetchHttpService.integration.test.d.ts +1 -0
  132. package/dist/services/api/HttpService/FetchHttpService.integration.test.js +52 -0
  133. package/dist/services/api/HttpService/FetchHttpService.js +29 -0
  134. package/dist/services/api/HttpService/HttpRequestConfig.d.ts +10 -0
  135. package/dist/services/api/HttpService/HttpRequestConfig.js +1 -0
  136. package/dist/services/api/HttpService/HttpResponse.d.ts +11 -0
  137. package/dist/services/api/HttpService/HttpResponse.js +3 -0
  138. package/dist/services/api/HttpService/IHttpService.d.ts +13 -0
  139. package/dist/services/api/HttpService/IHttpService.js +3 -0
  140. package/dist/services/api/HttpService/index.d.ts +9 -0
  141. package/dist/services/api/HttpService/index.js +10 -0
  142. package/dist/services/api/UserManagementControllerClient/index.d.ts +41 -0
  143. package/dist/services/api/UserManagementControllerClient/index.integration.test.d.ts +1 -0
  144. package/dist/services/api/UserManagementControllerClient/index.integration.test.js +60 -0
  145. package/dist/services/api/UserManagementControllerClient/index.js +117 -0
  146. package/dist/services/api/index.d.ts +5 -0
  147. package/dist/services/api/index.js +5 -0
  148. package/dist/services/auth/client/AuthService/index.d.ts +75 -0
  149. package/dist/services/auth/client/AuthService/index.js +200 -0
  150. package/dist/services/auth/client/AuthorizationManagementControllerClient/index.d.ts +48 -0
  151. package/dist/services/auth/client/AuthorizationManagementControllerClient/index.integration.test.d.ts +1 -0
  152. package/dist/services/auth/client/AuthorizationManagementControllerClient/index.integration.test.js +89 -0
  153. package/dist/services/auth/client/AuthorizationManagementControllerClient/index.js +148 -0
  154. package/dist/services/auth/client/index.d.ts +2 -0
  155. package/dist/services/auth/client/index.js +2 -0
  156. package/dist/services/auth/index.d.ts +1 -0
  157. package/dist/services/auth/index.js +1 -0
  158. package/dist/services/index.d.ts +2 -0
  159. package/dist/services/index.js +2 -0
  160. package/dist/utils/authorization/index.d.ts +17 -0
  161. package/dist/utils/authorization/index.js +45 -0
  162. package/dist/utils/index.d.ts +2 -0
  163. package/dist/utils/index.js +2 -0
  164. package/dist/utils/result/index.d.ts +21 -0
  165. package/dist/utils/result/index.js +16 -0
  166. package/dist/utils/search/index.d.ts +34 -0
  167. package/dist/utils/search/index.js +106 -0
  168. package/package.json +45 -0
  169. package/src/adapters/CollectionViewAdapter/index.integration.test.ts +197 -0
  170. package/src/adapters/CollectionViewAdapter/index.ts +477 -0
  171. package/src/adapters/SearchAdapter/index.ts +302 -0
  172. package/src/adapters/index.ts +2 -0
  173. package/src/data/api/dto/PropertyPathDto.ts +4 -0
  174. package/src/data/api/dto/ReadOptionsDto.ts +8 -0
  175. package/src/data/api/dto/ReadResultDto.ts +13 -0
  176. package/src/data/api/dto/ReadResultMetadataDto.ts +8 -0
  177. package/src/data/api/dto/crud/CrudMetadataDto.ts +4 -0
  178. package/src/data/api/dto/crud/index.ts +1 -0
  179. package/src/data/api/dto/index.ts +4 -0
  180. package/src/data/api/dto/read/ReadMetadataDto.ts +8 -0
  181. package/src/data/api/dto/read/ReadSelectedDefinitionDto.ts +21 -0
  182. package/src/data/api/dto/read/ReadSelectedNestedCollectionCriteriaDto.ts +25 -0
  183. package/src/data/api/dto/read/ReadSelectedNestedCriteriaDto.ts +20 -0
  184. package/src/data/api/dto/read/ReadSelectedOrderingDefinitionDto.ts +8 -0
  185. package/src/data/api/dto/read/ReadSelectedOrderingPropertyDefinitionDto.ts +16 -0
  186. package/src/data/api/dto/read/ReadSelectedPaginationDefinitionDto.ts +13 -0
  187. package/src/data/api/dto/read/ReadSelectedSearchDefinitionBuilder.ts +348 -0
  188. package/src/data/api/dto/read/ReadSelectedSearchDefinitionDto.ts +43 -0
  189. package/src/data/api/dto/read/ReadSelectedSearchPropertyDefinitionDto.ts +186 -0
  190. package/src/data/api/dto/read/index.ts +10 -0
  191. package/src/data/api/dto/response/ApiErrorDto.ts +21 -0
  192. package/src/data/api/dto/response/ApiErrorResponseDto.ts +13 -0
  193. package/src/data/api/dto/response/ApiResponseDto.ts +7 -0
  194. package/src/data/api/dto/response/ApiSuccessResponseDto.ts +13 -0
  195. package/src/data/api/dto/response/MetadataDto.ts +24 -0
  196. package/src/data/api/dto/response/index.ts +5 -0
  197. package/src/data/api/enum/index.ts +2 -0
  198. package/src/data/api/enum/read/ReadSelectedCollectionOperator.ts +17 -0
  199. package/src/data/api/enum/read/ReadSelectedComparisonOperator.ts +96 -0
  200. package/src/data/api/enum/read/ReadSelectedLogicalOperator.ts +16 -0
  201. package/src/data/api/enum/read/ReadSelectedOrderingDirection.ts +13 -0
  202. package/src/data/api/enum/read/ReadSelectedPropertyType.ts +96 -0
  203. package/src/data/api/enum/read/index.ts +5 -0
  204. package/src/data/api/enum/response/ErrorCode.ts +13 -0
  205. package/src/data/api/enum/response/index.ts +1 -0
  206. package/src/data/api/index.ts +3 -0
  207. package/src/data/api/interface/ICommonIdentifiable.ts +9 -0
  208. package/src/data/api/interface/IConcurrencySafe.ts +9 -0
  209. package/src/data/api/interface/IIdentifiable.ts +12 -0
  210. package/src/data/api/interface/IIdentifiableSecondary.ts +9 -0
  211. package/src/data/api/interface/index.ts +3 -0
  212. package/src/data/auth/dto/ClaimDto.ts +4 -0
  213. package/src/data/auth/dto/RegisterRequestDto.ts +4 -0
  214. package/src/data/auth/dto/RoleDto.ts +6 -0
  215. package/src/data/auth/dto/SignInRequestDto.ts +4 -0
  216. package/src/data/auth/dto/TokensDto.ts +4 -0
  217. package/src/data/auth/dto/UserDto.ts +18 -0
  218. package/src/data/auth/dto/UserInfoDto.ts +15 -0
  219. package/src/data/auth/dto/index.ts +5 -0
  220. package/src/data/auth/index.ts +2 -0
  221. package/src/data/auth/policy.ts +63 -0
  222. package/src/data/index.ts +2 -0
  223. package/src/index.ts +4 -0
  224. package/src/services/api/ApiCrudControllerClient/index.integration.test.ts +46 -0
  225. package/src/services/api/ApiCrudControllerClient/index.ts +135 -0
  226. package/src/services/api/ApiInitializationService/index.ts +254 -0
  227. package/src/services/api/ApiReadControllerClient/index.integration.test.ts +71 -0
  228. package/src/services/api/ApiReadControllerClient/index.ts +137 -0
  229. package/src/services/api/HttpService/FetchHttpService.integration.test.ts +65 -0
  230. package/src/services/api/HttpService/FetchHttpService.ts +34 -0
  231. package/src/services/api/HttpService/HttpRequestConfig.ts +10 -0
  232. package/src/services/api/HttpService/HttpResponse.ts +14 -0
  233. package/src/services/api/HttpService/IHttpService.ts +17 -0
  234. package/src/services/api/HttpService/README.md +106 -0
  235. package/src/services/api/HttpService/index.ts +12 -0
  236. package/src/services/api/UserManagementControllerClient/index.integration.test.ts +69 -0
  237. package/src/services/api/UserManagementControllerClient/index.ts +134 -0
  238. package/src/services/api/index.ts +5 -0
  239. package/src/services/auth/client/AuthService/index.ts +233 -0
  240. package/src/services/auth/client/AuthorizationManagementControllerClient/index.integration.test.ts +110 -0
  241. package/src/services/auth/client/AuthorizationManagementControllerClient/index.ts +165 -0
  242. package/src/services/auth/client/index.ts +2 -0
  243. package/src/services/auth/index.ts +1 -0
  244. package/src/services/index.ts +2 -0
  245. package/src/utils/authorization/index.ts +47 -0
  246. package/src/utils/index.ts +2 -0
  247. package/src/utils/result/index.ts +25 -0
  248. package/src/utils/search/index.ts +150 -0
  249. package/tsconfig.json +19 -0
  250. package/vitest-setup.ts +43 -0
  251. package/vitest.config.ts +59 -0
@@ -0,0 +1,254 @@
1
+ // websettings.json configuration file contents type
2
+ import { fail, ok } from '@utils';
3
+
4
+ type WebSettings = {
5
+ api: string | undefined;
6
+ };
7
+
8
+ /**
9
+ * Generic shared settings interface - can represent any user-configured settings structure
10
+ * The actual properties depend on what users configure in their appsettings.json or environment variables
11
+ */
12
+ type ISharedSettings = {
13
+ [key: string]: unknown;
14
+ };
15
+
16
+ /**
17
+ * Initialization info DTO that matches the server-side structure
18
+ */
19
+ type IInitializationInfoDto<TSharedSettings = ISharedSettings> = {
20
+ appSettings: TSharedSettings;
21
+ };
22
+
23
+ import { ApiErrorResponseDto, ApiSuccessResponseDto, EmptyMetadataDto } from '@/data';
24
+ import { defaultHttpService, IHttpService } from '../HttpService';
25
+
26
+ /**
27
+ * Central API configuration service, fetches and exposes Cornerstone API configuration
28
+ *
29
+ * @export
30
+ * @class ApiInitializationService
31
+ */
32
+ export class ApiInitializationService {
33
+ /**
34
+ * If API base URL detection was completed
35
+ * IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
36
+ */
37
+ public _apiBaseUrlDetected = false;
38
+
39
+ // #region HTTP service
40
+ /**
41
+ * If API base URL detection was completed, this property will hold the method by which detection was performed
42
+ * IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
43
+ */
44
+ public _apiBaseUrlDetectionMethod: string | undefined = undefined;
45
+ /**
46
+ * If API base URL detection was completed, this property will hold the detected API base URL
47
+ * IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
48
+ */
49
+ public _apiBaseUrl: string | undefined = undefined;
50
+
51
+ // #endregion
52
+
53
+ // #region API Base URL detection
54
+ /**
55
+ * If API base URL detection has failed, this property will hold the error with which it has failed
56
+ * IMPORTANT: This property is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
57
+ */
58
+ public _apiBaseError: Error | undefined = undefined;
59
+ // #region config App
60
+ public appConfig?: ISharedSettings | undefined;
61
+ private _httpService: IHttpService = defaultHttpService;
62
+ private _apiBaseUrlPromise: Promise<string | undefined> | undefined = undefined;
63
+
64
+ /**
65
+ * Initializes the API configuration
66
+ *
67
+ * This method calls all methods that need to be called during the API configuration phase:
68
+ * - Starts detection of API base URL.
69
+ * - Fetches shared settings from the server.
70
+ *
71
+ * @async
72
+ * @param httpService - HTTP service implementation to use for requests
73
+ * @param {AbortSignal} [signal] - Optional cancellation signal
74
+ * @return {Promise<void>} Resolves when initialization is complete.
75
+ */
76
+ public async initialize({
77
+ httpService,
78
+ signal,
79
+ }: {
80
+ httpService?: IHttpService;
81
+ signal?: AbortSignal;
82
+ } = {}): Promise<void> {
83
+ // Store configuration
84
+ if (httpService !== undefined) this._httpService = httpService;
85
+ // (Pre)detect API base URL
86
+ await this._getApiBaseUrl(signal);
87
+ // Initialize app config
88
+ await this._getAppConfig();
89
+ }
90
+
91
+ /**
92
+ * Gets configured HTTP service
93
+ * @returns
94
+ */
95
+ public _getHttpService(): IHttpService {
96
+ return this._httpService;
97
+ }
98
+
99
+ /**
100
+ * Tries detecting API base URL by multiple methods
101
+ *
102
+ * - Checks the response headers of the current URL for the `CORNERSTONE-API-BASEURL` header.
103
+ * - Attempts to load the `websettings.json` file and looks for an `api` field.
104
+ * - Defaults to `undefined`.
105
+ *
106
+ * IMPORTANT: This method is not meant to be used in normal cases - when ever possible use the .getApiUrl(relativeEndpointPath: string) method instead.
107
+ *
108
+ * @async
109
+ * @param {AbortSignal} [signal] - Optional cancellation signal
110
+ * @return {Promise<string | undefined>} Returns detected API base URL
111
+ * @throws {Error} Throws error if either of the detection methods fails
112
+ */
113
+ public async _getApiBaseUrl(signal?: AbortSignal): Promise<string | undefined> {
114
+ // Check if API already detected; don't reattempt detection
115
+ if (this._apiBaseUrlDetected) return this._apiBaseUrl;
116
+
117
+ // Check if detection already in progress; don't allow multiple detections in parallel
118
+ if (this._apiBaseUrlPromise) return this._apiBaseUrlPromise;
119
+
120
+ // Detect API base URL
121
+ return (this._apiBaseUrlPromise = new Promise<string | undefined>((resolve, reject) =>
122
+ (async () => {
123
+ // Reset
124
+ this._apiBaseUrlDetected = false;
125
+ this._apiBaseUrlDetectionMethod = undefined;
126
+ this._apiBaseUrl = undefined;
127
+ this._apiBaseError = undefined;
128
+
129
+ // Check CORNERSTONE-API-BASEURL header for API base URL
130
+ try {
131
+ const currentUrlResponse = await this._httpService.request(window.location.href, {
132
+ method: 'GET',
133
+ signal,
134
+ });
135
+ const apiHeader: string | null =
136
+ currentUrlResponse.headers['CORNERSTONE-API-BASEURL'] || currentUrlResponse.headers['cornerstone-api-baseurl'] || null;
137
+ if (apiHeader) {
138
+ this._apiBaseUrlDetected = true;
139
+ this._apiBaseUrlDetectionMethod = 'GET / Header:CORNERSTONE-API-BASEURL';
140
+ this._apiBaseUrl = apiHeader;
141
+ this._apiBaseError = undefined;
142
+ this._apiBaseUrlPromise = undefined;
143
+ return resolve(this._apiBaseUrl);
144
+ }
145
+ } catch (err) {
146
+ this._apiBaseError = err instanceof Error ? err : new Error('Failed loading API base URL from response header!');
147
+ }
148
+
149
+ // Check ./websettings.json header for API base URL
150
+ try {
151
+ const webSettingsResponse = await this._httpService.request('./websettings.json', {
152
+ method: 'GET',
153
+ signal,
154
+ });
155
+ if (webSettingsResponse.status === 404) return (this._apiBaseUrl = undefined);
156
+ const webSettings: WebSettings = await webSettingsResponse.json();
157
+ if (webSettings?.api) {
158
+ this._apiBaseUrlDetected = true;
159
+ this._apiBaseUrlDetectionMethod = 'GET websettings.json';
160
+ this._apiBaseUrl = webSettings.api;
161
+ this._apiBaseError = undefined;
162
+ this._apiBaseUrlPromise = undefined;
163
+ return resolve(this._apiBaseUrl);
164
+ }
165
+ } catch (err) {
166
+ this._apiBaseError = err instanceof Error ? err : new Error('Failed loading API base URL from settings file!');
167
+ }
168
+
169
+ // Check if error caught during any of the detection methods
170
+ if (this._apiBaseError !== undefined) {
171
+ this._apiBaseUrlDetected = false;
172
+ this._apiBaseUrlDetectionMethod = undefined;
173
+ this._apiBaseUrl = undefined;
174
+ this._apiBaseUrlPromise = undefined;
175
+ return reject(this._apiBaseError);
176
+ }
177
+
178
+ // Default to no API found
179
+ this._apiBaseUrlDetected = true;
180
+ this._apiBaseUrlDetectionMethod = undefined;
181
+ this._apiBaseUrl = undefined;
182
+ this._apiBaseError = undefined;
183
+ this._apiBaseUrlPromise = undefined;
184
+ return resolve(this._apiBaseUrl);
185
+ })(),
186
+ ));
187
+ }
188
+
189
+ /**
190
+ * Composes a full API URL for a provided endpoint path.
191
+ *
192
+ * @async
193
+ * @param relativeEndpointPathSections Relative endpoint path sections
194
+ * @return {Promise<string | undefined>} Returns the API endpoint's URL
195
+ * @throws {Error} Throws error if API base URL detection fails
196
+ */
197
+ public async getApiUrl(...relativeEndpointPathSections: string[]): Promise<string> {
198
+ const apiBaseUrl = await this._getApiBaseUrl();
199
+ const domain = !apiBaseUrl ? '/' : `${this.removeEndsWithSlashChar(apiBaseUrl)}/`;
200
+ const path = relativeEndpointPathSections.map(section => this.removeStartsWithSlashChar(this.removeEndsWithSlashChar(section))).join('/');
201
+ return `${domain}${path}`;
202
+ }
203
+
204
+ /**
205
+ * Removes starting '/' character
206
+ * @param str String to remove the starting '/' character from
207
+ * @returns String with starting '/' character removed
208
+ */
209
+ private removeStartsWithSlashChar(str: string): string {
210
+ return !str.startsWith('/') ? str : str.substring(1);
211
+ }
212
+
213
+ // #endregion
214
+
215
+ /**
216
+ * Removes ending '/' character
217
+ * @param str String to remove the ending '/' character from
218
+ * @returns String with ending '/' character removed
219
+ */
220
+ private removeEndsWithSlashChar(str: string): string {
221
+ return !str.endsWith('/') ? str : str.substring(0, str.length - 1);
222
+ }
223
+
224
+ /**
225
+ * Fetches initialization info (shared settings) from the server
226
+ * @returns Promise that resolves to shared settings or undefined if the request fails
227
+ */
228
+ private async _getAppConfig(): Promise<void> {
229
+ try {
230
+ const response = await fetch(`${this._apiBaseUrl}/system/init`, { credentials: 'include' });
231
+ if (!response.ok) {
232
+ throw fail(await response.json()) as ApiErrorResponseDto;
233
+ }
234
+ const apiResponse = ok<ApiSuccessResponseDto<IInitializationInfoDto, EmptyMetadataDto>, ApiErrorResponseDto>(await response.json());
235
+
236
+ if (!apiResponse.ok) {
237
+ throw apiResponse.error;
238
+ }
239
+
240
+ // Return the settings from the nested structure
241
+ this.appConfig = apiResponse.result.appSettings;
242
+ } catch (error) {
243
+ console.error('Failed to get app config', error);
244
+ this.appConfig = undefined;
245
+ }
246
+ }
247
+
248
+ // #endregion
249
+ }
250
+
251
+ /**
252
+ * Singleton instance of ApiInitializationService
253
+ */
254
+ export const apiInitializationService = new ApiInitializationService();
@@ -0,0 +1,71 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { ApiReadControllerClient } from '.';
3
+ import { FetchHttpService } from '../HttpService';
4
+ import { ReadSelectedComparisonOperator, ReadSelectedDefinitionDto, ReadSelectedLogicalOperator, ReadSelectedPropertyType } from '@/data';
5
+
6
+ type VehicleSlimDto = { Id: number; Brand: string; Model: string; Year: number; RowVersion: string };
7
+
8
+ describe('ApiReadControllerClient', () => {
9
+ let client: ApiReadControllerClient<number, any, any>;
10
+ let fetchService: FetchHttpService;
11
+
12
+ beforeEach(() => {
13
+ fetchService = new FetchHttpService();
14
+ client = new ApiReadControllerClient('/VehiclesSlim', fetchService);
15
+ });
16
+
17
+ it('should use injected HTTP service for readAll', async () => {
18
+ const result = (await client.readAll()) as { ok: boolean; result: any };
19
+
20
+ expect(result.ok).toBe(true);
21
+ expect(result.result).toBeDefined();
22
+ });
23
+
24
+ it('should use injected HTTP service for readSingle', async () => {
25
+ const testId = 1;
26
+ const result = (await client.readSingle(testId)) as { ok: boolean; result: any };
27
+
28
+ expect(result.ok).toBe(true);
29
+ expect(result.result).toBeDefined();
30
+ expect(typeof result.result).toBe('object');
31
+ expect(result.result.id).toBe(testId);
32
+ });
33
+
34
+ it('should use injected HTTP service for readSelected', async () => {
35
+ const definition: ReadSelectedDefinitionDto<VehicleSlimDto> = {
36
+ searchDefinition: {
37
+ searches: [
38
+ {
39
+ propertyCriteria: [
40
+ {
41
+ propertyName: 'Brand',
42
+ comparisonOperator: ReadSelectedComparisonOperator.Equal,
43
+ valueType: ReadSelectedPropertyType.String,
44
+ value: 'Audi',
45
+ },
46
+ {
47
+ propertyName: 'Brand',
48
+ comparisonOperator: ReadSelectedComparisonOperator.Equal,
49
+ valueType: ReadSelectedPropertyType.String,
50
+ value: 'BMW',
51
+ },
52
+ ],
53
+ logicalOperator: ReadSelectedLogicalOperator.Or,
54
+ },
55
+ ],
56
+ logicalOperator: ReadSelectedLogicalOperator.And,
57
+ },
58
+ };
59
+
60
+ const result = (await client.readSelected(definition)) as { ok: boolean; result: any };
61
+
62
+ expect(result.ok).toBe(true);
63
+ expect(result.result).toBeDefined();
64
+ });
65
+
66
+ it('should handle HTTP errors properly', async () => {
67
+ const invalidClient = new ApiReadControllerClient('/InvalidPath', fetchService);
68
+ const result = await invalidClient.readAll();
69
+ expect(result.ok).toBe(false);
70
+ });
71
+ });
@@ -0,0 +1,137 @@
1
+ import { apiInitializationService } from '../ApiInitializationService';
2
+ import {
3
+ ApiErrorResponseDto,
4
+ ApiResponseDto,
5
+ ApiSuccessResponseDto,
6
+ EmptyMetadataDto,
7
+ ReadMetadataDto,
8
+ ReadSelectedDefinitionDto,
9
+ } from '../../../data/api/dto';
10
+ import { IIdentifiable } from '../../../data/api/interface';
11
+ import { IHttpService } from '../HttpService';
12
+ import { fail, ok } from '@utils/result';
13
+ import { ErrorCode } from '@data';
14
+
15
+ /**
16
+ * Base client for read-only API controllers
17
+ */
18
+ export class ApiReadControllerClient<TKey, TResultDto extends IIdentifiable<TKey>, TSingleResultDto extends IIdentifiable<TKey>> {
19
+ /**
20
+ * Constructor
21
+ * @param baseControllerPath Base path to API controller
22
+ * @param httpService HTTP service implementation to use for requests
23
+ */
24
+ constructor(
25
+ protected readonly baseControllerPath: string,
26
+ httpService?: IHttpService,
27
+ ) {
28
+ this._httpService = httpService;
29
+ }
30
+
31
+ // #region HTTP service
32
+ private readonly _httpService?: IHttpService = undefined;
33
+
34
+ /**
35
+ * Gets globally selected HTTP service
36
+ */
37
+ protected get httpService(): IHttpService {
38
+ return this._httpService || apiInitializationService._getHttpService();
39
+ }
40
+
41
+ // #endregion
42
+
43
+ // #region API
44
+ /**
45
+ * Fetches all entities from the read controller.
46
+ * @param {AbortSignal} [signal] - Optional cancellation signal
47
+ * @returns {Promise<ApiSuccessResponseDto<TDto>[], ReadMetadataDto>>} List of all entities with metadata
48
+ */
49
+ public async readAll(signal?: AbortSignal): Promise<ApiResponseDto<TResultDto[], ReadMetadataDto>> {
50
+ try {
51
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/ReadAll`);
52
+ const res = await this.httpService.request(url, { method: 'GET', credentials: 'include', signal });
53
+
54
+ if (!res.ok) {
55
+ return fail<ApiSuccessResponseDto<TResultDto[], ReadMetadataDto>, ApiErrorResponseDto>(await res.json());
56
+ }
57
+
58
+ return ok<ApiSuccessResponseDto<TResultDto[], ReadMetadataDto>, ApiErrorResponseDto>(await res.json());
59
+ } catch (err) {
60
+ console.error(err);
61
+
62
+ return fail<ApiSuccessResponseDto<TResultDto[], ReadMetadataDto>, ApiErrorResponseDto>({
63
+ error: {
64
+ code: ErrorCode.UnknownError,
65
+ message: 'Unknown error fetching all records',
66
+ metadata: {} as EmptyMetadataDto,
67
+ },
68
+ });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Fetches selected entities based on a filter definition.
74
+ * @param {any} definition - The filter definition object
75
+ * @param {AbortSignal} [signal] - Optional cancellation signal
76
+ * @returns {Promise<ApiSuccessResponseDto<TDto>[], ReadMetadataDto>>} The result of the read operation with metadata
77
+ */
78
+ public async readSelected(definition: ReadSelectedDefinitionDto<TResultDto>, signal?: AbortSignal): Promise<ApiResponseDto<TResultDto[], ReadMetadataDto>> {
79
+ try {
80
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/ReadSelected`);
81
+ const res = await this.httpService.request(url, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify(definition),
85
+ credentials: 'include',
86
+ signal,
87
+ });
88
+
89
+ if (!res.ok) {
90
+ return fail<ApiSuccessResponseDto<TResultDto[], ReadMetadataDto>, ApiErrorResponseDto>(await res.json());
91
+ }
92
+
93
+ return ok<ApiSuccessResponseDto<TResultDto[], ReadMetadataDto>, ApiErrorResponseDto>(await res.json());
94
+ } catch (err) {
95
+ console.error(err);
96
+
97
+ return fail<ApiSuccessResponseDto<TResultDto[], ReadMetadataDto>, ApiErrorResponseDto>({
98
+ error: {
99
+ code: ErrorCode.UnknownError,
100
+ message: 'Unknown error fetching selected records',
101
+ metadata: {} as EmptyMetadataDto,
102
+ },
103
+ });
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Fetches a single entity by its ID.
109
+ * @param {TKey} id - The ID of the entity
110
+ * @param {AbortSignal} [signal] - Optional cancellation signal
111
+ * @returns {Promise<ApiSuccessResponseDto<TDto, EmptyMetadataDto>>} The requested entity
112
+ */
113
+ public async readSingle(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<TSingleResultDto, EmptyMetadataDto>> {
114
+ try {
115
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/ReadSingle?id=${encodeURIComponent(String(id))}`);
116
+ const res = await this.httpService.request(url, { method: 'GET', credentials: 'include', signal });
117
+
118
+ if (!res.ok) {
119
+ return fail<ApiSuccessResponseDto<TSingleResultDto, EmptyMetadataDto>, ApiErrorResponseDto>(await res.json());
120
+ }
121
+
122
+ return ok<ApiSuccessResponseDto<TSingleResultDto, EmptyMetadataDto>, ApiErrorResponseDto>(await res.json());
123
+ } catch (err) {
124
+ console.error(err);
125
+
126
+ return fail<ApiSuccessResponseDto<TSingleResultDto, EmptyMetadataDto>, ApiErrorResponseDto>({
127
+ error: {
128
+ code: ErrorCode.UnknownError,
129
+ message: `Unknown error fetching record with id ${id}`,
130
+ metadata: {} as EmptyMetadataDto,
131
+ },
132
+ });
133
+ }
134
+ }
135
+
136
+ // #endregion
137
+ }
@@ -0,0 +1,65 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { FetchHttpService } from './FetchHttpService';
3
+
4
+ const baseUrl = 'http://localhost:5000/api';
5
+ const credentials = {
6
+ username: 'admin@test.com',
7
+ password: 'Password1234!',
8
+ };
9
+
10
+ describe('FetchHttpService', () => {
11
+ let fetchService: FetchHttpService;
12
+
13
+ beforeEach(() => {
14
+ fetchService = new FetchHttpService();
15
+ });
16
+
17
+ it('should make a GET request successfully', async () => {
18
+ const result = await fetchService.request(`${baseUrl}/AnythingManuallyMappedRead/ReadAll`);
19
+
20
+ expect(result.ok).toBe(true);
21
+ expect(result.status).toBe(200);
22
+ expect(result.statusText).toBe('OK');
23
+
24
+ const jsonData = await result.json();
25
+ expect(jsonData.result).toBeDefined();
26
+ expect(jsonData.error).toBeNull();
27
+ });
28
+
29
+ it('should make a POST request with body', async () => {
30
+ const result = await fetchService.request(`${baseUrl}/auth/signin`, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify(credentials),
34
+ });
35
+
36
+ expect(result.ok).toBe(true);
37
+ expect(result.status).toBe(204);
38
+ });
39
+
40
+ it('should handle non-ok responses', async () => {
41
+ const result = await fetchService.request(`${baseUrl}/auth/signin`, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({ username: credentials.username, password: `${credentials.password}wrong` }),
45
+ });
46
+
47
+ expect(result.ok).toBe(false);
48
+ expect(result.status).toBe(400);
49
+ expect(result.statusText).toBe('Bad Request');
50
+
51
+ const jsonData = await result.json();
52
+ expect(jsonData.result).toBeNull();
53
+ expect(jsonData.error).toBeDefined();
54
+ expect(jsonData.error.code).toBe(400);
55
+ expect(jsonData.error.message).toBe('Invalid email or password');
56
+ });
57
+
58
+ it('should handle fetch errors from backend', async () => {
59
+ const result = await fetchService.request('http://localhost:5000/api/error');
60
+
61
+ expect(result.ok).toBe(false);
62
+ expect(result.status).toBe(404);
63
+ expect(result.statusText).toBe('Not Found');
64
+ });
65
+ });
@@ -0,0 +1,34 @@
1
+ // Ignore eslint any error in this file
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ import { HttpRequestConfig, HttpResponse, IHttpService } from '@/services';
5
+
6
+ /**
7
+ * Default HTTP service implementation using the Fetch API
8
+ */
9
+ export class FetchHttpService implements IHttpService {
10
+ public async request<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>> {
11
+ const response = await fetch(url, {
12
+ method: config?.method || 'GET',
13
+ headers: config?.headers,
14
+ body: config?.body,
15
+ credentials: config?.credentials || 'include',
16
+ signal: config?.signal,
17
+ });
18
+
19
+ // Convert Headers to a plain object
20
+ const headers: Record<string, string> = {};
21
+ response.headers.forEach((value, key) => {
22
+ headers[key] = value;
23
+ });
24
+
25
+ return {
26
+ ok: response.ok,
27
+ status: response.status,
28
+ statusText: response.statusText,
29
+ headers,
30
+ json: () => response.json(),
31
+ text: () => response.text(),
32
+ };
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * HTTP request configuration interface
3
+ */
4
+ export interface HttpRequestConfig {
5
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
6
+ headers?: Record<string, string>;
7
+ body?: string;
8
+ credentials?: RequestCredentials;
9
+ signal?: AbortSignal;
10
+ }
@@ -0,0 +1,14 @@
1
+ // Ignore eslint any error in this file
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ /**
5
+ * HTTP response interface
6
+ */
7
+ export interface HttpResponse<T = any> {
8
+ ok: boolean;
9
+ status: number;
10
+ statusText: string;
11
+ headers: Record<string, string>;
12
+ json(): Promise<T>;
13
+ text(): Promise<string>;
14
+ }
@@ -0,0 +1,17 @@
1
+ // Ignore eslint any error in this file
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ import { HttpRequestConfig, HttpResponse } from '@/services';
5
+
6
+ /**
7
+ * Abstract HTTP service interface that can be implemented by different HTTP clients
8
+ */
9
+ export interface IHttpService {
10
+ /**
11
+ * Performs an HTTP request
12
+ * @param url - The URL to make the request to
13
+ * @param config - Request configuration
14
+ * @returns Promise that resolves to the HTTP response
15
+ */
16
+ request<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>>;
17
+ }