@intellegens/cornerstone-client 0.0.9999-alpha-6 → 0.0.9999-alpha-8

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.
package/README.md CHANGED
@@ -7,19 +7,19 @@ This project includes services for easy communication from a Cornerstone client
7
7
  1. Install dependencies:
8
8
 
9
9
  ```bash
10
- npm install
10
+ pnpm install
11
11
  ```
12
12
 
13
13
  2. Build the project:
14
14
 
15
15
  ```bash
16
- npm run build
16
+ pnpm run build
17
17
  ```
18
18
 
19
19
  3. Run Client demo HTTP server:
20
20
 
21
21
  ```bash
22
- npm run demo
22
+ pnpm run demo
23
23
  ```
24
24
 
25
25
  4. Open
@@ -214,7 +214,7 @@ export class CollectionViewAdapter {
214
214
  abortController = new AbortController();
215
215
  this._currentAbortController = abortController;
216
216
  const definition = this._parseOptionsToDefinition();
217
- const response = await this._readClient.readSelectedAsync(definition, abortController?.signal);
217
+ const response = await this._readClient.readSelected(definition, abortController?.signal);
218
218
  if (!response.ok) {
219
219
  throw response.error;
220
220
  }
@@ -1 +1,2 @@
1
1
  export * from './dto';
2
+ export * from './policy';
@@ -1 +1,2 @@
1
1
  export * from './dto';
2
+ export * from './policy';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Interface for policies that can be checked.
3
+ *
4
+ * @interface IPolicy<TParams extends unknown[]>
5
+ * @template TParams - The type of the parameters that will be passed to the policy check function.
6
+ */
7
+ export interface IPolicy<TParams extends unknown[]> {
8
+ /**
9
+ * Checks if the policy is valid for the given parameters.
10
+ *
11
+ * @param params - Parameters to pass to the policy check function.
12
+ * @return A promise that resolves if the policy is valid, rejects otherwise.
13
+ */
14
+ check(...params: TParams): Promise<boolean>;
15
+ }
16
+ /**
17
+ * Base class for policies. It implements the IPolicy interface.
18
+ *
19
+ * @class PolicyBase
20
+ * @template TParams - The type of the parameters that will be passed to the policy check function.
21
+ */
22
+ export declare class PolicyBase<TParams extends unknown[]> implements IPolicy<TParams> {
23
+ /**
24
+ * Checks if the policy is valid for the given parameters.
25
+ *
26
+ * @return A promise that resolves if the policy is valid, rejects otherwise.
27
+ * @throws {Error} Error if not implemented.
28
+ */
29
+ check(): Promise<boolean>;
30
+ }
31
+ /**
32
+ * Class for policies that can be checked.
33
+ *
34
+ * @class Policy
35
+ * @template TParams - The type of the parameters that will be passed to the policy check function.
36
+ */
37
+ export declare class Policy<TParams extends unknown[]> extends PolicyBase<TParams> {
38
+ private readonly _verificationFn;
39
+ /**
40
+ * Creates a new policy.
41
+ *
42
+ * @param verificationFn - The function that will be used to verify the policy.
43
+ */
44
+ constructor(verificationFn: (...params: TParams) => Promise<boolean>);
45
+ /**
46
+ * Checks if the policy is valid for the given parameters.
47
+ *
48
+ * @param params - Parameters to pass to the policy check function.
49
+ * @return A promise that resolves if the policy is valid, rejects otherwise.
50
+ */
51
+ check(...params: TParams): Promise<boolean>;
52
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Base class for policies. It implements the IPolicy interface.
3
+ *
4
+ * @class PolicyBase
5
+ * @template TParams - The type of the parameters that will be passed to the policy check function.
6
+ */
7
+ export class PolicyBase {
8
+ /**
9
+ * Checks if the policy is valid for the given parameters.
10
+ *
11
+ * @return A promise that resolves if the policy is valid, rejects otherwise.
12
+ * @throws {Error} Error if not implemented.
13
+ */
14
+ async check() {
15
+ throw new Error('Not implemented');
16
+ }
17
+ }
18
+ /**
19
+ * Class for policies that can be checked.
20
+ *
21
+ * @class Policy
22
+ * @template TParams - The type of the parameters that will be passed to the policy check function.
23
+ */
24
+ export class Policy extends PolicyBase {
25
+ _verificationFn;
26
+ /**
27
+ * Creates a new policy.
28
+ *
29
+ * @param verificationFn - The function that will be used to verify the policy.
30
+ */
31
+ constructor(verificationFn) {
32
+ super();
33
+ this._verificationFn = verificationFn;
34
+ }
35
+ /**
36
+ * Checks if the policy is valid for the given parameters.
37
+ *
38
+ * @param params - Parameters to pass to the policy check function.
39
+ * @return A promise that resolves if the policy is valid, rejects otherwise.
40
+ */
41
+ async check(...params) {
42
+ return this._verificationFn(...params);
43
+ }
44
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@intellegens/cornerstone-client",
3
- "version": "0.0.9999-alpha-6",
3
+ "version": "0.0.9999-alpha-8",
4
4
  "private": false,
5
5
  "publishable": true,
6
- "main": "./index.js",
7
- "types": "./index.d.ts",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
8
  "type": "module",
9
9
  "author": "Intellegens",
10
10
  "license": "MIT",
@@ -14,32 +14,32 @@
14
14
  "cornerstone"
15
15
  ],
16
16
  "scripts": {
17
- "clean": "npx --yes rimraf '{dist}'",
17
+ "clean": "pnpx rimraf '{dist}'",
18
18
  "test": "jest",
19
19
  "coverage": "jest --coverage",
20
- "build": "npm run clean && tsc && tsc-alias",
21
- "start": "npm run build && npx vite build && npx tsx ./demo"
20
+ "build": "pnpm run clean && tsc && tsc-alias",
21
+ "start": "pnpm run build && pnpx vite build && pnpx tsx ./demo"
22
22
  },
23
23
  "devDependencies": {
24
- "@eslint/js": "^9.21.0",
25
- "@types/express": "^5.0.0",
26
- "@types/jest": "^29.5.12",
27
- "@types/node": "^22.13.9",
28
- "eslint": "^9.21.0",
29
- "eslint-config-prettier": "^10.0.1",
30
- "express": "^4.21.2",
31
- "globals": "^16.0.0",
32
- "jest": "^29.7.0",
33
- "jest-environment-jsdom": "^29.7.0",
34
- "prettier": "^3.5.2",
35
- "ts-jest": "^29.1.2",
36
- "tsc-alias": "^1.8.11",
37
- "typescript-eslint": "^8.26.0",
38
- "vite": "6.3.4",
39
- "zod": "^3.24.2"
24
+ "@eslint/js": "catalog:",
25
+ "@types/express": "catalog:",
26
+ "@types/jest": "catalog:",
27
+ "@types/node": "catalog:",
28
+ "eslint": "catalog:",
29
+ "eslint-config-prettier": "catalog:",
30
+ "express": "catalog:",
31
+ "globals": "catalog:",
32
+ "jest": "catalog:",
33
+ "jest-environment-jsdom": "catalog:",
34
+ "prettier": "catalog:",
35
+ "ts-jest": "catalog:",
36
+ "tsc-alias": "catalog:",
37
+ "typescript-eslint": "catalog:",
38
+ "vite": "catalog:",
39
+ "zod": "catalog:"
40
40
  },
41
41
  "dependencies": {
42
42
  "http-proxy-middleware": "^3.0.3",
43
- "typescript": "^5.4.5"
43
+ "typescript": "catalog:"
44
44
  }
45
45
  }
@@ -23,7 +23,7 @@ export declare class ApiCrudControllerClient<TKey, TDto extends IIdentifiable<TK
23
23
  * @param {AbortSignal} [signal] - Optional cancellation signal
24
24
  * @returns The created entity DTO
25
25
  */
26
- createAsync(dto: TDto, signal?: AbortSignal): Promise<ApiResponseDto<TDto, EmptyMetadataDto>>;
26
+ create(dto: TDto, signal?: AbortSignal): Promise<ApiResponseDto<TDto, EmptyMetadataDto>>;
27
27
  /**
28
28
  * Updates an existing entity.
29
29
  * @param id - The ID of the entity to update
@@ -31,11 +31,11 @@ export declare class ApiCrudControllerClient<TKey, TDto extends IIdentifiable<TK
31
31
  * @param {AbortSignal} [signal] - Optional cancellation signal
32
32
  * @returns The updated entity DTO
33
33
  */
34
- updateAsync(id: TKey, dto: TDto, signal?: AbortSignal): Promise<ApiResponseDto<TDto, EmptyMetadataDto>>;
34
+ update(id: TKey, dto: TDto, signal?: AbortSignal): Promise<ApiResponseDto<TDto, EmptyMetadataDto>>;
35
35
  /**
36
36
  * Deletes an entity by ID.
37
37
  * @param id - The ID of the entity to delete
38
38
  * @param {AbortSignal} [signal] - Optional cancellation signal
39
39
  */
40
- deleteAsync(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<null, EmptyMetadataDto>>;
40
+ delete(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
41
41
  }
@@ -1,7 +1,7 @@
1
1
  import { apiInitializationService } from '../ApiInitializationService';
2
2
  import { ApiReadControllerClient } from '../ApiReadControllerClient';
3
3
  import { ErrorCode } from '../../../data';
4
- import { fail } from '../../../utils/result';
4
+ import { fail, ok } from '../../../utils/result';
5
5
  /**
6
6
  * Generic API client for consuming any Cornerstone CrudController
7
7
  *
@@ -25,10 +25,10 @@ export class ApiCrudControllerClient extends ApiReadControllerClient {
25
25
  * @param {AbortSignal} [signal] - Optional cancellation signal
26
26
  * @returns The created entity DTO
27
27
  */
28
- async createAsync(dto, signal) {
28
+ async create(dto, signal) {
29
29
  try {
30
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/Create`);
31
- const res = await this.httpService.requestAsync(url, {
30
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/Create`);
31
+ const res = await this.httpService.request(url, {
32
32
  method: 'POST',
33
33
  headers: { 'Content-Type': 'application/json' },
34
34
  body: JSON.stringify(dto),
@@ -36,9 +36,9 @@ export class ApiCrudControllerClient extends ApiReadControllerClient {
36
36
  signal,
37
37
  });
38
38
  if (!res.ok) {
39
- return (await res.json());
39
+ return fail(await res.json());
40
40
  }
41
- return (await res.json());
41
+ return ok(await res.json());
42
42
  }
43
43
  catch (err) {
44
44
  console.error(err);
@@ -58,10 +58,10 @@ export class ApiCrudControllerClient extends ApiReadControllerClient {
58
58
  * @param {AbortSignal} [signal] - Optional cancellation signal
59
59
  * @returns The updated entity DTO
60
60
  */
61
- async updateAsync(id, dto, signal) {
61
+ async update(id, dto, signal) {
62
62
  try {
63
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/Update?id=${encodeURIComponent(String(id))}`);
64
- const res = await this.httpService.requestAsync(url, {
63
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/Update?id=${encodeURIComponent(String(id))}`);
64
+ const res = await this.httpService.request(url, {
65
65
  method: 'PUT',
66
66
  headers: { 'Content-Type': 'application/json' },
67
67
  body: JSON.stringify(dto),
@@ -69,9 +69,9 @@ export class ApiCrudControllerClient extends ApiReadControllerClient {
69
69
  signal,
70
70
  });
71
71
  if (!res.ok) {
72
- return (await res.json());
72
+ return fail(await res.json());
73
73
  }
74
- return (await res.json());
74
+ return ok(await res.json());
75
75
  }
76
76
  catch (err) {
77
77
  console.error(err);
@@ -89,18 +89,18 @@ export class ApiCrudControllerClient extends ApiReadControllerClient {
89
89
  * @param id - The ID of the entity to delete
90
90
  * @param {AbortSignal} [signal] - Optional cancellation signal
91
91
  */
92
- async deleteAsync(id, signal) {
92
+ async delete(id, signal) {
93
93
  try {
94
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/Delete?id=${encodeURIComponent(String(id))}`);
95
- const res = await this.httpService.requestAsync(url, {
94
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/Delete?id=${encodeURIComponent(String(id))}`);
95
+ const res = await this.httpService.request(url, {
96
96
  method: 'DELETE',
97
97
  credentials: 'include',
98
98
  signal,
99
99
  });
100
100
  if (!res.ok) {
101
- return { ok: false, ...(await res.json()) };
101
+ return fail(await res.json());
102
102
  }
103
- return (await res.json());
103
+ return ok(await res.json());
104
104
  }
105
105
  catch (err) {
106
106
  console.error(err);
@@ -3,7 +3,7 @@
3
3
  * The actual properties depend on what users configure in their appsettings.json or environment variables
4
4
  */
5
5
  type ISharedSettings = {
6
- [key: string]: any;
6
+ [key: string]: unknown;
7
7
  };
8
8
  import { IHttpService } from '../HttpService';
9
9
  /**
@@ -48,7 +48,7 @@ export declare class ApiInitializationService {
48
48
  * @param {AbortSignal} [signal] - Optional cancellation signal
49
49
  * @return {Promise<void>} Resolves when initialization is complete.
50
50
  */
51
- initializeAsync({ httpService, signal, }?: {
51
+ initialize({ httpService, signal, }?: {
52
52
  httpService?: IHttpService;
53
53
  signal?: AbortSignal;
54
54
  }): Promise<void>;
@@ -71,7 +71,7 @@ export declare class ApiInitializationService {
71
71
  * @return {Promise<string | undefined>} Returns detected API base URL
72
72
  * @throws {Error} Throws error if either of the detection methods fails
73
73
  */
74
- _getApiBaseUrlAsync(signal?: AbortSignal): Promise<string | undefined>;
74
+ _getApiBaseUrl(signal?: AbortSignal): Promise<string | undefined>;
75
75
  /**
76
76
  * Composes a full API URL for a provided endpoint path.
77
77
  *
@@ -80,7 +80,7 @@ export declare class ApiInitializationService {
80
80
  * @return {Promise<string | undefined>} Returns the API endpoint's URL
81
81
  * @throws {Error} Throws error if API base URL detection fails
82
82
  */
83
- getApiUrlAsync(...relativeEndpointPathSections: string[]): Promise<string>;
83
+ getApiUrl(...relativeEndpointPathSections: string[]): Promise<string>;
84
84
  /**
85
85
  * Removes starting '/' character
86
86
  * @param str String to remove the starting '/' character from
@@ -97,7 +97,7 @@ export declare class ApiInitializationService {
97
97
  * Fetches initialization info (shared settings) from the server
98
98
  * @returns Promise that resolves to shared settings or undefined if the request fails
99
99
  */
100
- private _getAppConfigAsync;
100
+ private _getAppConfig;
101
101
  }
102
102
  /**
103
103
  * Singleton instance of ApiInitializationService
@@ -1,3 +1,5 @@
1
+ // websettings.json configuration file contents type
2
+ import { fail, ok } from '../../../utils';
1
3
  import { defaultHttpService } from '../HttpService';
2
4
  /**
3
5
  * Central API configuration service, fetches and exposes Cornerstone API configuration
@@ -45,14 +47,14 @@ export class ApiInitializationService {
45
47
  * @param {AbortSignal} [signal] - Optional cancellation signal
46
48
  * @return {Promise<void>} Resolves when initialization is complete.
47
49
  */
48
- async initializeAsync({ httpService, signal, } = {}) {
50
+ async initialize({ httpService, signal, } = {}) {
49
51
  // Store configuration
50
52
  if (httpService !== undefined)
51
53
  this._httpService = httpService;
52
54
  // (Pre)detect API base URL
53
- await this._getApiBaseUrlAsync(signal);
55
+ await this._getApiBaseUrl(signal);
54
56
  // Initialize app config
55
- await this._getAppConfigAsync();
57
+ await this._getAppConfig();
56
58
  }
57
59
  /**
58
60
  * Gets configured HTTP service
@@ -75,7 +77,7 @@ export class ApiInitializationService {
75
77
  * @return {Promise<string | undefined>} Returns detected API base URL
76
78
  * @throws {Error} Throws error if either of the detection methods fails
77
79
  */
78
- async _getApiBaseUrlAsync(signal) {
80
+ async _getApiBaseUrl(signal) {
79
81
  // Check if API already detected; don't reattempt detection
80
82
  if (this._apiBaseUrlDetected)
81
83
  return this._apiBaseUrl;
@@ -91,7 +93,7 @@ export class ApiInitializationService {
91
93
  this._apiBaseError = undefined;
92
94
  // Check CORNERSTONE-API-BASEURL header for API base URL
93
95
  try {
94
- const currentUrlResponse = await this._httpService.requestAsync(window.location.href, {
96
+ const currentUrlResponse = await this._httpService.request(window.location.href, {
95
97
  method: 'GET',
96
98
  signal,
97
99
  });
@@ -110,7 +112,7 @@ export class ApiInitializationService {
110
112
  }
111
113
  // Check ./websettings.json header for API base URL
112
114
  try {
113
- const webSettingsResponse = await this._httpService.requestAsync('./websettings.json', {
115
+ const webSettingsResponse = await this._httpService.request('./websettings.json', {
114
116
  method: 'GET',
115
117
  signal,
116
118
  });
@@ -154,8 +156,8 @@ export class ApiInitializationService {
154
156
  * @return {Promise<string | undefined>} Returns the API endpoint's URL
155
157
  * @throws {Error} Throws error if API base URL detection fails
156
158
  */
157
- async getApiUrlAsync(...relativeEndpointPathSections) {
158
- const apiBaseUrl = await this._getApiBaseUrlAsync();
159
+ async getApiUrl(...relativeEndpointPathSections) {
160
+ const apiBaseUrl = await this._getApiBaseUrl();
159
161
  const domain = !apiBaseUrl ? '/' : `${this.removeEndsWithSlashChar(apiBaseUrl)}/`;
160
162
  const path = relativeEndpointPathSections.map(section => this.removeStartsWithSlashChar(this.removeEndsWithSlashChar(section))).join('/');
161
163
  return `${domain}${path}`;
@@ -181,13 +183,16 @@ export class ApiInitializationService {
181
183
  * Fetches initialization info (shared settings) from the server
182
184
  * @returns Promise that resolves to shared settings or undefined if the request fails
183
185
  */
184
- async _getAppConfigAsync() {
186
+ async _getAppConfig() {
185
187
  try {
186
188
  const response = await fetch(`${this._apiBaseUrl}/system/init`, { credentials: 'include' });
187
189
  if (!response.ok) {
188
- throw (await response.json());
190
+ throw fail(await response.json());
191
+ }
192
+ const apiResponse = ok(await response.json());
193
+ if (!apiResponse.ok) {
194
+ throw apiResponse.error;
189
195
  }
190
- const apiResponse = await response.json();
191
196
  // Return the settings from the nested structure
192
197
  this.appConfig = apiResponse.result.settings;
193
198
  }
@@ -12,7 +12,7 @@ export declare class ApiReadControllerClient<TKey, TDto extends IIdentifiable<TK
12
12
  * @param httpService HTTP service implementation to use for requests
13
13
  */
14
14
  constructor(baseControllerPath: string, httpService?: IHttpService);
15
- private _httpService?;
15
+ private readonly _httpService?;
16
16
  /**
17
17
  * Gets globally selected HTTP service
18
18
  */
@@ -22,19 +22,19 @@ export declare class ApiReadControllerClient<TKey, TDto extends IIdentifiable<TK
22
22
  * @param {AbortSignal} [signal] - Optional cancellation signal
23
23
  * @returns {Promise<ApiSuccessResponseDto<TDto>[], ReadMetadataDto>>} List of all entities with metadata
24
24
  */
25
- readAllAsync(signal?: AbortSignal): Promise<ApiResponseDto<TDto[], ReadMetadataDto>>;
25
+ readAll(signal?: AbortSignal): Promise<ApiResponseDto<TDto[], ReadMetadataDto>>;
26
26
  /**
27
27
  * Fetches selected entities based on a filter definition.
28
28
  * @param {any} definition - The filter definition object
29
29
  * @param {AbortSignal} [signal] - Optional cancellation signal
30
30
  * @returns {Promise<ApiSuccessResponseDto<TDto>[], ReadMetadataDto>>} The result of the read operation with metadata
31
31
  */
32
- readSelectedAsync(definition: ReadSelectedDefinitionDto<TDto>, signal?: AbortSignal): Promise<ApiResponseDto<TDto[], ReadMetadataDto>>;
32
+ readSelected(definition: ReadSelectedDefinitionDto<TDto>, signal?: AbortSignal): Promise<ApiResponseDto<TDto[], ReadMetadataDto>>;
33
33
  /**
34
34
  * Fetches a single entity by its ID.
35
35
  * @param {TKey} id - The ID of the entity
36
36
  * @param {AbortSignal} [signal] - Optional cancellation signal
37
37
  * @returns {Promise<ApiSuccessResponseDto<TDto, EmptyMetadataDto>>} The requested entity
38
38
  */
39
- readSingleAsync(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<TDto, EmptyMetadataDto>>;
39
+ readSingle(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<TDto, EmptyMetadataDto>>;
40
40
  }
@@ -1,5 +1,5 @@
1
1
  import { apiInitializationService } from '../ApiInitializationService';
2
- import { fail } from '../../../utils/result';
2
+ import { fail, ok } from '../../../utils/result';
3
3
  import { ErrorCode } from '../../../data';
4
4
  /**
5
5
  * Base client for read-only API controllers
@@ -30,14 +30,14 @@ export class ApiReadControllerClient {
30
30
  * @param {AbortSignal} [signal] - Optional cancellation signal
31
31
  * @returns {Promise<ApiSuccessResponseDto<TDto>[], ReadMetadataDto>>} List of all entities with metadata
32
32
  */
33
- async readAllAsync(signal) {
33
+ async readAll(signal) {
34
34
  try {
35
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/ReadAll`);
36
- const res = await this.httpService.requestAsync(url, { method: 'GET', credentials: 'include', signal });
35
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/ReadAll`);
36
+ const res = await this.httpService.request(url, { method: 'GET', credentials: 'include', signal });
37
37
  if (!res.ok) {
38
38
  return fail(await res.json());
39
39
  }
40
- return (await res.json());
40
+ return ok(await res.json());
41
41
  }
42
42
  catch (err) {
43
43
  console.error(err);
@@ -56,10 +56,10 @@ export class ApiReadControllerClient {
56
56
  * @param {AbortSignal} [signal] - Optional cancellation signal
57
57
  * @returns {Promise<ApiSuccessResponseDto<TDto>[], ReadMetadataDto>>} The result of the read operation with metadata
58
58
  */
59
- async readSelectedAsync(definition, signal) {
59
+ async readSelected(definition, signal) {
60
60
  try {
61
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/ReadSelected`);
62
- const res = await this.httpService.requestAsync(url, {
61
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/ReadSelected`);
62
+ const res = await this.httpService.request(url, {
63
63
  method: 'POST',
64
64
  headers: { 'Content-Type': 'application/json' },
65
65
  body: JSON.stringify(definition),
@@ -67,9 +67,9 @@ export class ApiReadControllerClient {
67
67
  signal,
68
68
  });
69
69
  if (!res.ok) {
70
- return (await res.json());
70
+ return fail(await res.json());
71
71
  }
72
- return (await res.json());
72
+ return ok(await res.json());
73
73
  }
74
74
  catch (err) {
75
75
  console.error(err);
@@ -88,14 +88,14 @@ export class ApiReadControllerClient {
88
88
  * @param {AbortSignal} [signal] - Optional cancellation signal
89
89
  * @returns {Promise<ApiSuccessResponseDto<TDto, EmptyMetadataDto>>} The requested entity
90
90
  */
91
- async readSingleAsync(id, signal) {
91
+ async readSingle(id, signal) {
92
92
  try {
93
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/ReadSingle?id=${encodeURIComponent(String(id))}`);
94
- const res = await this.httpService.requestAsync(url, { method: 'GET', credentials: 'include', signal });
93
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/ReadSingle?id=${encodeURIComponent(String(id))}`);
94
+ const res = await this.httpService.request(url, { method: 'GET', credentials: 'include', signal });
95
95
  if (!res.ok) {
96
- return (await res.json());
96
+ return fail(await res.json());
97
97
  }
98
- return (await res.json());
98
+ return ok(await res.json());
99
99
  }
100
100
  catch (err) {
101
101
  console.error(err);
@@ -3,5 +3,5 @@ import { HttpRequestConfig, HttpResponse, IHttpService } from '../../../services
3
3
  * Default HTTP service implementation using the Fetch API
4
4
  */
5
5
  export declare class FetchHttpService implements IHttpService {
6
- requestAsync<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>>;
6
+ request<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>>;
7
7
  }
@@ -4,7 +4,7 @@
4
4
  * Default HTTP service implementation using the Fetch API
5
5
  */
6
6
  export class FetchHttpService {
7
- async requestAsync(url, config) {
7
+ async request(url, config) {
8
8
  const response = await fetch(url, {
9
9
  method: config?.method || 'GET',
10
10
  headers: config?.headers,
@@ -9,5 +9,5 @@ export interface IHttpService {
9
9
  * @param config - Request configuration
10
10
  * @returns Promise that resolves to the HTTP response
11
11
  */
12
- requestAsync<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>>;
12
+ request<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>>;
13
13
  }
@@ -22,20 +22,20 @@ export declare class UserManagementControllerClient<TKey, TUser extends UserDto<
22
22
  * @param {AbortSignal} [signal] - Optional cancellation signal
23
23
  * @returns List of role names assigned to the user
24
24
  */
25
- getUserRolesAsync(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<string[], ReadMetadataDto>>;
25
+ getUserRoles(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<string[], ReadMetadataDto>>;
26
26
  /**
27
27
  * Gets the claims assigned to a user
28
28
  * @param id - The ID of the user
29
29
  * @param {AbortSignal} [signal] - Optional cancellation signal
30
30
  * @returns List of claims assigned to the user
31
31
  */
32
- getUserClaimsAsync(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<ClaimDto[], ReadMetadataDto>>;
32
+ getUserClaims(id: TKey, signal?: AbortSignal): Promise<ApiResponseDto<ClaimDto[], ReadMetadataDto>>;
33
33
  /**
34
34
  * Gets all available roles in the system
35
35
  * @param {AbortSignal} [signal] - Optional cancellation signal
36
36
  * @returns List of all role names
37
37
  */
38
- getAllRolesAsync(signal?: AbortSignal): Promise<ApiResponseDto<string[], ReadMetadataDto>>;
38
+ getAllRoles(signal?: AbortSignal): Promise<ApiResponseDto<string[], ReadMetadataDto>>;
39
39
  /**
40
40
  * Changes the password for a user
41
41
  * @param id - The ID of the user
@@ -43,5 +43,5 @@ export declare class UserManagementControllerClient<TKey, TUser extends UserDto<
43
43
  * @param {AbortSignal} [signal] - Optional cancellation signal
44
44
  * @returns Promise that resolves when the password is changed successfully
45
45
  */
46
- changePasswordAsync(id: TKey, newPassword: string, signal?: AbortSignal): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
46
+ changePassword(id: TKey, newPassword: string, signal?: AbortSignal): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
47
47
  }
@@ -25,10 +25,10 @@ export class UserManagementControllerClient extends ApiCrudControllerClient {
25
25
  * @param {AbortSignal} [signal] - Optional cancellation signal
26
26
  * @returns List of role names assigned to the user
27
27
  */
28
- async getUserRolesAsync(id, signal) {
28
+ async getUserRoles(id, signal) {
29
29
  try {
30
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, 'GetUserRoles', encodeURIComponent(String(id)));
31
- const res = await this.httpService.requestAsync(url, {
30
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, 'GetUserRoles', encodeURIComponent(String(id)));
31
+ const res = await this.httpService.request(url, {
32
32
  method: 'GET',
33
33
  credentials: 'include',
34
34
  signal,
@@ -55,10 +55,10 @@ export class UserManagementControllerClient extends ApiCrudControllerClient {
55
55
  * @param {AbortSignal} [signal] - Optional cancellation signal
56
56
  * @returns List of claims assigned to the user
57
57
  */
58
- async getUserClaimsAsync(id, signal) {
58
+ async getUserClaims(id, signal) {
59
59
  try {
60
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, 'GetUserClaims', encodeURIComponent(String(id)));
61
- const res = await this.httpService.requestAsync(url, {
60
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, 'GetUserClaims', encodeURIComponent(String(id)));
61
+ const res = await this.httpService.request(url, {
62
62
  method: 'GET',
63
63
  credentials: 'include',
64
64
  signal,
@@ -84,10 +84,10 @@ export class UserManagementControllerClient extends ApiCrudControllerClient {
84
84
  * @param {AbortSignal} [signal] - Optional cancellation signal
85
85
  * @returns List of all role names
86
86
  */
87
- async getAllRolesAsync(signal) {
87
+ async getAllRoles(signal) {
88
88
  try {
89
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, 'GetAllRoles');
90
- const res = await this.httpService.requestAsync(url, {
89
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, 'GetAllRoles');
90
+ const res = await this.httpService.request(url, {
91
91
  method: 'GET',
92
92
  credentials: 'include',
93
93
  signal,
@@ -115,10 +115,10 @@ export class UserManagementControllerClient extends ApiCrudControllerClient {
115
115
  * @param {AbortSignal} [signal] - Optional cancellation signal
116
116
  * @returns Promise that resolves when the password is changed successfully
117
117
  */
118
- async changePasswordAsync(id, newPassword, signal) {
118
+ async changePassword(id, newPassword, signal) {
119
119
  try {
120
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, 'ChangePassword', encodeURIComponent(String(id)));
121
- const res = await this.httpService.requestAsync(url, {
120
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, 'ChangePassword', encodeURIComponent(String(id)));
121
+ const res = await this.httpService.request(url, {
122
122
  method: 'POST',
123
123
  headers: {
124
124
  'Content-Type': 'application/json',
@@ -1,3 +1,4 @@
1
+ import { IHttpService } from '../../..';
1
2
  import { ApiResponseDto, EmptyMetadataDto, UserDto, UserInfoDto } from '../../../../data';
2
3
  export { UserDto, UserInfoDto };
3
4
  /**
@@ -8,6 +9,18 @@ export { UserDto, UserInfoDto };
8
9
  * @class AuthService
9
10
  */
10
11
  export declare class AuthService<TKey, TUser extends UserDto<TKey> = UserDto<TKey>> {
12
+ protected readonly baseControllerPath: string;
13
+ /**
14
+ * Constructor
15
+ * @param baseControllerPath Base path to Auth API controller
16
+ * @param httpService HTTP service implementation to use for requests
17
+ */
18
+ constructor(baseControllerPath?: string, httpService?: IHttpService);
19
+ private readonly _httpService?;
20
+ /**
21
+ * Gets globally selected HTTP service
22
+ */
23
+ protected get httpService(): IHttpService;
11
24
  /**
12
25
  * Holds currently authenticated user's details
13
26
  */
@@ -18,14 +31,14 @@ export declare class AuthService<TKey, TUser extends UserDto<TKey> = UserDto<TKe
18
31
  * @async
19
32
  * @return {Promise<void>} Resolves when initialization is complete.
20
33
  */
21
- initializeAsync(): Promise<void>;
34
+ initialize(): Promise<void>;
22
35
  /**
23
36
  * Checks if user is authenticated and retrieves the current user's details if they are
24
37
  *
25
38
  * @return {Promise<UserInfoDto<TKey, TUser>>} Currently logged in user's details
26
39
  * @throws {Error} Error if fetch fails
27
40
  */
28
- whoAmIAsync(): Promise<ApiResponseDto<UserInfoDto<TKey, TUser>, EmptyMetadataDto>>;
41
+ whoAmI(): Promise<ApiResponseDto<UserInfoDto<TKey, TUser>, EmptyMetadataDto>>;
29
42
  /**
30
43
  * Authenticates a user using their username and password, and retrieves user's details
31
44
  *
@@ -34,17 +47,21 @@ export declare class AuthService<TKey, TUser extends UserDto<TKey> = UserDto<TKe
34
47
  * @return {Promise<TUser>} Currently logged in user's details
35
48
  * @throws {Error} Error if fetch fails
36
49
  */
37
- signinAsync(username: string, password: string): Promise<ApiResponseDto<TUser, EmptyMetadataDto>>;
50
+ signIn(username: string, password: string): Promise<ApiResponseDto<TUser, EmptyMetadataDto>>;
38
51
  /**
39
52
  * Deauthenticates current user
40
53
  *
41
54
  * @return {Promise<void>}
42
55
  * @throws {Error} Error if fetch fails
43
56
  */
44
- signoutAsync(): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
57
+ signOut(): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
45
58
  /**
46
59
  * If any API response returns an "Unauthenticated" response, call this method
47
60
  * to update local authentication state to unauthenticated
48
61
  */
49
- _handleNoAuthApiResponse(): void;
62
+ handleNoAuthApiResponse(): void;
63
+ /**
64
+ * True if a user is currently loaded (post whoAmI/signIn)
65
+ */
66
+ isAuthenticated(): boolean;
50
67
  }
@@ -9,6 +9,25 @@ import { fail, ok } from '../../../../utils';
9
9
  * @class AuthService
10
10
  */
11
11
  export class AuthService {
12
+ baseControllerPath;
13
+ /**
14
+ * Constructor
15
+ * @param baseControllerPath Base path to Auth API controller
16
+ * @param httpService HTTP service implementation to use for requests
17
+ */
18
+ constructor(baseControllerPath = 'Auth', httpService) {
19
+ this.baseControllerPath = baseControllerPath;
20
+ this._httpService = httpService;
21
+ }
22
+ // #region HTTP service
23
+ _httpService = undefined;
24
+ /**
25
+ * Gets globally selected HTTP service
26
+ */
27
+ get httpService() {
28
+ return this._httpService || apiInitializationService._getHttpService();
29
+ }
30
+ // #endregion
12
31
  /**
13
32
  * Holds currently authenticated user's details
14
33
  */
@@ -19,9 +38,9 @@ export class AuthService {
19
38
  * @async
20
39
  * @return {Promise<void>} Resolves when initialization is complete.
21
40
  */
22
- async initializeAsync() {
41
+ async initialize() {
23
42
  // Check user's authentication status
24
- await this.whoAmIAsync();
43
+ await this.whoAmI();
25
44
  }
26
45
  /**
27
46
  * Checks if user is authenticated and retrieves the current user's details if they are
@@ -29,21 +48,20 @@ export class AuthService {
29
48
  * @return {Promise<UserInfoDto<TKey, TUser>>} Currently logged in user's details
30
49
  * @throws {Error} Error if fetch fails
31
50
  */
32
- async whoAmIAsync() {
51
+ async whoAmI() {
33
52
  try {
34
- const url = await apiInitializationService.getApiUrlAsync('/auth/whoami');
35
- const res = await fetch(url, { credentials: 'include' });
53
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, '/whoami');
54
+ const res = await this.httpService.request(url, { credentials: 'include' });
36
55
  if (!res.ok) {
37
56
  this.user = undefined;
38
57
  return fail(await res.json());
39
58
  }
40
- const response = (await res.json());
41
- if (response.ok) {
42
- this.user = response.result.user;
43
- }
44
- else {
59
+ const response = ok(await res.json());
60
+ if (!response.ok) {
45
61
  this.user = undefined;
62
+ return response;
46
63
  }
64
+ this.user = response.result.user;
47
65
  return response;
48
66
  }
49
67
  catch (err) {
@@ -65,10 +83,10 @@ export class AuthService {
65
83
  * @return {Promise<TUser>} Currently logged in user's details
66
84
  * @throws {Error} Error if fetch fails
67
85
  */
68
- async signinAsync(username, password) {
86
+ async signIn(username, password) {
69
87
  try {
70
- const url = await apiInitializationService.getApiUrlAsync('/auth/signin');
71
- const res = await fetch(url, {
88
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, '/signin');
89
+ const res = await this.httpService.request(url, {
72
90
  method: 'POST',
73
91
  headers: { 'Content-Type': 'application/json' },
74
92
  body: JSON.stringify({ username, password }),
@@ -78,13 +96,13 @@ export class AuthService {
78
96
  return fail(await res.json());
79
97
  }
80
98
  // TODO: Handle tokens if not using cookies
81
- const whoamIRes = await this.whoAmIAsync();
82
- if (!whoamIRes.ok) {
83
- return whoamIRes;
99
+ const whoAmIRes = await this.whoAmI();
100
+ if (!whoAmIRes.ok) {
101
+ return whoAmIRes;
84
102
  }
85
103
  return ok({
86
- result: whoamIRes.result.user,
87
- metadata: whoamIRes.metadata,
104
+ result: whoAmIRes.result.user,
105
+ metadata: whoAmIRes.metadata,
88
106
  });
89
107
  }
90
108
  catch (err) {
@@ -104,10 +122,10 @@ export class AuthService {
104
122
  * @return {Promise<void>}
105
123
  * @throws {Error} Error if fetch fails
106
124
  */
107
- async signoutAsync() {
125
+ async signOut() {
108
126
  try {
109
- const url = await apiInitializationService.getApiUrlAsync('/auth/signout');
110
- const res = await fetch(url, { credentials: 'include' });
127
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, '/signout');
128
+ const res = await this.httpService.request(url, { credentials: 'include' });
111
129
  if (!res.ok) {
112
130
  return fail(await res.json());
113
131
  }
@@ -129,7 +147,14 @@ export class AuthService {
129
147
  * If any API response returns an "Unauthenticated" response, call this method
130
148
  * to update local authentication state to unauthenticated
131
149
  */
132
- _handleNoAuthApiResponse() {
150
+ handleNoAuthApiResponse() {
133
151
  this.user = undefined;
134
152
  }
153
+ // #region Client-side authorization helpers
154
+ /**
155
+ * True if a user is currently loaded (post whoAmI/signIn)
156
+ */
157
+ isAuthenticated() {
158
+ return !!this.user;
159
+ }
135
160
  }
@@ -1,3 +1,4 @@
1
+ import { IHttpService } from '../../..';
1
2
  import { ApiResponseDto, EmptyMetadataDto, ReadMetadataDto, RoleDto } from '../../../../data';
2
3
  /**
3
4
  * Client for authorization management operations
@@ -10,32 +11,38 @@ export declare class AuthorizationManagementControllerClient {
10
11
  /**
11
12
  * Constructor
12
13
  * @param baseControllerPath Base path to API controller
14
+ * @param httpService HTTP service implementation to use for requests
13
15
  */
14
- constructor(baseControllerPath?: string);
16
+ constructor(baseControllerPath?: string, httpService?: IHttpService);
17
+ private readonly _httpService?;
18
+ /**
19
+ * Gets globally selected HTTP service
20
+ */
21
+ protected get httpService(): IHttpService;
15
22
  /**
16
23
  * Gets all roles in the system
17
24
  * @param includeClaims - Whether to include claims in the response
18
25
  * @param signal - Optional cancellation signal
19
26
  * @returns List of all roles
20
27
  */
21
- getAllRolesAsync(includeClaims: boolean, signal?: AbortSignal): Promise<ApiResponseDto<RoleDto[], ReadMetadataDto>>;
28
+ getAllRoles(includeClaims: boolean, signal?: AbortSignal): Promise<ApiResponseDto<RoleDto[], ReadMetadataDto>>;
22
29
  /**
23
30
  * Gets a role with its claims
24
31
  * @param roleName - The name of the role
25
32
  * @param signal
26
33
  * @returns The role with its claims
27
34
  */
28
- getRoleWithClaimsAsync(roleName: string, signal?: AbortSignal): Promise<ApiResponseDto<RoleDto, ReadMetadataDto>>;
35
+ getRoleWithClaims(roleName: string, signal?: AbortSignal): Promise<ApiResponseDto<RoleDto, ReadMetadataDto>>;
29
36
  /**
30
37
  * Creates or updates a role
31
38
  * @param roleDto - The role to create or update
32
39
  * @returns The created or updated role
33
40
  */
34
- upsertRoleAsync(roleDto: RoleDto): Promise<ApiResponseDto<RoleDto, EmptyMetadataDto>>;
41
+ upsertRole(roleDto: RoleDto): Promise<ApiResponseDto<RoleDto, EmptyMetadataDto>>;
35
42
  /**
36
43
  * Deletes a role
37
44
  * @param roleName - The name of the role to delete
38
45
  * @returns Promise that resolves when the role is deleted successfully
39
46
  */
40
- deleteRoleAsync(roleName: string): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
47
+ deleteRole(roleName: string): Promise<ApiResponseDto<undefined, EmptyMetadataDto>>;
41
48
  }
@@ -12,20 +12,31 @@ export class AuthorizationManagementControllerClient {
12
12
  /**
13
13
  * Constructor
14
14
  * @param baseControllerPath Base path to API controller
15
+ * @param httpService HTTP service implementation to use for requests
15
16
  */
16
- constructor(baseControllerPath = 'AuthorizationManagement') {
17
+ constructor(baseControllerPath = 'AuthorizationManagement', httpService) {
17
18
  this.baseControllerPath = baseControllerPath;
19
+ this._httpService = httpService;
18
20
  }
21
+ // #region HTTP service
22
+ _httpService = undefined;
23
+ /**
24
+ * Gets globally selected HTTP service
25
+ */
26
+ get httpService() {
27
+ return this._httpService || apiInitializationService._getHttpService();
28
+ }
29
+ // #endregion
19
30
  /**
20
31
  * Gets all roles in the system
21
32
  * @param includeClaims - Whether to include claims in the response
22
33
  * @param signal - Optional cancellation signal
23
34
  * @returns List of all roles
24
35
  */
25
- async getAllRolesAsync(includeClaims, signal) {
36
+ async getAllRoles(includeClaims, signal) {
26
37
  try {
27
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/GetAllRoles?includeClaims=${includeClaims}`);
28
- const res = await fetch(url, {
38
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/GetAllRoles?includeClaims=${includeClaims}`);
39
+ const res = await this.httpService.request(url, {
29
40
  credentials: 'include',
30
41
  signal,
31
42
  });
@@ -51,10 +62,10 @@ export class AuthorizationManagementControllerClient {
51
62
  * @param signal
52
63
  * @returns The role with its claims
53
64
  */
54
- async getRoleWithClaimsAsync(roleName, signal) {
65
+ async getRoleWithClaims(roleName, signal) {
55
66
  try {
56
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/GetRoleWithClaims/${encodeURIComponent(roleName)}`);
57
- const res = await fetch(url, {
67
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/GetRoleWithClaims/${encodeURIComponent(roleName)}`);
68
+ const res = await this.httpService.request(url, {
58
69
  credentials: 'include',
59
70
  signal,
60
71
  });
@@ -79,10 +90,10 @@ export class AuthorizationManagementControllerClient {
79
90
  * @param roleDto - The role to create or update
80
91
  * @returns The created or updated role
81
92
  */
82
- async upsertRoleAsync(roleDto) {
93
+ async upsertRole(roleDto) {
83
94
  try {
84
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/UpsertRole`);
85
- const res = await fetch(url, {
95
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/UpsertRole`);
96
+ const res = await this.httpService.request(url, {
86
97
  method: 'PUT',
87
98
  headers: {
88
99
  'Content-Type': 'application/json',
@@ -111,10 +122,10 @@ export class AuthorizationManagementControllerClient {
111
122
  * @param roleName - The name of the role to delete
112
123
  * @returns Promise that resolves when the role is deleted successfully
113
124
  */
114
- async deleteRoleAsync(roleName) {
125
+ async deleteRole(roleName) {
115
126
  try {
116
- const url = await apiInitializationService.getApiUrlAsync(this.baseControllerPath, `/DeleteRole/${encodeURIComponent(roleName)}`);
117
- const res = await fetch(url, {
127
+ const url = await apiInitializationService.getApiUrl(this.baseControllerPath, `/DeleteRole/${encodeURIComponent(roleName)}`);
128
+ const res = await this.httpService.request(url, {
118
129
  method: 'DELETE',
119
130
  credentials: 'include',
120
131
  });
@@ -0,0 +1,17 @@
1
+ import { IPolicy } from '../../data';
2
+ /**
3
+ * Checks if a policy is valid for the given parameters.
4
+ *
5
+ * @param policy Policy to check, either an instance or a class.
6
+ * @param params Parameters to pass to the policy check function.
7
+ * @returns A promise that resolves if the policy is valid, rejects otherwise.
8
+ */
9
+ export declare function check<TParams extends unknown[]>(policy: IPolicy<TParams> | (new () => IPolicy<TParams>), ...params: TParams): Promise<boolean>;
10
+ /**
11
+ * Composes multiple policies into a single policy.
12
+ *
13
+ * @param composition Composition method, either 'AND' or 'OR'.
14
+ * @param policies Policies to compose.
15
+ * @returns A new policy that represents the composition of the policies.
16
+ */
17
+ export declare function compose<TParams extends unknown[]>(composition: 'AND' | 'OR', ...policies: IPolicy<TParams>[]): IPolicy<TParams>;
@@ -0,0 +1,45 @@
1
+ import { PolicyBase } from '../../data';
2
+ /**
3
+ * Checks if a policy is valid for the given parameters.
4
+ *
5
+ * @param policy Policy to check, either an instance or a class.
6
+ * @param params Parameters to pass to the policy check function.
7
+ * @returns A promise that resolves if the policy is valid, rejects otherwise.
8
+ */
9
+ export async function check(policy, ...params) {
10
+ // If passed an instance of a policy
11
+ if (policy instanceof PolicyBase) {
12
+ return policy.check(...params);
13
+ }
14
+ // If passed a policy class
15
+ else {
16
+ return new policy().check(...params);
17
+ }
18
+ }
19
+ /**
20
+ * Composes multiple policies into a single policy.
21
+ *
22
+ * @param composition Composition method, either 'AND' or 'OR'.
23
+ * @param policies Policies to compose.
24
+ * @returns A new policy that represents the composition of the policies.
25
+ */
26
+ export function compose(composition, ...policies) {
27
+ return {
28
+ check: async (...params) => {
29
+ // Check all policies
30
+ if (composition === 'AND') {
31
+ const results = await Promise.allSettled(policies.map(policy => policy.check(...params)));
32
+ return results.every(result => result);
33
+ }
34
+ // Check any policies, exit as soon as any are true
35
+ else if (composition === 'OR') {
36
+ const results = await Promise.allSettled(policies.map(policy => policy.check(...params)));
37
+ return results.some(result => result);
38
+ }
39
+ // Invalid composition
40
+ else {
41
+ throw new Error('Invalid composition');
42
+ }
43
+ },
44
+ };
45
+ }