@ethlete/query 5.16.0 → 5.16.2

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 (184) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/esm2022/ethlete-query.mjs +5 -0
  3. package/esm2022/index.mjs +20 -0
  4. package/esm2022/lib/auth/auth-provider.types.mjs +2 -0
  5. package/esm2022/lib/auth/auth-provider.utils.mjs +25 -0
  6. package/esm2022/lib/auth/basic-auth-provider.mjs +15 -0
  7. package/esm2022/lib/auth/bearer-auth-provider.mjs +168 -0
  8. package/esm2022/lib/auth/custom-header-auth-provider.mjs +12 -0
  9. package/esm2022/lib/auth/index.mjs +6 -0
  10. package/esm2022/lib/class-validator/class-validator.types.mjs +2 -0
  11. package/esm2022/lib/class-validator/class-validator.utils.mjs +4 -0
  12. package/esm2022/lib/class-validator/index.mjs +3 -0
  13. package/esm2022/lib/devtools/components/query-devtools/index.mjs +2 -0
  14. package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +297 -0
  15. package/esm2022/lib/devtools/pipes/index.mjs +2 -0
  16. package/esm2022/lib/devtools/pipes/query-short-name.pipe.mjs +24 -0
  17. package/esm2022/lib/devtools/utils/index.mjs +2 -0
  18. package/esm2022/lib/devtools/utils/query-client.utils.mjs +13 -0
  19. package/esm2022/lib/directives/index.mjs +4 -0
  20. package/esm2022/lib/directives/infinity-query-trigger.directive.mjs +56 -0
  21. package/esm2022/lib/directives/infinity-query.directive.mjs +147 -0
  22. package/esm2022/lib/directives/query.directive.mjs +123 -0
  23. package/esm2022/lib/entity/entity-store.mjs +127 -0
  24. package/esm2022/lib/entity/entity.types.mjs +2 -0
  25. package/esm2022/lib/entity/entity.utils.mjs +29 -0
  26. package/esm2022/lib/entity/index.mjs +4 -0
  27. package/esm2022/lib/gql/gql.mjs +23 -0
  28. package/esm2022/lib/gql/index.mjs +2 -0
  29. package/esm2022/lib/infinite-query/index.mjs +4 -0
  30. package/esm2022/lib/infinite-query/infinity-query.mjs +171 -0
  31. package/esm2022/lib/infinite-query/infinity-query.types.mjs +2 -0
  32. package/esm2022/lib/infinite-query/infinity-query.utils.mjs +3 -0
  33. package/esm2022/lib/logger/index.mjs +2 -0
  34. package/esm2022/lib/logger/logger.mjs +16 -0
  35. package/esm2022/lib/query/index.mjs +4 -0
  36. package/esm2022/lib/query/query.mjs +430 -0
  37. package/esm2022/lib/query/query.types.mjs +2 -0
  38. package/esm2022/lib/query/query.utils.mjs +253 -0
  39. package/esm2022/lib/query-client/index.mjs +4 -0
  40. package/esm2022/lib/query-client/query-client.mjs +80 -0
  41. package/esm2022/lib/query-client/query-client.types.mjs +2 -0
  42. package/esm2022/lib/query-client/query-client.utils.mjs +20 -0
  43. package/esm2022/lib/query-creator/index.mjs +3 -0
  44. package/esm2022/lib/query-creator/query-creator.mjs +55 -0
  45. package/esm2022/lib/query-creator/query-creator.types.mjs +2 -0
  46. package/esm2022/lib/query-form/index.mjs +4 -0
  47. package/esm2022/lib/query-form/query-form.mjs +379 -0
  48. package/esm2022/lib/query-form/query-form.types.mjs +2 -0
  49. package/esm2022/lib/query-form/query-form.utils.mjs +97 -0
  50. package/esm2022/lib/query-store/index.mjs +2 -0
  51. package/esm2022/lib/query-store/query-store.mjs +144 -0
  52. package/esm2022/lib/request/index.mjs +4 -0
  53. package/esm2022/lib/request/request.mjs +196 -0
  54. package/esm2022/lib/request/request.types.mjs +2 -0
  55. package/esm2022/lib/request/request.util.mjs +252 -0
  56. package/esm2022/lib/symfony/index.mjs +3 -0
  57. package/esm2022/lib/symfony/symfony.types.mjs +2 -0
  58. package/esm2022/lib/symfony/symfony.utils.mjs +31 -0
  59. package/esm2022/lib/utils/data.utils.mjs +138 -0
  60. package/esm2022/lib/utils/deep-freeze.util.mjs +17 -0
  61. package/esm2022/lib/utils/def.util.mjs +8 -0
  62. package/esm2022/lib/utils/index.mjs +4 -0
  63. package/fesm2022/ethlete-query.mjs +3289 -0
  64. package/fesm2022/ethlete-query.mjs.map +1 -0
  65. package/{src/index.ts → index.d.ts} +0 -2
  66. package/lib/auth/auth-provider.types.d.ts +133 -0
  67. package/lib/auth/auth-provider.utils.d.ts +9 -0
  68. package/lib/auth/basic-auth-provider.d.ts +10 -0
  69. package/lib/auth/bearer-auth-provider.d.ts +23 -0
  70. package/lib/auth/custom-header-auth-provider.d.ts +9 -0
  71. package/{src/lib/class-validator/class-validator.types.ts → lib/class-validator/class-validator.types.d.ts} +3 -4
  72. package/lib/class-validator/class-validator.utils.d.ts +2 -0
  73. package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +151 -0
  74. package/lib/devtools/pipes/query-short-name.pipe.d.ts +8 -0
  75. package/lib/devtools/utils/query-client.utils.d.ts +12 -0
  76. package/lib/directives/infinity-query-trigger.directive.d.ts +15 -0
  77. package/lib/directives/infinity-query.directive.d.ts +49 -0
  78. package/lib/directives/query.directive.d.ts +63 -0
  79. package/lib/entity/entity-store.d.ts +25 -0
  80. package/lib/entity/entity.types.d.ts +25 -0
  81. package/lib/entity/entity.utils.d.ts +12 -0
  82. package/lib/gql/gql.d.ts +6 -0
  83. package/lib/infinite-query/infinity-query.d.ts +29 -0
  84. package/lib/infinite-query/infinity-query.types.d.ts +104 -0
  85. package/lib/infinite-query/infinity-query.utils.d.ts +4 -0
  86. package/lib/logger/logger.d.ts +12 -0
  87. package/lib/query/query.d.ts +60 -0
  88. package/lib/query/query.types.d.ts +388 -0
  89. package/lib/query/query.utils.d.ts +82 -0
  90. package/lib/query-client/query-client.d.ts +29 -0
  91. package/lib/query-client/query-client.types.d.ts +82 -0
  92. package/lib/query-client/query-client.utils.d.ts +4 -0
  93. package/lib/query-creator/query-creator.d.ts +20 -0
  94. package/lib/query-creator/query-creator.types.d.ts +13 -0
  95. package/lib/query-form/query-form.d.ts +91 -0
  96. package/lib/query-form/query-form.types.d.ts +145 -0
  97. package/lib/query-form/query-form.utils.d.ts +13 -0
  98. package/lib/query-store/query-store.d.ts +31 -0
  99. package/lib/request/request.d.ts +3 -0
  100. package/lib/request/request.types.d.ts +126 -0
  101. package/lib/request/request.util.d.ts +92 -0
  102. package/lib/symfony/symfony.types.d.ts +19 -0
  103. package/lib/symfony/symfony.utils.d.ts +6 -0
  104. package/lib/utils/data.utils.d.ts +59 -0
  105. package/lib/utils/deep-freeze.util.d.ts +1 -0
  106. package/{src/lib/utils/def.util.ts → lib/utils/def.util.d.ts} +1 -1
  107. package/package.json +23 -6
  108. package/.eslintrc.json +0 -40
  109. package/jest.config.ts +0 -23
  110. package/ng-package.json +0 -8
  111. package/project.json +0 -37
  112. package/src/lib/auth/auth-provider.types.ts +0 -160
  113. package/src/lib/auth/auth-provider.utils.ts +0 -39
  114. package/src/lib/auth/basic-auth-provider.ts +0 -17
  115. package/src/lib/auth/bearer-auth-provider.ts +0 -243
  116. package/src/lib/auth/custom-header-auth-provider.ts +0 -13
  117. package/src/lib/class-validator/class-validator.utils.ts +0 -5
  118. package/src/lib/devtools/components/query-devtools/query-devtools.component.html +0 -402
  119. package/src/lib/devtools/components/query-devtools/query-devtools.component.scss +0 -360
  120. package/src/lib/devtools/components/query-devtools/query-devtools.component.ts +0 -427
  121. package/src/lib/devtools/pipes/query-short-name.pipe.ts +0 -24
  122. package/src/lib/devtools/utils/query-client.utils.ts +0 -25
  123. package/src/lib/directives/infinity-query-trigger.directive.ts +0 -58
  124. package/src/lib/directives/infinity-query.directive.ts +0 -208
  125. package/src/lib/directives/query.directive.ts +0 -201
  126. package/src/lib/entity/entity-store.ts +0 -180
  127. package/src/lib/entity/entity.types.ts +0 -34
  128. package/src/lib/entity/entity.utils.ts +0 -51
  129. package/src/lib/gql/gql.ts +0 -22
  130. package/src/lib/infinite-query/infinity-query.ts +0 -245
  131. package/src/lib/infinite-query/infinity-query.types.ts +0 -149
  132. package/src/lib/infinite-query/infinity-query.utils.ts +0 -14
  133. package/src/lib/logger/logger.ts +0 -30
  134. package/src/lib/query/query.ts +0 -631
  135. package/src/lib/query/query.types.ts +0 -532
  136. package/src/lib/query/query.utils.spec.ts +0 -92
  137. package/src/lib/query/query.utils.ts +0 -450
  138. package/src/lib/query-client/query-client.spec.ts +0 -77
  139. package/src/lib/query-client/query-client.ts +0 -195
  140. package/src/lib/query-client/query-client.types.ts +0 -101
  141. package/src/lib/query-client/query-client.utils.spec.ts +0 -61
  142. package/src/lib/query-client/query-client.utils.ts +0 -28
  143. package/src/lib/query-creator/query-creator.ts +0 -103
  144. package/src/lib/query-creator/query-creator.types.ts +0 -74
  145. package/src/lib/query-form/query-form.ts +0 -526
  146. package/src/lib/query-form/query-form.types.ts +0 -169
  147. package/src/lib/query-form/query-form.utils.ts +0 -116
  148. package/src/lib/query-store/query-store.ts +0 -192
  149. package/src/lib/request/request.ts +0 -253
  150. package/src/lib/request/request.types.ts +0 -164
  151. package/src/lib/request/request.util.spec.ts +0 -177
  152. package/src/lib/request/request.util.ts +0 -399
  153. package/src/lib/stories/angular-example-page.mdx +0 -85
  154. package/src/lib/stories/authentication-page.mdx +0 -35
  155. package/src/lib/stories/changelog-page.mdx +0 -9
  156. package/src/lib/stories/client-page.mdx +0 -100
  157. package/src/lib/stories/queries-page.mdx +0 -117
  158. package/src/lib/symfony/symfony.types.ts +0 -21
  159. package/src/lib/symfony/symfony.utils.ts +0 -43
  160. package/src/lib/utils/data.utils.ts +0 -280
  161. package/src/lib/utils/deep-freeze.util.ts +0 -21
  162. package/src/test-setup.ts +0 -1
  163. package/tsconfig.json +0 -29
  164. package/tsconfig.lib.json +0 -12
  165. package/tsconfig.lib.prod.json +0 -10
  166. package/tsconfig.spec.json +0 -11
  167. /package/{src/lib/auth/index.ts → lib/auth/index.d.ts} +0 -0
  168. /package/{src/lib/class-validator/index.ts → lib/class-validator/index.d.ts} +0 -0
  169. /package/{src/lib/devtools/components/query-devtools/index.ts → lib/devtools/components/query-devtools/index.d.ts} +0 -0
  170. /package/{src/lib/devtools/pipes/index.ts → lib/devtools/pipes/index.d.ts} +0 -0
  171. /package/{src/lib/devtools/utils/index.ts → lib/devtools/utils/index.d.ts} +0 -0
  172. /package/{src/lib/directives/index.ts → lib/directives/index.d.ts} +0 -0
  173. /package/{src/lib/entity/index.ts → lib/entity/index.d.ts} +0 -0
  174. /package/{src/lib/gql/index.ts → lib/gql/index.d.ts} +0 -0
  175. /package/{src/lib/infinite-query/index.ts → lib/infinite-query/index.d.ts} +0 -0
  176. /package/{src/lib/logger/index.ts → lib/logger/index.d.ts} +0 -0
  177. /package/{src/lib/query/index.ts → lib/query/index.d.ts} +0 -0
  178. /package/{src/lib/query-client/index.ts → lib/query-client/index.d.ts} +0 -0
  179. /package/{src/lib/query-creator/index.ts → lib/query-creator/index.d.ts} +0 -0
  180. /package/{src/lib/query-form/index.ts → lib/query-form/index.d.ts} +0 -0
  181. /package/{src/lib/query-store/index.ts → lib/query-store/index.d.ts} +0 -0
  182. /package/{src/lib/request/index.ts → lib/request/index.d.ts} +0 -0
  183. /package/{src/lib/symfony/index.ts → lib/symfony/index.d.ts} +0 -0
  184. /package/{src/lib/utils/index.ts → lib/utils/index.d.ts} +0 -0
@@ -1,177 +0,0 @@
1
- import { invalidBaseRouteError, invalidRouteError, pathParamsMissingInRouteFunctionError } from '../logger';
2
- import { RouteString } from '../query';
3
- import { buildQueryString, buildRoute, isRequestError } from './request.util';
4
-
5
- describe('isRequestError', () => {
6
- it('should be falsy', () => {
7
- expect(isRequestError('foo')).toBeFalsy();
8
- });
9
-
10
- it('should by truthy', () => {
11
- expect(isRequestError({ status: 200, statusText: 'Error', url: '...' })).toBeTruthy();
12
- });
13
- });
14
-
15
- describe('buildQueryString', () => {
16
- const uriBrackets = encodeURIComponent('[]');
17
- const dot = encodeURIComponent('.');
18
- const uriBracketsZero = encodeURIComponent('[0]');
19
- const brOpen = encodeURIComponent('[');
20
- const brClose = encodeURIComponent(']');
21
- const uriBracketsOne = encodeURIComponent('[1]');
22
- const uriBracketsTwo = encodeURIComponent('[2]');
23
-
24
- it('should work with a single param', () => {
25
- expect(buildQueryString({ foo: 123 })).toEqual('foo=123');
26
- });
27
-
28
- it('should work with multiple params', () => {
29
- expect(buildQueryString({ foo: 123, bar: 'string' })).toEqual('foo=123&bar=string');
30
- });
31
-
32
- it('should work with valid but falsy params', () => {
33
- expect(
34
- buildQueryString({
35
- bar: 0,
36
- stuff: -0,
37
- }),
38
- ).toEqual('bar=0&stuff=0');
39
- });
40
-
41
- it('should work with arrays', () => {
42
- expect(
43
- buildQueryString({
44
- foo: ['abc', 'def'],
45
- bar: true,
46
- }),
47
- ).toEqual(`foo${uriBrackets}=abc&foo${uriBrackets}=def&bar=true`);
48
- });
49
-
50
- it('should filter out invalid values using default config', () => {
51
- expect(
52
- buildQueryString({
53
- foo: ['abc', 'def', null, undefined, NaN],
54
- bar: true,
55
- baz: Infinity,
56
- bi: ' ',
57
- }),
58
- ).toEqual(`foo${uriBrackets}=abc&foo${uriBrackets}=def&bar=true`);
59
- });
60
-
61
- it('should use correct filtered array indexes', () => {
62
- expect(
63
- buildQueryString(
64
- {
65
- foo: ['abc', null, 'def', NaN, Infinity, -Infinity, undefined, 'bar'],
66
- },
67
- {
68
- writeArrayIndexes: true,
69
- },
70
- ),
71
- ).toEqual(`foo${uriBracketsZero}=abc&foo${uriBracketsOne}=def&foo${uriBracketsTwo}=bar`);
72
- });
73
-
74
- it('should work with array params containing objects in dot notation', () => {
75
- expect(
76
- buildQueryString(
77
- {
78
- filters: [{ name: 'category', values: ['Stuff', 'Other'] }],
79
- },
80
- {
81
- objectNotation: 'dot',
82
- writeArrayIndexes: true,
83
- },
84
- ),
85
- ).toEqual(
86
- `filters${uriBracketsZero}${dot}name=category&filters${uriBracketsZero}${dot}values${uriBracketsZero}=Stuff&filters${uriBracketsZero}${dot}values${uriBracketsOne}=Other`,
87
- );
88
- });
89
-
90
- it('should work with array params containing objects in bracket notation', () => {
91
- expect(
92
- buildQueryString(
93
- {
94
- filters: [{ name: 'category', values: ['Stuff', 'Other'] }],
95
- },
96
- {
97
- objectNotation: 'bracket',
98
- writeArrayIndexes: true,
99
- },
100
- ),
101
- ).toEqual(
102
- `filters${uriBracketsZero}${brOpen}name${brClose}=category&filters${uriBracketsZero}${brOpen}values${brClose}${uriBracketsZero}=Stuff&filters${uriBracketsZero}${brOpen}values${brClose}${uriBracketsOne}=Other`,
103
- );
104
- });
105
- });
106
-
107
- describe('buildRoute', () => {
108
- it('should work without params', () => {
109
- expect(
110
- buildRoute({
111
- base: 'https://example.com',
112
- route: '/foo',
113
- }),
114
- ).toBe('https://example.com/foo');
115
- });
116
-
117
- it('should work with path params', () => {
118
- expect(
119
- buildRoute({
120
- base: 'https://example.com',
121
- route: (args) => '/foo/' + args['id'],
122
- pathParams: { id: 1 },
123
- }),
124
- ).toBe('https://example.com/foo/1');
125
- });
126
-
127
- it('should work with query params', () => {
128
- expect(
129
- buildRoute({
130
- base: 'https://example.com',
131
- route: '/foo',
132
- queryParams: { id: 1 },
133
- }),
134
- ).toBe('https://example.com/foo?id=1');
135
- });
136
-
137
- it('should work with query and path params', () => {
138
- expect(
139
- buildRoute({
140
- base: 'https://example.com',
141
- route: (args) => '/foo/' + args['id'],
142
- pathParams: { id: 1 },
143
- queryParams: { filter: true },
144
- }),
145
- ).toBe('https://example.com/foo/1?filter=true');
146
- });
147
-
148
- it('should fail with a route function without provided path params', () => {
149
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
- const fn = (args: any) => '/foo/' + args.id;
151
-
152
- expect(() => {
153
- buildRoute({
154
- base: 'https://example.com',
155
- route: (args) => '/foo/' + args['id'],
156
- });
157
- }).toThrow(pathParamsMissingInRouteFunctionError(fn({})));
158
- });
159
-
160
- it('should fail with base route ending with a slash', () => {
161
- expect(() => {
162
- buildRoute({
163
- base: 'https://example.com/',
164
- route: '/foo',
165
- });
166
- }).toThrow(invalidBaseRouteError('https://example.com/'));
167
- });
168
-
169
- it('should fail with route missing a slash at the start', () => {
170
- expect(() => {
171
- buildRoute({
172
- base: 'https://example.com',
173
- route: 'foo' as RouteString,
174
- });
175
- }).toThrow(invalidRouteError('foo'));
176
- });
177
- });
@@ -1,399 +0,0 @@
1
- import { invalidBaseRouteError, invalidRouteError, pathParamsMissingInRouteFunctionError } from '../logger';
2
- import { AnyRoute } from '../query';
3
- import { isSymfonyPagerfantaOutOfRangeError } from '../symfony';
4
- import {
5
- BuildQueryStringConfig,
6
- Method,
7
- PathParams,
8
- QueryParams,
9
- RequestError,
10
- RequestHeaders,
11
- RequestRetryFn,
12
- } from './request.types';
13
-
14
- export const isNaN = (value: unknown): boolean => typeof value === 'number' && Number.isNaN(value);
15
- export const isEmptyString = (value: unknown): boolean => typeof value === 'string' && value.trim() === '';
16
-
17
- export const isRequestError = <T = unknown>(error: unknown): error is RequestError<T> =>
18
- error instanceof Object && 'status' in error && 'statusText' in error && 'url' in error;
19
-
20
- export const buildRoute = (options: {
21
- base: string;
22
- route: AnyRoute | null | undefined;
23
- pathParams?: PathParams;
24
- queryParams?: QueryParams;
25
- queryParamConfig?: BuildQueryStringConfig;
26
- }) => {
27
- if (options.base.endsWith('/')) {
28
- throw invalidBaseRouteError(options.base);
29
- }
30
-
31
- let route: string | null = null;
32
-
33
- if (typeof options.route === 'function') {
34
- if (!options.pathParams) {
35
- throw pathParamsMissingInRouteFunctionError(options.route({}));
36
- }
37
-
38
- route = options.route(options.pathParams);
39
- } else {
40
- route = options.route ?? null;
41
- }
42
-
43
- if (route && !route.startsWith('/')) {
44
- throw invalidRouteError(route);
45
- }
46
-
47
- if (options.queryParams) {
48
- const queryString = buildQueryString(options.queryParams, options.queryParamConfig);
49
-
50
- if (queryString) {
51
- route = route ? `${route}?${queryString}` : `/?${queryString}`;
52
- }
53
- }
54
-
55
- return `${options.base}${route ?? ''}`;
56
- };
57
-
58
- export const buildQueryString = (params: QueryParams, config?: BuildQueryStringConfig): string | null => {
59
- const objectNotation = config?.objectNotation ?? 'bracket';
60
- const writeArrayIndexes = config?.writeArrayIndexes ?? false;
61
- const ignoredValues = config?.ignoredValues ?? [undefined, null, Infinity, -Infinity];
62
- const ignoredValuesFns = config?.ignoredValuesFns ?? [isNaN, isEmptyString];
63
-
64
- const queryParams: string[] = [];
65
-
66
- function processValue(key: string, value: unknown) {
67
- if (Array.isArray(value)) {
68
- let currentFilteredIndex = 0;
69
- for (const arrayValue of value) {
70
- const nestedKey = writeArrayIndexes ? `${key}[${currentFilteredIndex}]` : `${key}[]`;
71
-
72
- const didAddValue = processValue(nestedKey, arrayValue);
73
-
74
- if (didAddValue) {
75
- currentFilteredIndex++;
76
- }
77
- }
78
-
79
- return null;
80
- } else if (typeof value === 'object' && value !== null) {
81
- for (const [objKey, val] of Object.entries(value)) {
82
- const nestedKey = objectNotation === 'dot' ? `${key}.${objKey}` : `${key}[${objKey}]`;
83
- processValue(nestedKey, val);
84
- }
85
-
86
- return null;
87
- } else {
88
- if (ignoredValues.includes(value)) {
89
- return false;
90
- }
91
-
92
- if (ignoredValuesFns.some((fn) => fn(value))) {
93
- return false;
94
- }
95
-
96
- const encodedKey = encodeURIComponent(key);
97
- const encodedValue = encodeURIComponent(value as string);
98
-
99
- queryParams.push(`${encodedKey}=${encodedValue}`);
100
-
101
- return true;
102
- }
103
- }
104
-
105
- for (const [key, val] of Object.entries(params)) {
106
- processValue(key, val);
107
- }
108
-
109
- return queryParams.length ? queryParams.join('&') : null;
110
- };
111
-
112
- export const extractExpiresInSeconds = (headers: RequestHeaders) => {
113
- const cacheControl = headers['cache-control'];
114
- const age = headers['age'];
115
- const expires = headers['expires'];
116
-
117
- // In seconds
118
- let expiresIn: number | null = null;
119
- let maxAge: number | null = null;
120
-
121
- if (cacheControl?.includes('no-cache')) {
122
- return null;
123
- }
124
-
125
- if (cacheControl?.includes('max-age')) {
126
- const m = cacheControl.split('max-age=')[1];
127
-
128
- if (m) {
129
- maxAge = parseInt(m);
130
- }
131
- } else if (cacheControl?.includes('s-maxage')) {
132
- const m = cacheControl.split('s-maxage=')[1];
133
-
134
- if (m) {
135
- maxAge = parseInt(m);
136
- }
137
- }
138
-
139
- if (maxAge && age) {
140
- const ageSeconds = parseInt(age);
141
-
142
- expiresIn = maxAge - ageSeconds;
143
- } else if (maxAge) {
144
- expiresIn = maxAge / 2; // We assume the response is half way to its expiration
145
- } else if (expires) {
146
- // Used by some apis to tell the response will never expire
147
- // In this case we let the response expire after 1 hour
148
- if (expires === '-1') {
149
- expiresIn = 3600;
150
- } else {
151
- const expiresDate = new Date(expires);
152
-
153
- // check if the date is valid
154
- if (expiresDate.toString() !== 'Invalid Date') {
155
- expiresIn = Math.floor((expiresDate.getTime() - Date.now()) / 1000);
156
- }
157
- }
158
- }
159
-
160
- return expiresIn;
161
- };
162
-
163
- export const buildTimestampFromSeconds = (seconds: number | null) => {
164
- if (seconds === null) {
165
- return null;
166
- }
167
-
168
- return new Date(Date.now() + seconds * 1000).getTime();
169
- };
170
-
171
- export const serializeBody = (body: unknown): ArrayBuffer | URLSearchParams | Blob | FormData | string | null => {
172
- if (body === null || body === undefined) {
173
- return null;
174
- }
175
-
176
- if (
177
- body instanceof ArrayBuffer ||
178
- body instanceof Blob ||
179
- body instanceof FormData ||
180
- body instanceof URLSearchParams ||
181
- typeof body === 'string'
182
- ) {
183
- return body as ArrayBuffer | URLSearchParams | Blob | FormData | string;
184
- }
185
-
186
- if (typeof body === 'object' || typeof body === 'boolean' || Array.isArray(body)) {
187
- return JSON.stringify(body);
188
- }
189
-
190
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
- return (body as any).toString();
192
- };
193
-
194
- export const transformMethod = (config: { method: Method; transferVia?: 'GET' | 'POST' }) => {
195
- if (config.method === 'GQL_QUERY' || config.method === 'GQL_MUTATE') {
196
- if (!config.transferVia) {
197
- return 'POST';
198
- }
199
-
200
- return config.transferVia;
201
- }
202
-
203
- return config.method;
204
- };
205
-
206
- export const detectContentTypeHeader = (body: unknown) => {
207
- // An empty body has no content type.
208
- if (body === null) {
209
- return null;
210
- }
211
- // FormData bodies rely on the browser's content type assignment.
212
- if (body instanceof FormData) {
213
- return null;
214
- }
215
- // Blobs usually have their own content type. If it doesn't, then
216
- // no type can be inferred.
217
- if (body instanceof Blob) {
218
- return body.type || null;
219
- }
220
- // Array buffers have unknown contents and thus no type can be inferred.
221
- if (body instanceof ArrayBuffer) {
222
- return null;
223
- }
224
- // Technically, strings could be a form of JSON data, but it's safe enough
225
- // to assume they're plain strings.
226
- if (typeof body === 'string') {
227
- return 'text/plain';
228
- }
229
-
230
- // Arrays, objects, boolean and numbers will be encoded as JSON.
231
- if (typeof body === 'object' || typeof body === 'number' || typeof body === 'boolean') {
232
- return 'application/json';
233
- }
234
- // No type could be inferred.
235
- return null;
236
- };
237
-
238
- export const hasHeader = (headers: RequestHeaders, header: string) => {
239
- return Object.keys(headers).some((key) => key.toLowerCase() === header.toLowerCase());
240
- };
241
-
242
- export const forEachHeader = (headers: RequestHeaders, callback: (value: string, key: string) => void) => {
243
- Object.entries(headers).forEach(([key, value]) => {
244
- callback(key, value);
245
- });
246
- };
247
-
248
- export const parseAllXhrResponseHeaders = (xhr: XMLHttpRequest) => {
249
- const headers = xhr.getAllResponseHeaders();
250
- const parsedHeaders: RequestHeaders = {};
251
-
252
- for (const line of headers.split('\n')) {
253
- const index = line.indexOf(':');
254
- if (index > 0) {
255
- const name = line.slice(0, index);
256
- const key = name.toLowerCase();
257
- const value = line.slice(index + 1).trim();
258
-
259
- parsedHeaders[key] = value;
260
- }
261
- }
262
-
263
- return parsedHeaders;
264
- };
265
-
266
- export const getResponseUrl = (xhr: XMLHttpRequest): string | null => {
267
- if ('responseURL' in xhr && xhr.responseURL) {
268
- return xhr.responseURL;
269
- }
270
- if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
271
- return xhr.getResponseHeader('X-Request-URL');
272
- }
273
- return null;
274
- };
275
-
276
- export const shouldRetryRequest: RequestRetryFn = (config) => {
277
- const defaultRetryDelay = 1000 + 1000 * config.currentRetryCount;
278
-
279
- if (config.currentRetryCount > 3) {
280
- return { retry: false };
281
- }
282
-
283
- if (!isRequestError(config.error)) {
284
- return { retry: false };
285
- }
286
-
287
- const { status, detail } = config.error;
288
-
289
- // Retry on 5xx errors
290
- if (status >= 500) {
291
- // Don't retry if a requested page is out of range
292
- if (isSymfonyPagerfantaOutOfRangeError(detail)) {
293
- return { retry: false };
294
- }
295
-
296
- return { retry: true, delay: defaultRetryDelay };
297
- }
298
-
299
- // Retry on 408 or 425 errors
300
- if (status === HttpStatusCode.RequestTimeout || status === HttpStatusCode.TooEarly) {
301
- return { retry: true, delay: defaultRetryDelay };
302
- }
303
-
304
- // Retry on 429 errors
305
- if (status === HttpStatusCode.TooManyRequests) {
306
- const retryAfter =
307
- config.headers['retry-after'] ||
308
- config.headers['Retry-After'] ||
309
- config.headers['x-retry-after'] ||
310
- config.headers['X-Retry-After'];
311
-
312
- if (retryAfter) {
313
- const delay = parseInt(retryAfter) * 1000;
314
- return { retry: true, delay: Number.isNaN(delay) ? defaultRetryDelay : delay };
315
- }
316
-
317
- return { retry: true, delay: defaultRetryDelay };
318
- }
319
-
320
- // Code 0 usually means the internet connection is down. We retry in this case.
321
- // It could also be a CORS issue but that should not be the case in production.
322
- if (status === 0) {
323
- return { retry: true, delay: defaultRetryDelay };
324
- }
325
-
326
- return { retry: false };
327
- };
328
-
329
- export const enum HttpStatusCode {
330
- Unknown = 0,
331
-
332
- Continue = 100,
333
- SwitchingProtocols = 101,
334
- Processing = 102,
335
- EarlyHints = 103,
336
-
337
- Ok = 200,
338
- Created = 201,
339
- Accepted = 202,
340
- NonAuthoritativeInformation = 203,
341
- NoContent = 204,
342
- ResetContent = 205,
343
- PartialContent = 206,
344
- MultiStatus = 207,
345
- AlreadyReported = 208,
346
- ImUsed = 226,
347
-
348
- MultipleChoices = 300,
349
- MovedPermanently = 301,
350
- Found = 302,
351
- SeeOther = 303,
352
- NotModified = 304,
353
- UseProxy = 305,
354
- Unused = 306,
355
- TemporaryRedirect = 307,
356
- PermanentRedirect = 308,
357
-
358
- BadRequest = 400,
359
- Unauthorized = 401,
360
- PaymentRequired = 402,
361
- Forbidden = 403,
362
- NotFound = 404,
363
- MethodNotAllowed = 405,
364
- NotAcceptable = 406,
365
- ProxyAuthenticationRequired = 407,
366
- RequestTimeout = 408,
367
- Conflict = 409,
368
- Gone = 410,
369
- LengthRequired = 411,
370
- PreconditionFailed = 412,
371
- PayloadTooLarge = 413,
372
- UriTooLong = 414,
373
- UnsupportedMediaType = 415,
374
- RangeNotSatisfiable = 416,
375
- ExpectationFailed = 417,
376
- ImATeapot = 418,
377
- MisdirectedRequest = 421,
378
- UnprocessableEntity = 422,
379
- Locked = 423,
380
- FailedDependency = 424,
381
- TooEarly = 425,
382
- UpgradeRequired = 426,
383
- PreconditionRequired = 428,
384
- TooManyRequests = 429,
385
- RequestHeaderFieldsTooLarge = 431,
386
- UnavailableForLegalReasons = 451,
387
-
388
- InternalServerError = 500,
389
- NotImplemented = 501,
390
- BadGateway = 502,
391
- ServiceUnavailable = 503,
392
- GatewayTimeout = 504,
393
- HttpVersionNotSupported = 505,
394
- VariantAlsoNegotiates = 506,
395
- InsufficientStorage = 507,
396
- LoopDetected = 508,
397
- NotExtended = 510,
398
- NetworkAuthenticationRequired = 511,
399
- }
@@ -1,85 +0,0 @@
1
- import { Meta } from '@storybook/blocks';
2
-
3
- <Meta title="HttpClient/Angular Usage Example" />
4
-
5
- # Example
6
-
7
- In Angular there are some helper tools that make it very easy to use the queries in Angular.
8
-
9
- ```ts
10
- // product.queries.ts
11
-
12
- export interface GetProductsArgs {
13
- queryParams: {
14
- search?: string;
15
- };
16
- }
17
-
18
- export interface ProductView {
19
- id: string;
20
- name: string;
21
- }
22
-
23
- export const getProducts = client.get({
24
- route: '/products',
25
- secure: true,
26
- types: {
27
- args: def<GetProductsArgs>(),
28
- response: def<ProductView[]>(),
29
- },
30
- });
31
- ```
32
-
33
- ```ts
34
- // product-list.component.ts
35
- import { getProducts } from './product.queries';
36
- import { ChangeDetectionStrategy, Component, ViewEncapsulation, effect } from '@angular/core';
37
- import { ReactiveFormsModule, FormControl } from '@angular/forms';
38
- import { QueryDirective } from '@ethlete/query';
39
- import { controlValueSignal } from '@ethlete/core';
40
-
41
- @Component({
42
- selector: 'app-product-list',
43
- standalone: true,
44
- imports: [QueryDirective, ReactiveFormsModule],
45
- templateUrl: './product-list.component.html',
46
- encapsulation: ViewEncapsulation.None,
47
- changeDetection: ChangeDetectionStrategy.OnPush,
48
- })
49
- export class ProductListComponent {
50
- getProductsQuery = getProducts.createSignal();
51
- search = new FormControl<null | string>(null);
52
- searchChanges = controlValueSignal(this.search);
53
-
54
- constructor() {
55
- effect(() => {
56
- const search = this.searchChanges();
57
- this.getProductsQuery.set(
58
- getProductsQuery
59
- .prepare({
60
- queryParams: {
61
- search,
62
- },
63
- })
64
- .execute(),
65
- );
66
- });
67
- }
68
- }
69
- ```
70
-
71
- ```html
72
- <!-- product-list.component.html -->
73
-
74
- <input [formControl]="search" />
75
-
76
- <div *etQuery="getProductsQuery() as response; loading as loading; error as error">
77
- @for (product of response; track product.id) {
78
- <div>{{ product.name }}</div>
79
- } @if(loading) {
80
- <p>Loading Template</p>
81
- } @if(error && !loading) {
82
- <p>Error Template</p>
83
- }
84
- </div>
85
- ```
@@ -1,35 +0,0 @@
1
- import { Meta } from '@storybook/blocks';
2
-
3
- <Meta title="HttpClient/Authentication" />
4
-
5
- # Authentication
6
-
7
- The client offers you the option of using authentication. There are various AuthProviders that you can easily configure and transfer to the client.
8
-
9
- ## BearerAuthProvider
10
-
11
- The BearerAuthProvider is an AuthProvider that works with a Bearer Token. Once you have received the access token after logging in, you can easily pass it to the BearerAuthProvider. The BearerAuthProvider then refreshes the token once it has expired.
12
-
13
- ```ts
14
- import { QueryClient } from '@ethlete/query';
15
-
16
- export const client = new QueryClient({
17
- baseRoute: 'https://api.test.com',
18
- });
19
-
20
- const tokenFromLogin = 'token';
21
-
22
- const bearerAuthProvider = new BearerAuthProvider({
23
- token: tokenFromLogin,
24
- refreshConfig: {
25
- queryCreator: postRefreshToken,
26
- token: refreshToken,
27
- cookieName: 'auth-token',
28
- cookieEnabled: true,
29
- responseAdapter: (x) => ({ token: x.token, refreshToken: x.refresh_token }),
30
- requestArgsAdapter: (token) => ({ body: { refresh_token: token.refreshToken } }),
31
- },
32
- });
33
-
34
- client.setAuthProvider(bearerAuthProvider);
35
- ```
@@ -1,9 +0,0 @@
1
- import { Markdown, Meta } from '@storybook/blocks';
2
-
3
- import Readme from '../../../CHANGELOG.md?raw';
4
-
5
- <Meta title="Changelog/Query" />
6
-
7
- # Changelog
8
-
9
- <Markdown>{Readme}</Markdown>