@ethlete/query 5.15.0 → 5.16.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 (184) hide show
  1. package/.eslintrc.json +40 -0
  2. package/CHANGELOG.md +14 -0
  3. package/jest.config.ts +23 -0
  4. package/ng-package.json +8 -0
  5. package/package.json +6 -23
  6. package/project.json +37 -0
  7. package/{index.d.ts → src/index.ts} +2 -0
  8. package/src/lib/auth/auth-provider.types.ts +160 -0
  9. package/src/lib/auth/auth-provider.utils.ts +39 -0
  10. package/src/lib/auth/basic-auth-provider.ts +17 -0
  11. package/src/lib/auth/bearer-auth-provider.ts +243 -0
  12. package/src/lib/auth/custom-header-auth-provider.ts +13 -0
  13. package/{lib/class-validator/class-validator.types.d.ts → src/lib/class-validator/class-validator.types.ts} +4 -3
  14. package/src/lib/class-validator/class-validator.utils.ts +5 -0
  15. package/src/lib/devtools/components/query-devtools/query-devtools.component.html +402 -0
  16. package/src/lib/devtools/components/query-devtools/query-devtools.component.scss +360 -0
  17. package/src/lib/devtools/components/query-devtools/query-devtools.component.ts +427 -0
  18. package/src/lib/devtools/pipes/query-short-name.pipe.ts +24 -0
  19. package/src/lib/devtools/utils/query-client.utils.ts +25 -0
  20. package/src/lib/directives/infinity-query-trigger.directive.ts +58 -0
  21. package/src/lib/directives/infinity-query.directive.ts +208 -0
  22. package/src/lib/directives/query.directive.ts +201 -0
  23. package/src/lib/entity/entity-store.ts +180 -0
  24. package/src/lib/entity/entity.types.ts +34 -0
  25. package/src/lib/entity/entity.utils.ts +51 -0
  26. package/src/lib/gql/gql.ts +22 -0
  27. package/src/lib/infinite-query/infinity-query.ts +245 -0
  28. package/src/lib/infinite-query/infinity-query.types.ts +149 -0
  29. package/src/lib/infinite-query/infinity-query.utils.ts +14 -0
  30. package/src/lib/logger/logger.ts +30 -0
  31. package/src/lib/query/query.ts +631 -0
  32. package/src/lib/query/query.types.ts +532 -0
  33. package/src/lib/query/query.utils.spec.ts +92 -0
  34. package/src/lib/query/query.utils.ts +450 -0
  35. package/src/lib/query-client/query-client.spec.ts +77 -0
  36. package/src/lib/query-client/query-client.ts +195 -0
  37. package/src/lib/query-client/query-client.types.ts +101 -0
  38. package/src/lib/query-client/query-client.utils.spec.ts +61 -0
  39. package/src/lib/query-client/query-client.utils.ts +28 -0
  40. package/src/lib/query-creator/query-creator.ts +103 -0
  41. package/src/lib/query-creator/query-creator.types.ts +74 -0
  42. package/src/lib/query-form/query-form.ts +526 -0
  43. package/src/lib/query-form/query-form.types.ts +169 -0
  44. package/src/lib/query-form/query-form.utils.ts +116 -0
  45. package/src/lib/query-store/query-store.ts +192 -0
  46. package/src/lib/request/request.ts +253 -0
  47. package/src/lib/request/request.types.ts +164 -0
  48. package/src/lib/request/request.util.spec.ts +177 -0
  49. package/src/lib/request/request.util.ts +399 -0
  50. package/src/lib/stories/angular-example-page.mdx +85 -0
  51. package/src/lib/stories/authentication-page.mdx +35 -0
  52. package/src/lib/stories/changelog-page.mdx +9 -0
  53. package/src/lib/stories/client-page.mdx +100 -0
  54. package/src/lib/stories/queries-page.mdx +117 -0
  55. package/src/lib/symfony/symfony.types.ts +21 -0
  56. package/src/lib/symfony/symfony.utils.ts +43 -0
  57. package/src/lib/utils/data.utils.ts +280 -0
  58. package/src/lib/utils/deep-freeze.util.ts +21 -0
  59. package/{lib/utils/def.util.d.ts → src/lib/utils/def.util.ts} +1 -1
  60. package/src/test-setup.ts +1 -0
  61. package/tsconfig.json +29 -0
  62. package/tsconfig.lib.json +12 -0
  63. package/tsconfig.lib.prod.json +10 -0
  64. package/tsconfig.spec.json +11 -0
  65. package/esm2022/ethlete-query.mjs +0 -5
  66. package/esm2022/index.mjs +0 -20
  67. package/esm2022/lib/auth/auth-provider.types.mjs +0 -2
  68. package/esm2022/lib/auth/auth-provider.utils.mjs +0 -25
  69. package/esm2022/lib/auth/basic-auth-provider.mjs +0 -15
  70. package/esm2022/lib/auth/bearer-auth-provider.mjs +0 -168
  71. package/esm2022/lib/auth/custom-header-auth-provider.mjs +0 -12
  72. package/esm2022/lib/auth/index.mjs +0 -6
  73. package/esm2022/lib/class-validator/class-validator.types.mjs +0 -2
  74. package/esm2022/lib/class-validator/class-validator.utils.mjs +0 -4
  75. package/esm2022/lib/class-validator/index.mjs +0 -3
  76. package/esm2022/lib/devtools/components/query-devtools/index.mjs +0 -2
  77. package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +0 -297
  78. package/esm2022/lib/devtools/pipes/index.mjs +0 -2
  79. package/esm2022/lib/devtools/pipes/query-short-name.pipe.mjs +0 -24
  80. package/esm2022/lib/devtools/utils/index.mjs +0 -2
  81. package/esm2022/lib/devtools/utils/query-client.utils.mjs +0 -13
  82. package/esm2022/lib/directives/index.mjs +0 -4
  83. package/esm2022/lib/directives/infinity-query-trigger.directive.mjs +0 -56
  84. package/esm2022/lib/directives/infinity-query.directive.mjs +0 -147
  85. package/esm2022/lib/directives/query.directive.mjs +0 -123
  86. package/esm2022/lib/entity/entity-store.mjs +0 -127
  87. package/esm2022/lib/entity/entity.types.mjs +0 -2
  88. package/esm2022/lib/entity/entity.utils.mjs +0 -29
  89. package/esm2022/lib/entity/index.mjs +0 -4
  90. package/esm2022/lib/gql/gql.mjs +0 -23
  91. package/esm2022/lib/gql/index.mjs +0 -2
  92. package/esm2022/lib/infinite-query/index.mjs +0 -4
  93. package/esm2022/lib/infinite-query/infinity-query.mjs +0 -171
  94. package/esm2022/lib/infinite-query/infinity-query.types.mjs +0 -2
  95. package/esm2022/lib/infinite-query/infinity-query.utils.mjs +0 -3
  96. package/esm2022/lib/logger/index.mjs +0 -2
  97. package/esm2022/lib/logger/logger.mjs +0 -16
  98. package/esm2022/lib/query/index.mjs +0 -4
  99. package/esm2022/lib/query/query.mjs +0 -428
  100. package/esm2022/lib/query/query.types.mjs +0 -2
  101. package/esm2022/lib/query/query.utils.mjs +0 -253
  102. package/esm2022/lib/query-client/index.mjs +0 -4
  103. package/esm2022/lib/query-client/query-client.mjs +0 -80
  104. package/esm2022/lib/query-client/query-client.types.mjs +0 -2
  105. package/esm2022/lib/query-client/query-client.utils.mjs +0 -20
  106. package/esm2022/lib/query-creator/index.mjs +0 -3
  107. package/esm2022/lib/query-creator/query-creator.mjs +0 -55
  108. package/esm2022/lib/query-creator/query-creator.types.mjs +0 -2
  109. package/esm2022/lib/query-form/index.mjs +0 -4
  110. package/esm2022/lib/query-form/query-form.mjs +0 -368
  111. package/esm2022/lib/query-form/query-form.types.mjs +0 -2
  112. package/esm2022/lib/query-form/query-form.utils.mjs +0 -97
  113. package/esm2022/lib/query-store/index.mjs +0 -2
  114. package/esm2022/lib/query-store/query-store.mjs +0 -144
  115. package/esm2022/lib/request/index.mjs +0 -4
  116. package/esm2022/lib/request/request.mjs +0 -196
  117. package/esm2022/lib/request/request.types.mjs +0 -2
  118. package/esm2022/lib/request/request.util.mjs +0 -252
  119. package/esm2022/lib/symfony/index.mjs +0 -3
  120. package/esm2022/lib/symfony/symfony.types.mjs +0 -2
  121. package/esm2022/lib/symfony/symfony.utils.mjs +0 -31
  122. package/esm2022/lib/utils/data.utils.mjs +0 -138
  123. package/esm2022/lib/utils/deep-freeze.util.mjs +0 -17
  124. package/esm2022/lib/utils/def.util.mjs +0 -8
  125. package/esm2022/lib/utils/index.mjs +0 -4
  126. package/fesm2022/ethlete-query.mjs +0 -3276
  127. package/fesm2022/ethlete-query.mjs.map +0 -1
  128. package/lib/auth/auth-provider.types.d.ts +0 -133
  129. package/lib/auth/auth-provider.utils.d.ts +0 -9
  130. package/lib/auth/basic-auth-provider.d.ts +0 -10
  131. package/lib/auth/bearer-auth-provider.d.ts +0 -23
  132. package/lib/auth/custom-header-auth-provider.d.ts +0 -9
  133. package/lib/class-validator/class-validator.utils.d.ts +0 -2
  134. package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +0 -151
  135. package/lib/devtools/pipes/query-short-name.pipe.d.ts +0 -8
  136. package/lib/devtools/utils/query-client.utils.d.ts +0 -12
  137. package/lib/directives/infinity-query-trigger.directive.d.ts +0 -15
  138. package/lib/directives/infinity-query.directive.d.ts +0 -49
  139. package/lib/directives/query.directive.d.ts +0 -63
  140. package/lib/entity/entity-store.d.ts +0 -25
  141. package/lib/entity/entity.types.d.ts +0 -25
  142. package/lib/entity/entity.utils.d.ts +0 -12
  143. package/lib/gql/gql.d.ts +0 -6
  144. package/lib/infinite-query/infinity-query.d.ts +0 -29
  145. package/lib/infinite-query/infinity-query.types.d.ts +0 -104
  146. package/lib/infinite-query/infinity-query.utils.d.ts +0 -4
  147. package/lib/logger/logger.d.ts +0 -12
  148. package/lib/query/query.d.ts +0 -60
  149. package/lib/query/query.types.d.ts +0 -388
  150. package/lib/query/query.utils.d.ts +0 -91
  151. package/lib/query-client/query-client.d.ts +0 -29
  152. package/lib/query-client/query-client.types.d.ts +0 -82
  153. package/lib/query-client/query-client.utils.d.ts +0 -4
  154. package/lib/query-creator/query-creator.d.ts +0 -20
  155. package/lib/query-creator/query-creator.types.d.ts +0 -13
  156. package/lib/query-form/query-form.d.ts +0 -89
  157. package/lib/query-form/query-form.types.d.ts +0 -138
  158. package/lib/query-form/query-form.utils.d.ts +0 -13
  159. package/lib/query-store/query-store.d.ts +0 -31
  160. package/lib/request/request.d.ts +0 -3
  161. package/lib/request/request.types.d.ts +0 -126
  162. package/lib/request/request.util.d.ts +0 -92
  163. package/lib/symfony/symfony.types.d.ts +0 -19
  164. package/lib/symfony/symfony.utils.d.ts +0 -6
  165. package/lib/utils/data.utils.d.ts +0 -59
  166. package/lib/utils/deep-freeze.util.d.ts +0 -1
  167. /package/{lib/auth/index.d.ts → src/lib/auth/index.ts} +0 -0
  168. /package/{lib/class-validator/index.d.ts → src/lib/class-validator/index.ts} +0 -0
  169. /package/{lib/devtools/components/query-devtools/index.d.ts → src/lib/devtools/components/query-devtools/index.ts} +0 -0
  170. /package/{lib/devtools/pipes/index.d.ts → src/lib/devtools/pipes/index.ts} +0 -0
  171. /package/{lib/devtools/utils/index.d.ts → src/lib/devtools/utils/index.ts} +0 -0
  172. /package/{lib/directives/index.d.ts → src/lib/directives/index.ts} +0 -0
  173. /package/{lib/entity/index.d.ts → src/lib/entity/index.ts} +0 -0
  174. /package/{lib/gql/index.d.ts → src/lib/gql/index.ts} +0 -0
  175. /package/{lib/infinite-query/index.d.ts → src/lib/infinite-query/index.ts} +0 -0
  176. /package/{lib/logger/index.d.ts → src/lib/logger/index.ts} +0 -0
  177. /package/{lib/query/index.d.ts → src/lib/query/index.ts} +0 -0
  178. /package/{lib/query-client/index.d.ts → src/lib/query-client/index.ts} +0 -0
  179. /package/{lib/query-creator/index.d.ts → src/lib/query-creator/index.ts} +0 -0
  180. /package/{lib/query-form/index.d.ts → src/lib/query-form/index.ts} +0 -0
  181. /package/{lib/query-store/index.d.ts → src/lib/query-store/index.ts} +0 -0
  182. /package/{lib/request/index.d.ts → src/lib/request/index.ts} +0 -0
  183. /package/{lib/symfony/index.d.ts → src/lib/symfony/index.ts} +0 -0
  184. /package/{lib/utils/index.d.ts → src/lib/utils/index.ts} +0 -0
package/.eslintrc.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "extends": ["../../.eslintrc.json"],
3
+ "ignorePatterns": ["!**/*"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts"],
7
+ "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
8
+ "rules": {
9
+ "@angular-eslint/directive-selector": [
10
+ "error",
11
+ {
12
+ "type": "attribute",
13
+ "prefix": "et"
14
+ }
15
+ ],
16
+ "@angular-eslint/component-selector": [
17
+ "error",
18
+ {
19
+ "type": ["element", "attribute"],
20
+ "prefix": "et",
21
+ "style": "kebab-case"
22
+ }
23
+ ],
24
+ "@angular-eslint/no-host-metadata-property": "off"
25
+ }
26
+ },
27
+ {
28
+ "files": ["*.html"],
29
+ "extends": ["plugin:@nx/angular-template"],
30
+ "rules": {}
31
+ },
32
+ {
33
+ "files": ["*.json"],
34
+ "parser": "jsonc-eslint-parser",
35
+ "rules": {
36
+ "@nx/dependency-checks": "off"
37
+ }
38
+ }
39
+ ]
40
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @ethlete/query
2
2
 
3
+ ## 5.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`3c0cb69`](https://github.com/ethlete-io/ethdk/commit/3c0cb69d3942c1e562905b940b2d75fcb81e77f9) Thanks [@TomTomB](https://github.com/TomTomB)! - Add option to `QueryForm` `resetAllFieldsToDefault` method to skip specific fields by their key
8
+
9
+ - [`3c0cb69`](https://github.com/ethlete-io/ethdk/commit/3c0cb69d3942c1e562905b940b2d75fcb81e77f9) Thanks [@TomTomB](https://github.com/TomTomB)! - Add `appendDefaultValueToUrl` option to `QueryField`
10
+
11
+ ## 5.15.1
12
+
13
+ ### Patch Changes
14
+
15
+ - [`69ee325`](https://github.com/ethlete-io/ethdk/commit/69ee32561bf0df78569a1649053a37edf9741b9c) Thanks [@TomTomB](https://github.com/TomTomB)! - Bump only for updating peer deps
16
+
3
17
  ## 5.15.0
4
18
 
5
19
  ### Minor Changes
package/jest.config.ts ADDED
@@ -0,0 +1,23 @@
1
+ /* eslint-disable */
2
+ export default {
3
+ displayName: 'query',
4
+ preset: '../../jest.preset.js',
5
+ setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
6
+ globals: {},
7
+ coverageDirectory: '../../coverage/libs/query',
8
+ transform: {
9
+ '^.+\\.(ts|mjs|js|html)$': [
10
+ 'jest-preset-angular',
11
+ {
12
+ tsconfig: '<rootDir>/tsconfig.spec.json',
13
+ stringifyContentPathRegex: '\\.(html|svg)$',
14
+ },
15
+ ],
16
+ },
17
+ transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
18
+ snapshotSerializers: [
19
+ 'jest-preset-angular/build/serializers/no-ng-attributes',
20
+ 'jest-preset-angular/build/serializers/ng-snapshot',
21
+ 'jest-preset-angular/build/serializers/html-comment',
22
+ ],
23
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/libs/query",
4
+ "assets": ["CHANGELOG.md"],
5
+ "lib": {
6
+ "entryFile": "src/index.ts"
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,30 +1,13 @@
1
1
  {
2
2
  "name": "@ethlete/query",
3
- "version": "5.15.0",
3
+ "version": "5.16.0",
4
4
  "peerDependencies": {
5
- "@angular/common": "18.0.3",
6
- "@angular/core": "18.0.3",
7
- "@angular/forms": "18.0.3",
8
- "@angular/router": "18.0.3",
5
+ "@angular/common": "18.0.6",
6
+ "@angular/core": "18.0.6",
7
+ "@angular/forms": "18.0.6",
8
+ "@angular/router": "18.0.6",
9
9
  "@ethlete/core": "^4.0.3",
10
10
  "@ethlete/types": "^1.6.2",
11
11
  "rxjs": "^7.8.1"
12
- },
13
- "module": "fesm2022/ethlete-query.mjs",
14
- "typings": "index.d.ts",
15
- "exports": {
16
- "./package.json": {
17
- "default": "./package.json"
18
- },
19
- ".": {
20
- "types": "./index.d.ts",
21
- "esm2022": "./esm2022/ethlete-query.mjs",
22
- "esm": "./esm2022/ethlete-query.mjs",
23
- "default": "./fesm2022/ethlete-query.mjs"
24
- }
25
- },
26
- "sideEffects": false,
27
- "dependencies": {
28
- "tslib": "^2.3.0"
29
12
  }
30
- }
13
+ }
package/project.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "query",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/query/src",
5
+ "prefix": "et",
6
+ "tags": [],
7
+ "projectType": "library",
8
+ "targets": {
9
+ "build": {
10
+ "executor": "@nx/angular:package",
11
+ "outputs": ["{workspaceRoot}/dist/{projectRoot}"],
12
+ "options": {
13
+ "project": "libs/query/ng-package.json"
14
+ },
15
+ "configurations": {
16
+ "production": {
17
+ "tsConfig": "libs/query/tsconfig.lib.prod.json"
18
+ },
19
+ "development": {
20
+ "tsConfig": "libs/query/tsconfig.lib.json"
21
+ }
22
+ },
23
+ "defaultConfiguration": "production"
24
+ },
25
+ "test": {
26
+ "executor": "@nx/jest:jest",
27
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
28
+ "options": {
29
+ "jestConfig": "libs/query/jest.config.ts"
30
+ }
31
+ },
32
+ "lint": {
33
+ "executor": "@nx/eslint:lint",
34
+ "outputs": ["{options.outputFile}"]
35
+ }
36
+ }
37
+ }
@@ -16,3 +16,5 @@ export * from './lib/query-store';
16
16
  export * from './lib/request';
17
17
  export * from './lib/symfony';
18
18
  export * from './lib/utils';
19
+
20
+ // Burst cache
@@ -0,0 +1,160 @@
1
+ import { AnyQueryCreator, QueryArgsOf, QueryResponseOf } from '../query-creator';
2
+
3
+ export interface AuthProvider {
4
+ /**
5
+ * The up to date auth header.
6
+ * This will automatically get injected into every query that has the `secure` property set to `true`.
7
+ */
8
+ header: Record<string, string>;
9
+
10
+ /**
11
+ * Cleanup function that gets called once the auth provider gets destroyed.
12
+ * @internal
13
+ */
14
+ cleanUp(): void;
15
+ }
16
+
17
+ export interface AuthProviderBasicConfig {
18
+ /**
19
+ * Basic auth username
20
+ */
21
+ username: string;
22
+
23
+ /**
24
+ * Basic auth password
25
+ */
26
+ password: string;
27
+ }
28
+
29
+ export interface AuthProviderCustomHeaderConfig {
30
+ /**
31
+ * The custom header name
32
+ * @example `X-Api-Key`
33
+ */
34
+ name: string;
35
+
36
+ /**
37
+ * The custom header value
38
+ * @example `myApiKey`
39
+ */
40
+ value: string;
41
+ }
42
+
43
+ export interface AuthProviderBearerConfig<T extends AnyQueryCreator> {
44
+ /**
45
+ * The initial jwt
46
+ */
47
+ token?: string;
48
+
49
+ /**
50
+ * Refresh token configuration
51
+ */
52
+ refreshConfig?: BearerRefreshConfig<T>;
53
+ }
54
+
55
+ export interface BearerRefreshConfig<T extends AnyQueryCreator> {
56
+ /**
57
+ * The query used to trade the refresh token for a new token response.
58
+ */
59
+ queryCreator: T;
60
+
61
+ /**
62
+ * Determines if the token should be refreshed if **any** query returns a 401 response.
63
+ * @default true
64
+ */
65
+ refreshOnUnauthorizedResponse?: boolean;
66
+
67
+ /**
68
+ * The initial refresh token
69
+ */
70
+ token?: string;
71
+
72
+ /**
73
+ * The cookie name where the refresh token is stored
74
+ */
75
+ cookieName?: string;
76
+
77
+ /**
78
+ * The domain of the cookie. If not set, the current origin will be used.
79
+ *
80
+ * @example
81
+ * "https://example.com" -> "example.com"
82
+ * "https://sub.example.com" -> "example.com"
83
+ */
84
+ cookieDomain?: string;
85
+
86
+ /**
87
+ * The days until the cookie expires
88
+ * @default 30
89
+ */
90
+ cookieExpiresInDays?: number;
91
+
92
+ /**
93
+ * The path of the cookie
94
+ * @default '/'
95
+ */
96
+ cookiePath?: string;
97
+
98
+ /**
99
+ * Enable or disable the cookie
100
+ * @default true
101
+ */
102
+ cookieEnabled?: boolean;
103
+
104
+ /**
105
+ * The same site property of the cookie
106
+ * @default 'lax'
107
+ */
108
+ cookieSameSite?: 'strict' | 'none' | 'lax';
109
+
110
+ /**
111
+ * The time in milliseconds before the token expires when the refresh should be triggered.
112
+ * @default 300000 (5 minutes)
113
+ */
114
+ refreshBuffer?: number;
115
+
116
+ /**
117
+ * The expires in property name inside the jwt body
118
+ * @default 'exp'
119
+ */
120
+ expiresInPropertyName?: string;
121
+
122
+ /**
123
+ * Determines when the token should be refreshed.
124
+ * @default AuthBearerRefreshStrategy.BeforeExpiration
125
+ */
126
+ strategy?: AuthBearerRefreshStrategy;
127
+
128
+ /**
129
+ * Adapter function used to build the request body for the refresh request.
130
+ * @default { body: { refreshToken: "refreshToken" } }
131
+ */
132
+ requestArgsAdapter?: (tokens: { token: string | null; refreshToken: string }) => QueryArgsOf<T>;
133
+
134
+ /**
135
+ * Adapter function used to extract the token and refreshToken from the response.
136
+ * @default { token: "token", refreshToken: "refreshToken" }
137
+ */
138
+ responseAdapter?: (response: NonNullable<QueryResponseOf<T>>) => TokenResponse;
139
+ }
140
+
141
+ export const enum AuthBearerRefreshStrategy {
142
+ /**
143
+ * Automatically refresh the token 5 minutes before expiration.
144
+ * The time can be configured with the `refreshBuffer` property.
145
+ */
146
+ BeforeExpiration = 'beforeExpiration',
147
+ }
148
+
149
+ export interface TokenResponse {
150
+ /**
151
+ * The access token used inside the authorization http header.
152
+ */
153
+ token: string | null;
154
+
155
+ /**
156
+ * The refresh token used for requesting a new token response once the current tokes is expired.
157
+ * This property is required if the bearer refresh config is set.
158
+ */
159
+ refreshToken: string | null;
160
+ }
@@ -0,0 +1,39 @@
1
+ import { AnyQueryCreator } from '../query-creator';
2
+ import { AuthProvider } from './auth-provider.types';
3
+ import { BasicAuthProvider } from './basic-auth-provider';
4
+ import { BearerAuthProvider } from './bearer-auth-provider';
5
+ import { CustomHeaderAuthProvider } from './custom-header-auth-provider';
6
+
7
+ export const isBasicAuthProvider = (authProvider: AuthProvider): authProvider is BasicAuthProvider =>
8
+ authProvider instanceof BasicAuthProvider;
9
+
10
+ export const isBearerAuthProvider = <T extends AnyQueryCreator>(
11
+ authProvider: AuthProvider,
12
+ ): authProvider is BearerAuthProvider<T> => authProvider instanceof BearerAuthProvider;
13
+
14
+ export const isCustomHeaderAuthProvider = (authProvider: AuthProvider): authProvider is CustomHeaderAuthProvider =>
15
+ authProvider instanceof CustomHeaderAuthProvider;
16
+
17
+ export const decryptBearer = <Result = Record<string, unknown>>(token: string) => {
18
+ try {
19
+ const base64Url = token.split('.')[1];
20
+
21
+ if (!base64Url) {
22
+ return null;
23
+ }
24
+
25
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
26
+ const jsonPayload = decodeURIComponent(
27
+ atob(base64)
28
+ .split('')
29
+ .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
30
+ .join(''),
31
+ );
32
+
33
+ return JSON.parse(jsonPayload) as Result;
34
+ } catch (error) {
35
+ console.error(`Invalid bearer token: ${token}`, error);
36
+
37
+ return null;
38
+ }
39
+ };
@@ -0,0 +1,17 @@
1
+ import { AuthProvider, AuthProviderBasicConfig } from './auth-provider.types';
2
+
3
+ export class BasicAuthProvider implements AuthProvider {
4
+ get header() {
5
+ return { Authorization: `Basic ${this._basicAuthString}` };
6
+ }
7
+
8
+ private get _basicAuthString() {
9
+ return btoa(`${this._config.username}:${this._config.password}`);
10
+ }
11
+
12
+ constructor(public _config: AuthProviderBasicConfig) {}
13
+
14
+ cleanUp(): void {
15
+ // noop
16
+ }
17
+ }
@@ -0,0 +1,243 @@
1
+ import { deleteCookie, getCookie, setCookie } from '@ethlete/core';
2
+ import { BehaviorSubject, Subject, takeUntil, tap, timer } from 'rxjs';
3
+ import {
4
+ isQueryStateFailure,
5
+ isQueryStateLoading,
6
+ isQueryStateSuccess,
7
+ switchQueryState,
8
+ takeUntilResponse,
9
+ } from '../query';
10
+ import { AnyQueryCreator, ConstructQuery, QueryResponseOf } from '../query-creator';
11
+ import {
12
+ AuthBearerRefreshStrategy,
13
+ AuthProvider,
14
+ AuthProviderBearerConfig,
15
+ TokenResponse,
16
+ } from './auth-provider.types';
17
+ import { decryptBearer } from './auth-provider.utils';
18
+
19
+ export class BearerAuthProvider<T extends AnyQueryCreator> implements AuthProvider {
20
+ private readonly _destroy$ = new Subject<boolean>();
21
+ private readonly _currentRefreshQuery$ = new BehaviorSubject<ConstructQuery<T> | null>(null);
22
+
23
+ private readonly _tokens$ = new BehaviorSubject<TokenResponse>({
24
+ token: null,
25
+ refreshToken: null,
26
+ });
27
+
28
+ get header() {
29
+ return { Authorization: `Bearer ${this._tokens$.getValue().token}` };
30
+ }
31
+
32
+ get tokens$() {
33
+ return this._tokens$.asObservable();
34
+ }
35
+
36
+ get tokens() {
37
+ return this._tokens$.getValue();
38
+ }
39
+
40
+ get currentRefreshQuery$() {
41
+ return this._currentRefreshQuery$.asObservable();
42
+ }
43
+
44
+ get currentRefreshQuery() {
45
+ return this._currentRefreshQuery$.getValue();
46
+ }
47
+
48
+ get shouldRefreshOnUnauthorizedResponse() {
49
+ return (
50
+ !!this._config.refreshConfig &&
51
+ (this._config.refreshConfig.refreshOnUnauthorizedResponse === undefined ||
52
+ this._config.refreshConfig.refreshOnUnauthorizedResponse)
53
+ );
54
+ }
55
+
56
+ constructor(public _config: AuthProviderBearerConfig<T>) {
57
+ const cookieEnabled = _config.refreshConfig?.cookieEnabled ?? true;
58
+ const cookieToken =
59
+ _config.refreshConfig?.cookieName && cookieEnabled ? getCookie(_config.refreshConfig.cookieName) ?? null : null;
60
+
61
+ this._tokens$.next({
62
+ token: _config.token || null,
63
+ refreshToken: _config.refreshConfig?.token || cookieToken || null,
64
+ });
65
+
66
+ if (!this.tokens.token && !this.tokens.refreshToken) {
67
+ if (!_config.refreshConfig?.cookieName) {
68
+ console.error(
69
+ 'A BearerAuthProvider was created without token or refresh token. You should provide at least a cookieName where the refresh token might be stored.',
70
+ );
71
+ }
72
+
73
+ return;
74
+ }
75
+
76
+ if (this.tokens.token) {
77
+ this._prepareForRefresh();
78
+
79
+ if (this.tokens.refreshToken && this._config.refreshConfig?.cookieName && cookieEnabled) {
80
+ this._setCookie();
81
+ }
82
+ } else if (this.tokens.refreshToken) {
83
+ this._refreshQuery();
84
+ }
85
+ }
86
+
87
+ cleanUp(): void {
88
+ this._destroy$.next(true);
89
+ this._destroy$.unsubscribe();
90
+ this._currentRefreshQuery$.next(null);
91
+
92
+ if (this._config.refreshConfig?.cookieName) {
93
+ this._deleteCookie();
94
+ }
95
+ }
96
+
97
+ enableCookie() {
98
+ if (!this._config.refreshConfig?.cookieName) {
99
+ throw new Error('No cookie name was provided');
100
+ }
101
+
102
+ if (!this.tokens.refreshToken) {
103
+ throw new Error('No refresh token was provided');
104
+ }
105
+
106
+ this._config.refreshConfig.cookieEnabled = true;
107
+
108
+ this._setCookie();
109
+ }
110
+
111
+ disableCookie() {
112
+ if (!this._config.refreshConfig?.cookieName) {
113
+ throw new Error('No cookie name was provided');
114
+ }
115
+
116
+ this._config.refreshConfig.cookieEnabled = false;
117
+
118
+ this._deleteCookie();
119
+ }
120
+
121
+ private _setCookie() {
122
+ if (!this._config.refreshConfig?.cookieName || !this.tokens.refreshToken) return;
123
+
124
+ setCookie(
125
+ this._config.refreshConfig.cookieName,
126
+ this.tokens.refreshToken,
127
+ this._config.refreshConfig.cookieExpiresInDays,
128
+ this._config.refreshConfig.cookieDomain,
129
+ this._config.refreshConfig.cookiePath,
130
+ this._config.refreshConfig.cookieSameSite,
131
+ );
132
+ }
133
+
134
+ private _deleteCookie() {
135
+ if (!this._config.refreshConfig?.cookieName) return;
136
+
137
+ deleteCookie(
138
+ this._config.refreshConfig.cookieName,
139
+ this._config.refreshConfig.cookiePath,
140
+ this._config.refreshConfig.cookieDomain,
141
+ );
142
+ }
143
+
144
+ private _prepareForRefresh() {
145
+ if (!this.tokens.token || !this._config.refreshConfig) {
146
+ return;
147
+ }
148
+
149
+ const bearer = decryptBearer(this.tokens.token);
150
+
151
+ if (!bearer) {
152
+ return;
153
+ }
154
+
155
+ const expiresInPropertyName = this._config.refreshConfig.expiresInPropertyName || 'exp';
156
+ const expiresIn = bearer[expiresInPropertyName] as number | undefined;
157
+ const fiveMinutes = 5 * 60 * 1000;
158
+ const refreshBuffer = this._config.refreshConfig.refreshBuffer ?? fiveMinutes;
159
+
160
+ if (expiresIn === undefined) {
161
+ throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
162
+ }
163
+
164
+ const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
165
+
166
+ const strategy = this._config.refreshConfig.strategy ?? AuthBearerRefreshStrategy.BeforeExpiration;
167
+
168
+ switch (strategy) {
169
+ case AuthBearerRefreshStrategy.BeforeExpiration:
170
+ timer(remainingTime)
171
+ .pipe(takeUntil(this._destroy$))
172
+ .subscribe(() => this._refreshQuery());
173
+ break;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @internal
179
+ */
180
+ _refreshQuery() {
181
+ const currentQuery = this._currentRefreshQuery$.getValue();
182
+
183
+ if (isQueryStateLoading(currentQuery?.rawState)) {
184
+ return currentQuery;
185
+ }
186
+
187
+ const currentRefreshToken = this.tokens.refreshToken;
188
+
189
+ if (!currentRefreshToken || !this._config.refreshConfig) {
190
+ return;
191
+ }
192
+
193
+ const args = this._config.refreshConfig.requestArgsAdapter?.({
194
+ token: this.tokens.token,
195
+ refreshToken: currentRefreshToken,
196
+ }) ?? {
197
+ body: { refreshToken: currentRefreshToken },
198
+ };
199
+
200
+ const query = this._config.refreshConfig.queryCreator.prepare(args).execute({ skipCache: true });
201
+
202
+ this._currentRefreshQuery$.next(query as ConstructQuery<T>);
203
+
204
+ this._currentRefreshQuery$
205
+ .pipe(
206
+ switchQueryState(),
207
+ tap((state) => {
208
+ if (isQueryStateSuccess(state)) {
209
+ if (this._config.refreshConfig?.responseAdapter) {
210
+ const tokens = this._config.refreshConfig.responseAdapter(state.response as QueryResponseOf<T>);
211
+
212
+ this._tokens$.next(tokens);
213
+ } else {
214
+ this._tokens$.next({
215
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
216
+ token: (state.response as any)['token'],
217
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
218
+ refreshToken: (state.response as any)['refreshToken'],
219
+ });
220
+ }
221
+ const cookieEnabled = this._config.refreshConfig?.cookieEnabled ?? true;
222
+
223
+ if (this._config.refreshConfig?.cookieName && this.tokens.refreshToken && cookieEnabled) {
224
+ this._setCookie();
225
+ }
226
+
227
+ this._prepareForRefresh();
228
+ } else if (isQueryStateFailure(state)) {
229
+ this._tokens$.next({ token: null, refreshToken: null });
230
+
231
+ if (this._config.refreshConfig?.cookieName) {
232
+ this._deleteCookie();
233
+ }
234
+ }
235
+ }),
236
+ takeUntilResponse(),
237
+ takeUntil(this._destroy$),
238
+ )
239
+ .subscribe();
240
+
241
+ return query;
242
+ }
243
+ }
@@ -0,0 +1,13 @@
1
+ import { AuthProvider, AuthProviderCustomHeaderConfig } from './auth-provider.types';
2
+
3
+ export class CustomHeaderAuthProvider implements AuthProvider {
4
+ get header() {
5
+ return { [this._config.name]: this._config.value };
6
+ }
7
+
8
+ constructor(public _config: AuthProviderCustomHeaderConfig) {}
9
+
10
+ cleanUp(): void {
11
+ // noop
12
+ }
13
+ }
@@ -1,6 +1,7 @@
1
1
  import { HttpStatusCode } from '../request';
2
+
2
3
  export interface ClassValidatorError {
3
- statusCode: HttpStatusCode;
4
- message: string[];
5
- error: string;
4
+ statusCode: HttpStatusCode;
5
+ message: string[];
6
+ error: string;
6
7
  }
@@ -0,0 +1,5 @@
1
+ import { ClassValidatorError } from './class-validator.types';
2
+
3
+ export const isClassValidatorError = (error: unknown): error is ClassValidatorError => {
4
+ return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
5
+ };