@cratis/arc 20.55.1 → 20.57.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 (149) hide show
  1. package/Globals.ts +15 -0
  2. package/dist/cjs/Globals.js +2 -0
  3. package/dist/cjs/Globals.js.map +1 -1
  4. package/dist/cjs/queries/ObservableQueryFor.js +13 -23
  5. package/dist/cjs/queries/ObservableQueryFor.js.map +1 -1
  6. package/dist/cjs/queries/QueryFor.js +13 -25
  7. package/dist/cjs/queries/QueryFor.js.map +1 -1
  8. package/dist/cjs/queries/QueryHttpMethod.js +23 -0
  9. package/dist/cjs/queries/QueryHttpMethod.js.map +1 -0
  10. package/dist/cjs/queries/QueryHttpMethodResolver.js +25 -0
  11. package/dist/cjs/queries/QueryHttpMethodResolver.js.map +1 -0
  12. package/dist/cjs/queries/QueryHttpRequest.js +0 -0
  13. package/dist/cjs/queries/QueryHttpRequest.js.map +1 -0
  14. package/dist/cjs/queries/index.js +9 -0
  15. package/dist/cjs/queries/index.js.map +1 -1
  16. package/dist/esm/Globals.d.ts +4 -0
  17. package/dist/esm/Globals.d.ts.map +1 -1
  18. package/dist/esm/Globals.js +2 -0
  19. package/dist/esm/Globals.js.map +1 -1
  20. package/dist/esm/queries/IQuery.d.ts +2 -0
  21. package/dist/esm/queries/IQuery.d.ts.map +1 -1
  22. package/dist/esm/queries/ObservableQueryFor.d.ts +3 -0
  23. package/dist/esm/queries/ObservableQueryFor.d.ts.map +1 -1
  24. package/dist/esm/queries/ObservableQueryFor.js +13 -23
  25. package/dist/esm/queries/ObservableQueryFor.js.map +1 -1
  26. package/dist/esm/queries/QueryFor.d.ts +3 -0
  27. package/dist/esm/queries/QueryFor.d.ts.map +1 -1
  28. package/dist/esm/queries/QueryFor.js +13 -25
  29. package/dist/esm/queries/QueryFor.js.map +1 -1
  30. package/dist/esm/queries/QueryHttpMethod.d.ts +6 -0
  31. package/dist/esm/queries/QueryHttpMethod.d.ts.map +1 -0
  32. package/dist/esm/queries/QueryHttpMethod.js +21 -0
  33. package/dist/esm/queries/QueryHttpMethod.js.map +1 -0
  34. package/dist/esm/queries/QueryHttpMethodResolver.d.ts +14 -0
  35. package/dist/esm/queries/QueryHttpMethodResolver.d.ts.map +1 -0
  36. package/dist/esm/queries/QueryHttpMethodResolver.js +22 -0
  37. package/dist/esm/queries/QueryHttpMethodResolver.js.map +1 -0
  38. package/dist/esm/queries/QueryHttpRequest.d.ts +21 -0
  39. package/dist/esm/queries/QueryHttpRequest.d.ts.map +1 -0
  40. package/dist/esm/queries/QueryHttpRequest.js +0 -0
  41. package/dist/esm/queries/QueryHttpRequest.js.map +1 -0
  42. package/dist/esm/queries/for_QueryFor/when_performing/with_query_http_method.d.ts +2 -0
  43. package/dist/esm/queries/for_QueryFor/when_performing/with_query_http_method.d.ts.map +1 -0
  44. package/dist/esm/queries/for_QueryFor/when_performing/with_query_http_method.js +51 -0
  45. package/dist/esm/queries/for_QueryFor/when_performing/with_query_http_method.js.map +1 -0
  46. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_get_method.d.ts +2 -0
  47. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_get_method.d.ts.map +1 -0
  48. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_get_method.js +23 -0
  49. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_get_method.js.map +1 -0
  50. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method.d.ts +2 -0
  51. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method.d.ts.map +1 -0
  52. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method.js +28 -0
  53. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method.js.map +1 -0
  54. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method_and_paging_and_sorting.d.ts +2 -0
  55. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method_and_paging_and_sorting.d.ts.map +1 -0
  56. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method_and_paging_and_sorting.js +26 -0
  57. package/dist/esm/queries/for_buildQueryHttpRequest/when_building/with_query_method_and_paging_and_sorting.js.map +1 -0
  58. package/dist/esm/queries/for_executeQueryHttpRequest/options.d.ts +3 -0
  59. package/dist/esm/queries/for_executeQueryHttpRequest/options.d.ts.map +1 -0
  60. package/dist/esm/queries/for_executeQueryHttpRequest/options.js +16 -0
  61. package/dist/esm/queries/for_executeQueryHttpRequest/options.js.map +1 -0
  62. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver.d.ts +2 -0
  63. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver.d.ts.map +1 -0
  64. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver.js +23 -0
  65. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver.js.map +1 -0
  66. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver_and_an_explicit_override.d.ts +2 -0
  67. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver_and_an_explicit_override.d.ts.map +1 -0
  68. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver_and_an_explicit_override.js +23 -0
  69. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver_and_an_explicit_override.js.map +1 -0
  70. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_after_query_was_found_unsupported.d.ts +2 -0
  71. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_after_query_was_found_unsupported.d.ts.map +1 -0
  72. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_after_query_was_found_unsupported.js +21 -0
  73. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_after_query_was_found_unsupported.js.map +1 -0
  74. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_a_network_failure.d.ts +2 -0
  75. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_a_network_failure.d.ts.map +1 -0
  76. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_a_network_failure.js +23 -0
  77. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_a_network_failure.js.map +1 -0
  78. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_aborted_request.d.ts +2 -0
  79. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_aborted_request.d.ts.map +1 -0
  80. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_aborted_request.js +25 -0
  81. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_aborted_request.js.map +1 -0
  82. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_application_error.d.ts +2 -0
  83. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_application_error.d.ts.map +1 -0
  84. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_application_error.js +21 -0
  85. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_application_error.js.map +1 -0
  86. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_multiple_backends.d.ts +2 -0
  87. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_multiple_backends.d.ts.map +1 -0
  88. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_multiple_backends.js +31 -0
  89. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_multiple_backends.js.map +1 -0
  90. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_supported.d.ts +2 -0
  91. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_supported.d.ts.map +1 -0
  92. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_supported.js +19 -0
  93. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_supported.js.map +1 -0
  94. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_unsupported.d.ts +2 -0
  95. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_unsupported.d.ts.map +1 -0
  96. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_unsupported.js +22 -0
  97. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_unsupported.js.map +1 -0
  98. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_get_method.d.ts +2 -0
  99. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_get_method.d.ts.map +1 -0
  100. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_get_method.js +19 -0
  101. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_get_method.js.map +1 -0
  102. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_query_method_and_server_rejecting_the_verb.d.ts +2 -0
  103. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_query_method_and_server_rejecting_the_verb.d.ts.map +1 -0
  104. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_query_method_and_server_rejecting_the_verb.js +19 -0
  105. package/dist/esm/queries/for_executeQueryHttpRequest/when_performing/with_query_method_and_server_rejecting_the_verb.js.map +1 -0
  106. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url.d.ts +2 -0
  107. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url.d.ts.map +1 -0
  108. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url.js +12 -0
  109. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url.js.map +1 -0
  110. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url_and_a_custom_method.d.ts +2 -0
  111. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url_and_a_custom_method.d.ts.map +1 -0
  112. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url_and_a_custom_method.js +12 -0
  113. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url_and_a_custom_method.js.map +1 -0
  114. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_short_url.d.ts +2 -0
  115. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_short_url.d.ts.map +1 -0
  116. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_short_url.js +11 -0
  117. package/dist/esm/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_short_url.js.map +1 -0
  118. package/dist/esm/queries/index.d.ts +3 -0
  119. package/dist/esm/queries/index.d.ts.map +1 -1
  120. package/dist/esm/queries/index.js +3 -0
  121. package/dist/esm/queries/index.js.map +1 -1
  122. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  123. package/package.json +1 -1
  124. package/queries/IQuery.ts +7 -0
  125. package/queries/ObservableQueryFor.ts +16 -25
  126. package/queries/QueryFor.ts +16 -27
  127. package/queries/QueryHttpMethod.ts +24 -0
  128. package/queries/QueryHttpMethodResolver.ts +51 -0
  129. package/queries/QueryHttpRequest.ts +0 -0
  130. package/queries/for_QueryFor/when_performing/with_query_http_method.ts +63 -0
  131. package/queries/for_buildQueryHttpRequest/when_building/with_get_method.ts +31 -0
  132. package/queries/for_buildQueryHttpRequest/when_building/with_query_method.ts +42 -0
  133. package/queries/for_buildQueryHttpRequest/when_building/with_query_method_and_paging_and_sorting.ts +35 -0
  134. package/queries/for_executeQueryHttpRequest/options.ts +24 -0
  135. package/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver.ts +31 -0
  136. package/queries/for_executeQueryHttpRequest/when_performing/with_a_resolver_and_an_explicit_override.ts +31 -0
  137. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_after_query_was_found_unsupported.ts +33 -0
  138. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_a_network_failure.ts +31 -0
  139. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_aborted_request.ts +31 -0
  140. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_an_application_error.ts +29 -0
  141. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_multiple_backends.ts +43 -0
  142. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_supported.ts +26 -0
  143. package/queries/for_executeQueryHttpRequest/when_performing/with_auto_and_query_unsupported.ts +30 -0
  144. package/queries/for_executeQueryHttpRequest/when_performing/with_get_method.ts +26 -0
  145. package/queries/for_executeQueryHttpRequest/when_performing/with_query_method_and_server_rejecting_the_verb.ts +26 -0
  146. package/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url.ts +17 -0
  147. package/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_long_url_and_a_custom_method.ts +17 -0
  148. package/queries/for_lengthBasedQueryHttpMethod/when_resolving/with_a_short_url.ts +16 -0
  149. package/queries/index.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/arc",
3
- "version": "20.55.1",
3
+ "version": "20.57.0",
4
4
  "description": "",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
package/queries/IQuery.ts CHANGED
@@ -4,11 +4,18 @@
4
4
  import { ICanBeConfigured } from '../ICanBeConfigured';
5
5
  import { Paging } from './Paging';
6
6
  import { Sorting } from './Sorting';
7
+ import { QueryHttpMethod } from './QueryHttpMethod';
7
8
 
8
9
  /**
9
10
  * Defines the commonalities between all query types.
10
11
  */
11
12
  export interface IQuery extends ICanBeConfigured {
13
+ /**
14
+ * Set the HTTP method used to perform the query, overriding the global default.
15
+ * @param method The {@link QueryHttpMethod} to use.
16
+ */
17
+ setHttpMethod(method: QueryHttpMethod): void;
18
+
12
19
  /**
13
20
  * Gets the sorting for the query.
14
21
  */
@@ -14,11 +14,12 @@ import { Sorting } from './Sorting';
14
14
  import { Paging } from './Paging';
15
15
  import { SortDirection } from './SortDirection';
16
16
  import { Globals } from '../Globals';
17
- import { joinPaths } from '../joinPaths';
18
17
  import { UrlHelpers } from '../UrlHelpers';
19
18
  import { GetHttpHeaders } from '../GetHttpHeaders';
20
19
  import { ParameterDescriptor } from '../reflection/ParameterDescriptor';
21
20
  import { ParametersHelper } from '../reflection/ParametersHelper';
21
+ import { QueryHttpMethod } from './QueryHttpMethod';
22
+ import { executeQueryHttpRequest } from './QueryHttpRequest';
22
23
 
23
24
  /* eslint-disable @typescript-eslint/no-explicit-any */
24
25
 
@@ -32,6 +33,7 @@ export abstract class ObservableQueryFor<TDataType, TParameters = object> implem
32
33
  private _origin: string;
33
34
  private _connection?: IObservableQueryConnection<TDataType>;
34
35
  private _httpHeadersCallback: GetHttpHeaders;
36
+ private _httpMethod?: QueryHttpMethod;
35
37
 
36
38
  abstract readonly route: string;
37
39
  abstract readonly defaultValue: TDataType;
@@ -84,6 +86,11 @@ export abstract class ObservableQueryFor<TDataType, TParameters = object> implem
84
86
  this._httpHeadersCallback = callback;
85
87
  }
86
88
 
89
+ /** @inheritdoc */
90
+ setHttpMethod(method: QueryHttpMethod): void {
91
+ this._httpMethod = method;
92
+ }
93
+
87
94
  /** @inheritdoc */
88
95
  subscribe(callback: OnNextResult<QueryResult<TDataType>>, args?: TParameters): ObservableQuerySubscription<TDataType> {
89
96
  if (this._connection) {
@@ -142,31 +149,9 @@ export abstract class ObservableQueryFor<TDataType, TParameters = object> implem
142
149
  });
143
150
  }
144
151
 
145
- const { route, unusedParameters } = UrlHelpers.replaceRouteParameters(this.route, args as object);
146
- let actualRoute = joinPaths(this._apiBasePath, route);
147
-
148
- const additionalParams: Record<string, string | number> = {};
149
- if (this.paging.hasPaging) {
150
- additionalParams.page = this.paging.page;
151
- additionalParams.pageSize = this.paging.pageSize;
152
- }
153
-
154
- if (this.sorting.hasSorting) {
155
- additionalParams.sortBy = this.sorting.field;
156
- additionalParams.sortDirection = (this.sorting.direction === SortDirection.descending) ? 'desc' : 'asc';
157
- }
158
-
159
152
  // Collect parameter values from parameterDescriptors that are set
160
153
  const parameterValues = ParametersHelper.collectParameterValues(this);
161
154
 
162
- const queryParams = UrlHelpers.buildQueryParams({ ...unusedParameters, ...parameterValues }, additionalParams);
163
- const queryString = queryParams.toString();
164
- if (queryString) {
165
- actualRoute += (actualRoute.includes('?') ? '&' : '?') + queryString;
166
- }
167
-
168
- const url = UrlHelpers.createUrlFrom(this._origin, this._apiBasePath, actualRoute);
169
-
170
155
  const headers = {
171
156
  ...(this._httpHeadersCallback?.() || {}),
172
157
  'Accept': 'application/json',
@@ -177,8 +162,14 @@ export abstract class ObservableQueryFor<TDataType, TParameters = object> implem
177
162
  headers[Globals.microserviceHttpHeader] = this._microservice;
178
163
  }
179
164
 
180
- const response = await fetch(url, {
181
- method: 'GET',
165
+ const response = await executeQueryHttpRequest(this._httpMethod, {
166
+ route: this.route,
167
+ apiBasePath: this._apiBasePath,
168
+ origin: this._origin,
169
+ args: (args as object) ?? {},
170
+ parameterValues,
171
+ paging: this.paging,
172
+ sorting: this.sorting,
182
173
  headers
183
174
  });
184
175
 
@@ -9,12 +9,11 @@ import { Constructor } from '@cratis/fundamentals';
9
9
  import { Paging } from './Paging';
10
10
  import { Globals } from '../Globals';
11
11
  import { Sorting } from './Sorting';
12
- import { SortDirection } from './SortDirection';
13
- import { joinPaths } from '../joinPaths';
14
- import { UrlHelpers } from '../UrlHelpers';
15
12
  import { GetHttpHeaders } from '../GetHttpHeaders';
16
13
  import { ParameterDescriptor } from '../reflection/ParameterDescriptor';
17
14
  import { ParametersHelper } from '../reflection/ParametersHelper';
15
+ import { QueryHttpMethod } from './QueryHttpMethod';
16
+ import { executeQueryHttpRequest } from './QueryHttpRequest';
18
17
 
19
18
  /**
20
19
  * Represents an implementation of {@link IQueryFor}.
@@ -25,6 +24,7 @@ export abstract class QueryFor<TDataType, TParameters = object> implements IQuer
25
24
  private _apiBasePath: string;
26
25
  private _origin: string;
27
26
  private _httpHeadersCallback: GetHttpHeaders;
27
+ private _httpMethod?: QueryHttpMethod;
28
28
  abstract readonly route: string;
29
29
  /** Backend fully-qualified query name used as cache key. Overridden in generated proxies. */
30
30
  readonly queryName?: string;
@@ -74,6 +74,11 @@ export abstract class QueryFor<TDataType, TParameters = object> implements IQuer
74
74
  this._httpHeadersCallback = callback;
75
75
  }
76
76
 
77
+ /** @inheritdoc */
78
+ setHttpMethod(method: QueryHttpMethod): void {
79
+ this._httpMethod = method;
80
+ }
81
+
77
82
  /** @inheritdoc */
78
83
  async perform(args?: TParameters): Promise<QueryResult<TDataType>> {
79
84
  const noSuccess = { ...QueryResult.noSuccess, ...{ data: this.defaultValue } } as QueryResult<TDataType>;
@@ -117,31 +122,9 @@ export abstract class QueryFor<TDataType, TParameters = object> implements IQuer
117
122
 
118
123
  this.abortController = new AbortController();
119
124
 
120
- const { route, unusedParameters } = UrlHelpers.replaceRouteParameters(this.route, args as object);
121
- let actualRoute = joinPaths(this._apiBasePath, route);
122
-
123
- const additionalParams: Record<string, string | number> = {};
124
- if (this.paging.hasPaging) {
125
- additionalParams.page = this.paging.page;
126
- additionalParams.pageSize = this.paging.pageSize;
127
- }
128
-
129
- if (this.sorting.hasSorting) {
130
- additionalParams.sortBy = this.sorting.field;
131
- additionalParams.sortDirection = (this.sorting.direction === SortDirection.descending) ? 'desc' : 'asc';
132
- }
133
-
134
125
  // Collect parameter values from parameterDescriptors that are set
135
126
  const parameterValues = ParametersHelper.collectParameterValues(this);
136
127
 
137
- const queryParams = UrlHelpers.buildQueryParams({ ...unusedParameters, ...parameterValues }, additionalParams);
138
- const queryString = queryParams.toString();
139
- if (queryString) {
140
- actualRoute += (actualRoute.includes('?') ? '&' : '?') + queryString;
141
- }
142
-
143
- const url = UrlHelpers.createUrlFrom(this._origin, this._apiBasePath, actualRoute);
144
-
145
128
  const headers = {
146
129
  ... this._httpHeadersCallback?.(), ...
147
130
  {
@@ -154,8 +137,14 @@ export abstract class QueryFor<TDataType, TParameters = object> implements IQuer
154
137
  headers[Globals.microserviceHttpHeader] = this._microservice;
155
138
  }
156
139
 
157
- const response = await fetch(url, {
158
- method: 'GET',
140
+ const response = await executeQueryHttpRequest(this._httpMethod, {
141
+ route: this.route,
142
+ apiBasePath: this._apiBasePath,
143
+ origin: this._origin,
144
+ args: (args as object) ?? {},
145
+ parameterValues,
146
+ paging: this.paging,
147
+ sorting: this.sorting,
159
148
  headers,
160
149
  signal: this.abortController.signal
161
150
  });
@@ -0,0 +1,24 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * Defines the HTTP method used to perform a query over HTTP.
6
+ */
7
+ export enum QueryHttpMethod {
8
+ /**
9
+ * Use the GET method, carrying arguments in the URL query string (default).
10
+ */
11
+ Get = 'GET',
12
+
13
+ /**
14
+ * Use the QUERY method (RFC 10008), carrying arguments in a JSON request body.
15
+ */
16
+ Query = 'QUERY',
17
+
18
+ /**
19
+ * Prefer the QUERY method and automatically fall back to GET when the server or network path
20
+ * does not support it. The outcome is remembered for the rest of the session so subsequent
21
+ * queries go straight to the working transport.
22
+ */
23
+ Auto = 'AUTO',
24
+ }
@@ -0,0 +1,51 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { QueryHttpMethod } from './QueryHttpMethod';
5
+
6
+ /**
7
+ * The information a {@link QueryHttpMethodResolver} uses to decide the HTTP method for a query.
8
+ */
9
+ export interface QueryHttpMethodResolverContext {
10
+ /** The URL the query would use as a GET request (arguments in the query string). */
11
+ readonly url: URL;
12
+ /** The route template for the query. */
13
+ readonly route: string;
14
+ /** The arguments for the query. */
15
+ readonly args: object;
16
+ }
17
+
18
+ /**
19
+ * A policy that decides, per query, which {@link QueryHttpMethod} to use.
20
+ * Consulted only when the query has no explicit method set via {@code setHttpMethod}.
21
+ */
22
+ export type QueryHttpMethodResolver = (context: QueryHttpMethodResolverContext) => QueryHttpMethod;
23
+
24
+ /**
25
+ * The default URL length, in characters, above which {@link lengthBasedQueryHttpMethod} prefers QUERY.
26
+ * 2000 is the widely-safe URL length across browsers, servers and proxies.
27
+ */
28
+ export const defaultQueryHttpMethodUrlThreshold = 2000;
29
+
30
+ /**
31
+ * Options for {@link lengthBasedQueryHttpMethod}.
32
+ */
33
+ export interface LengthBasedQueryHttpMethodOptions {
34
+ /** URL length (in characters) above which QUERY is preferred. Defaults to {@link defaultQueryHttpMethodUrlThreshold}. */
35
+ threshold?: number;
36
+ /** The method to use when the URL exceeds the threshold. Defaults to {@link QueryHttpMethod.Auto} so it still falls back to GET. */
37
+ whenLong?: QueryHttpMethod;
38
+ }
39
+
40
+ /**
41
+ * Creates a {@link QueryHttpMethodResolver} that keeps short queries on GET and prefers QUERY only when
42
+ * the GET URL would exceed a threshold — targeting the arguments-too-large-for-a-URL case while leaving
43
+ * small, cacheable queries on GET.
44
+ * @param options Optional {@link LengthBasedQueryHttpMethodOptions}.
45
+ * @returns A {@link QueryHttpMethodResolver}.
46
+ */
47
+ export function lengthBasedQueryHttpMethod(options?: LengthBasedQueryHttpMethodOptions): QueryHttpMethodResolver {
48
+ const threshold = options?.threshold ?? defaultQueryHttpMethodUrlThreshold;
49
+ const whenLong = options?.whenLong ?? QueryHttpMethod.Auto;
50
+ return context => (context.url.href.length > threshold ? whenLong : QueryHttpMethod.Get);
51
+ }
Binary file
@@ -0,0 +1,63 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { a_query_for } from '../given/a_query_for';
5
+ import { given } from '../../../given';
6
+ import * as sinon from 'sinon';
7
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
8
+ import { QueryResult } from '../../QueryResult';
9
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
10
+
11
+ describe('with query http method', given(a_query_for, context => {
12
+ let result: QueryResult<string>;
13
+ let fetchStub: sinon.SinonStub;
14
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
15
+ const mockResponse = {
16
+ data: 'test-result',
17
+ isSuccess: true,
18
+ isAuthorized: true,
19
+ isValid: true,
20
+ hasExceptions: false,
21
+ validationResults: [],
22
+ exceptionMessages: [],
23
+ exceptionStackTrace: '',
24
+ paging: { totalItems: 0, totalPages: 0, page: 0, size: 0 }
25
+ };
26
+
27
+ beforeEach(async () => {
28
+ fetchHelper = createFetchHelper();
29
+ fetchStub = fetchHelper.stubFetch();
30
+ fetchStub.resolves({
31
+ json: sinon.stub().resolves(mockResponse),
32
+ ok: true,
33
+ status: 200
34
+ } as unknown as Response);
35
+
36
+ context.query.setOrigin('https://api.example.com');
37
+ context.query.setApiBasePath('/api/v1');
38
+ context.query.setHttpMethod(QueryHttpMethod.Query);
39
+
40
+ result = await context.query.perform({ id: 'test-id' });
41
+ });
42
+
43
+ afterEach(() => {
44
+ fetchHelper.restore();
45
+ });
46
+
47
+ it('should return successful result', () => result.isSuccess.should.be.true);
48
+
49
+ it('should call fetch with QUERY method', () => {
50
+ const options = fetchStub.getCall(0).args[1];
51
+ options.method.should.equal('QUERY');
52
+ });
53
+
54
+ it('should keep the route parameter in the path', () => {
55
+ const call = fetchStub.getCall(0);
56
+ call.args[0].href.should.equal('https://api.example.com/api/v1/api/test/test-id');
57
+ });
58
+
59
+ it('should send a JSON request body', () => {
60
+ const options = fetchStub.getCall(0).args[1];
61
+ (options.body === undefined).should.be.false;
62
+ });
63
+ }));
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { buildQueryHttpRequest } from '../../QueryHttpRequest';
5
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
6
+ import { Paging } from '../../Paging';
7
+ import { Sorting } from '../../Sorting';
8
+
9
+ describe('when building with get method', () => {
10
+ let result: { url: URL; init: RequestInit };
11
+
12
+ beforeEach(() => {
13
+ result = buildQueryHttpRequest(QueryHttpMethod.Get, {
14
+ route: '/api/test/{id}',
15
+ apiBasePath: '/api/v1',
16
+ origin: 'https://api.example.com',
17
+ args: { id: 'abc', filter: 'active' },
18
+ parameterValues: {},
19
+ paging: Paging.noPaging,
20
+ sorting: Sorting.none,
21
+ headers: { 'Accept': 'application/json' }
22
+ });
23
+ });
24
+
25
+ it('should use the GET method', () => result.init.method!.should.equal('GET'));
26
+
27
+ it('should substitute route parameters and append remaining arguments to the query string', () =>
28
+ result.url.href.should.equal('https://api.example.com/api/v1/api/test/abc?filter=active'));
29
+
30
+ it('should not have a body', () => (result.init.body === undefined).should.be.true);
31
+ });
@@ -0,0 +1,42 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { buildQueryHttpRequest } from '../../QueryHttpRequest';
5
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
6
+ import { Paging } from '../../Paging';
7
+ import { Sorting } from '../../Sorting';
8
+
9
+ describe('when building with query method', () => {
10
+ let result: { url: URL; init: RequestInit };
11
+ /* eslint-disable @typescript-eslint/no-explicit-any */
12
+ let body: any;
13
+ /* eslint-enable @typescript-eslint/no-explicit-any */
14
+
15
+ beforeEach(() => {
16
+ result = buildQueryHttpRequest(QueryHttpMethod.Query, {
17
+ route: '/api/test/{id}',
18
+ apiBasePath: '/api/v1',
19
+ origin: 'https://api.example.com',
20
+ args: { id: 'abc', filter: 'active' },
21
+ parameterValues: {},
22
+ paging: Paging.noPaging,
23
+ sorting: Sorting.none,
24
+ headers: { 'Accept': 'application/json' }
25
+ });
26
+ body = JSON.parse(result.init.body as string);
27
+ });
28
+
29
+ it('should use the QUERY method', () => result.init.method!.should.equal('QUERY'));
30
+
31
+ it('should substitute route parameters in the path', () =>
32
+ result.url.href.should.equal('https://api.example.com/api/v1/api/test/abc'));
33
+
34
+ it('should not put arguments in the query string', () => result.url.search.should.equal(''));
35
+
36
+ it('should carry non-route arguments in the body', () => body.arguments.filter.should.equal('active'));
37
+
38
+ it('should not carry route arguments in the body', () => (body.arguments.id === undefined).should.be.true);
39
+
40
+ it('should set the content type to json', () =>
41
+ new Headers(result.init.headers).get('Content-Type')!.should.equal('application/json'));
42
+ });
@@ -0,0 +1,35 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { buildQueryHttpRequest } from '../../QueryHttpRequest';
5
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
6
+ import { Paging } from '../../Paging';
7
+ import { Sorting } from '../../Sorting';
8
+ import { SortDirection } from '../../SortDirection';
9
+
10
+ describe('when building with query method and paging and sorting', () => {
11
+ let result: { url: URL; init: RequestInit };
12
+ /* eslint-disable @typescript-eslint/no-explicit-any */
13
+ let body: any;
14
+ /* eslint-enable @typescript-eslint/no-explicit-any */
15
+
16
+ beforeEach(() => {
17
+ result = buildQueryHttpRequest(QueryHttpMethod.Query, {
18
+ route: '/api/all',
19
+ apiBasePath: '',
20
+ origin: 'https://api.example.com',
21
+ args: {},
22
+ parameterValues: {},
23
+ paging: new Paging(2, 25),
24
+ sorting: new Sorting('name', SortDirection.descending),
25
+ headers: {}
26
+ });
27
+ body = JSON.parse(result.init.body as string);
28
+ });
29
+
30
+ it('should carry paging in the body', () => body.paging.should.deep.equal({ page: 2, pageSize: 25 }));
31
+
32
+ it('should carry sorting in the body', () => body.sorting.should.deep.equal({ field: 'name', direction: 'desc' }));
33
+
34
+ it('should not put paging or sorting in the query string', () => result.url.search.should.equal(''));
35
+ });
@@ -0,0 +1,24 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { Paging } from '../Paging';
5
+ import { Sorting } from '../Sorting';
6
+ import { BuildQueryHttpRequestOptions } from '../QueryHttpRequest';
7
+
8
+ /**
9
+ * Builds a minimal set of options for exercising {@link executeQueryHttpRequest}.
10
+ * @param overrides Optional property overrides (e.g. a different origin).
11
+ */
12
+ export function makeOptions(overrides?: Partial<BuildQueryHttpRequestOptions>): BuildQueryHttpRequestOptions {
13
+ return {
14
+ route: '/api/test',
15
+ apiBasePath: '',
16
+ origin: 'https://api.example.com',
17
+ args: {},
18
+ parameterValues: {},
19
+ paging: Paging.noPaging,
20
+ sorting: Sorting.none,
21
+ headers: {},
22
+ ...overrides
23
+ };
24
+ }
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import * as sinon from 'sinon';
5
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
6
+ import { executeQueryHttpRequest, resetQueryHttpMethodResolution } from '../../QueryHttpRequest';
7
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
8
+ import { Globals } from '../../../Globals';
9
+ import { makeOptions } from '../options';
10
+
11
+ describe('when performing with a resolver', () => {
12
+ let fetchStub: sinon.SinonStub;
13
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
14
+
15
+ beforeEach(async () => {
16
+ resetQueryHttpMethodResolution();
17
+ fetchHelper = createFetchHelper();
18
+ fetchStub = fetchHelper.stubFetch();
19
+ fetchStub.resolves({ status: 200 } as unknown as Response);
20
+ Globals.queryHttpMethodResolver = () => QueryHttpMethod.Query;
21
+ // No explicit per-query override — the resolver decides.
22
+ await executeQueryHttpRequest(undefined, makeOptions());
23
+ });
24
+
25
+ afterEach(() => {
26
+ Globals.queryHttpMethodResolver = undefined;
27
+ fetchHelper.restore();
28
+ });
29
+
30
+ it('should use the method chosen by the resolver', () => fetchStub.getCall(0).args[1].method.should.equal('QUERY'));
31
+ });
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import * as sinon from 'sinon';
5
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
6
+ import { executeQueryHttpRequest, resetQueryHttpMethodResolution } from '../../QueryHttpRequest';
7
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
8
+ import { Globals } from '../../../Globals';
9
+ import { makeOptions } from '../options';
10
+
11
+ describe('when performing with a resolver and an explicit override', () => {
12
+ let fetchStub: sinon.SinonStub;
13
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
14
+
15
+ beforeEach(async () => {
16
+ resetQueryHttpMethodResolution();
17
+ fetchHelper = createFetchHelper();
18
+ fetchStub = fetchHelper.stubFetch();
19
+ fetchStub.resolves({ status: 200 } as unknown as Response);
20
+ Globals.queryHttpMethodResolver = () => QueryHttpMethod.Query;
21
+ // An explicit per-query override must win over the resolver.
22
+ await executeQueryHttpRequest(QueryHttpMethod.Get, makeOptions());
23
+ });
24
+
25
+ afterEach(() => {
26
+ Globals.queryHttpMethodResolver = undefined;
27
+ fetchHelper.restore();
28
+ });
29
+
30
+ it('should honor the explicit override over the resolver', () => fetchStub.getCall(0).args[1].method.should.equal('GET'));
31
+ });
@@ -0,0 +1,33 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import * as sinon from 'sinon';
5
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
6
+ import { executeQueryHttpRequest, resetQueryHttpMethodResolution } from '../../QueryHttpRequest';
7
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
8
+ import { makeOptions } from '../options';
9
+
10
+ describe('when performing with auto after query was found unsupported', () => {
11
+ let fetchStub: sinon.SinonStub;
12
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
13
+
14
+ beforeEach(async () => {
15
+ resetQueryHttpMethodResolution();
16
+ fetchHelper = createFetchHelper();
17
+ fetchStub = fetchHelper.stubFetch();
18
+ fetchStub.callsFake((_url: URL, init: RequestInit) =>
19
+ Promise.resolve({ status: init.method === 'QUERY' ? 405 : 200 } as unknown as Response));
20
+
21
+ // First Auto query learns that QUERY is unsupported and falls back to GET.
22
+ await executeQueryHttpRequest(QueryHttpMethod.Auto, makeOptions());
23
+ // Second Auto query should go straight to GET without probing QUERY again.
24
+ await executeQueryHttpRequest(QueryHttpMethod.Auto, makeOptions());
25
+ });
26
+
27
+ afterEach(() => fetchHelper.restore());
28
+
29
+ it('should only attempt QUERY once across both queries', () =>
30
+ fetchStub.getCalls().filter(call => call.args[1].method === 'QUERY').length.should.equal(1));
31
+ it('should send the second query directly as GET', () => fetchStub.getCall(2).args[1].method.should.equal('GET'));
32
+ it('should send three requests in total', () => fetchStub.callCount.should.equal(3));
33
+ });
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import * as sinon from 'sinon';
5
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
6
+ import { executeQueryHttpRequest, resetQueryHttpMethodResolution } from '../../QueryHttpRequest';
7
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
8
+ import { makeOptions } from '../options';
9
+
10
+ describe('when performing with auto and a network failure', () => {
11
+ let fetchStub: sinon.SinonStub;
12
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
13
+ let result: Response;
14
+
15
+ beforeEach(async () => {
16
+ resetQueryHttpMethodResolution();
17
+ fetchHelper = createFetchHelper();
18
+ fetchStub = fetchHelper.stubFetch();
19
+ fetchStub.callsFake((_url: URL, init: RequestInit) =>
20
+ init.method === 'QUERY'
21
+ ? Promise.reject(new TypeError('Failed to fetch'))
22
+ : Promise.resolve({ status: 200 } as unknown as Response));
23
+ result = await executeQueryHttpRequest(QueryHttpMethod.Auto, makeOptions());
24
+ });
25
+
26
+ afterEach(() => fetchHelper.restore());
27
+
28
+ it('should attempt QUERY first', () => fetchStub.getCall(0).args[1].method.should.equal('QUERY'));
29
+ it('should fall back to GET on the thrown error', () => fetchStub.getCall(1).args[1].method.should.equal('GET'));
30
+ it('should return the GET response', () => result.status.should.equal(200));
31
+ });
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import * as sinon from 'sinon';
5
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
6
+ import { executeQueryHttpRequest, resetQueryHttpMethodResolution } from '../../QueryHttpRequest';
7
+ import { QueryHttpMethod } from '../../QueryHttpMethod';
8
+ import { makeOptions } from '../options';
9
+
10
+ describe('when performing with auto and an aborted request', () => {
11
+ let fetchStub: sinon.SinonStub;
12
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
13
+ let error: { name?: string } | undefined;
14
+
15
+ beforeEach(async () => {
16
+ resetQueryHttpMethodResolution();
17
+ fetchHelper = createFetchHelper();
18
+ fetchStub = fetchHelper.stubFetch();
19
+ fetchStub.callsFake(() => Promise.reject(Object.assign(new Error('aborted'), { name: 'AbortError' })));
20
+ try {
21
+ await executeQueryHttpRequest(QueryHttpMethod.Auto, makeOptions());
22
+ } catch (caught) {
23
+ error = caught as { name?: string };
24
+ }
25
+ });
26
+
27
+ afterEach(() => fetchHelper.restore());
28
+
29
+ it('should rethrow the abort error', () => error!.name!.should.equal('AbortError'));
30
+ it('should not fall back to GET', () => fetchStub.callCount.should.equal(1));
31
+ });