@chetodb/business-central 0.0.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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.es.md +143 -0
  3. package/README.md +143 -0
  4. package/dist/auth/auth.types.d.ts +49 -0
  5. package/dist/auth/auth.types.d.ts.map +1 -0
  6. package/dist/auth/auth.types.js +2 -0
  7. package/dist/auth/auth.types.js.map +1 -0
  8. package/dist/auth/index.d.ts +3 -0
  9. package/dist/auth/index.d.ts.map +1 -0
  10. package/dist/auth/index.js +2 -0
  11. package/dist/auth/index.js.map +1 -0
  12. package/dist/auth/oauth2.provider.d.ts +47 -0
  13. package/dist/auth/oauth2.provider.d.ts.map +1 -0
  14. package/dist/auth/oauth2.provider.js +131 -0
  15. package/dist/auth/oauth2.provider.js.map +1 -0
  16. package/dist/auth/oauth2.provider.spec.d.ts +2 -0
  17. package/dist/auth/oauth2.provider.spec.d.ts.map +1 -0
  18. package/dist/auth/oauth2.provider.spec.js +150 -0
  19. package/dist/auth/oauth2.provider.spec.js.map +1 -0
  20. package/dist/client/business-central.client.d.ts +97 -0
  21. package/dist/client/business-central.client.d.ts.map +1 -0
  22. package/dist/client/business-central.client.js +155 -0
  23. package/dist/client/business-central.client.js.map +1 -0
  24. package/dist/client/business-central.client.spec.d.ts +2 -0
  25. package/dist/client/business-central.client.spec.d.ts.map +1 -0
  26. package/dist/client/business-central.client.spec.js +151 -0
  27. package/dist/client/business-central.client.spec.js.map +1 -0
  28. package/dist/client/index.d.ts +2 -0
  29. package/dist/client/index.d.ts.map +1 -0
  30. package/dist/client/index.js +2 -0
  31. package/dist/client/index.js.map +1 -0
  32. package/dist/client/pagination.d.ts +15 -0
  33. package/dist/client/pagination.d.ts.map +1 -0
  34. package/dist/client/pagination.js +29 -0
  35. package/dist/client/pagination.js.map +1 -0
  36. package/dist/client/pagination.spec.d.ts +2 -0
  37. package/dist/client/pagination.spec.d.ts.map +1 -0
  38. package/dist/client/pagination.spec.js +42 -0
  39. package/dist/client/pagination.spec.js.map +1 -0
  40. package/dist/http/http.client.d.ts +44 -0
  41. package/dist/http/http.client.d.ts.map +1 -0
  42. package/dist/http/http.client.js +155 -0
  43. package/dist/http/http.client.js.map +1 -0
  44. package/dist/http/http.client.spec.d.ts +2 -0
  45. package/dist/http/http.client.spec.d.ts.map +1 -0
  46. package/dist/http/http.client.spec.js +235 -0
  47. package/dist/http/http.client.spec.js.map +1 -0
  48. package/dist/http/http.types.d.ts +12 -0
  49. package/dist/http/http.types.d.ts.map +1 -0
  50. package/dist/http/http.types.js +2 -0
  51. package/dist/http/http.types.js.map +1 -0
  52. package/dist/http/index.d.ts +5 -0
  53. package/dist/http/index.d.ts.map +1 -0
  54. package/dist/http/index.js +3 -0
  55. package/dist/http/index.js.map +1 -0
  56. package/dist/http/url-builder.d.ts +47 -0
  57. package/dist/http/url-builder.d.ts.map +1 -0
  58. package/dist/http/url-builder.js +58 -0
  59. package/dist/http/url-builder.js.map +1 -0
  60. package/dist/http/url-builder.spec.d.ts +2 -0
  61. package/dist/http/url-builder.spec.d.ts.map +1 -0
  62. package/dist/http/url-builder.spec.js +43 -0
  63. package/dist/http/url-builder.spec.js.map +1 -0
  64. package/dist/index.d.ts +12 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +12 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/odata/filter-builder.d.ts +67 -0
  69. package/dist/odata/filter-builder.d.ts.map +1 -0
  70. package/dist/odata/filter-builder.js +127 -0
  71. package/dist/odata/filter-builder.js.map +1 -0
  72. package/dist/odata/filter-builder.spec.d.ts +2 -0
  73. package/dist/odata/filter-builder.spec.d.ts.map +1 -0
  74. package/dist/odata/filter-builder.spec.js +68 -0
  75. package/dist/odata/filter-builder.spec.js.map +1 -0
  76. package/dist/odata/index.d.ts +2 -0
  77. package/dist/odata/index.d.ts.map +1 -0
  78. package/dist/odata/index.js +2 -0
  79. package/dist/odata/index.js.map +1 -0
  80. package/dist/types/azure-config.types.d.ts +15 -0
  81. package/dist/types/azure-config.types.d.ts.map +1 -0
  82. package/dist/types/azure-config.types.js +2 -0
  83. package/dist/types/azure-config.types.js.map +1 -0
  84. package/dist/types/azure-key.types.d.ts +13 -0
  85. package/dist/types/azure-key.types.d.ts.map +1 -0
  86. package/dist/types/azure-key.types.js +2 -0
  87. package/dist/types/azure-key.types.js.map +1 -0
  88. package/dist/types/client.types.d.ts +52 -0
  89. package/dist/types/client.types.d.ts.map +1 -0
  90. package/dist/types/client.types.js +2 -0
  91. package/dist/types/client.types.js.map +1 -0
  92. package/dist/types/defaults.d.ts +21 -0
  93. package/dist/types/defaults.d.ts.map +1 -0
  94. package/dist/types/defaults.js +43 -0
  95. package/dist/types/defaults.js.map +1 -0
  96. package/dist/types/defaults.spec.d.ts +2 -0
  97. package/dist/types/defaults.spec.d.ts.map +1 -0
  98. package/dist/types/defaults.spec.js +37 -0
  99. package/dist/types/defaults.spec.js.map +1 -0
  100. package/dist/types/index.d.ts +7 -0
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +2 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/types/options.types.d.ts +46 -0
  105. package/dist/types/options.types.d.ts.map +1 -0
  106. package/dist/types/options.types.js +2 -0
  107. package/dist/types/options.types.js.map +1 -0
  108. package/dist/types/response.types.d.ts +10 -0
  109. package/dist/types/response.types.d.ts.map +1 -0
  110. package/dist/types/response.types.js +2 -0
  111. package/dist/types/response.types.js.map +1 -0
  112. package/dist/utils/config.utils.d.ts +5 -0
  113. package/dist/utils/config.utils.d.ts.map +1 -0
  114. package/dist/utils/config.utils.js +10 -0
  115. package/dist/utils/config.utils.js.map +1 -0
  116. package/dist/utils/config.utils.spec.d.ts +2 -0
  117. package/dist/utils/config.utils.spec.d.ts.map +1 -0
  118. package/dist/utils/config.utils.spec.js +16 -0
  119. package/dist/utils/config.utils.spec.js.map +1 -0
  120. package/dist/utils/date.utils.d.ts +13 -0
  121. package/dist/utils/date.utils.d.ts.map +1 -0
  122. package/dist/utils/date.utils.js +25 -0
  123. package/dist/utils/date.utils.js.map +1 -0
  124. package/dist/utils/date.utils.spec.d.ts +2 -0
  125. package/dist/utils/date.utils.spec.d.ts.map +1 -0
  126. package/dist/utils/date.utils.spec.js +20 -0
  127. package/dist/utils/date.utils.spec.js.map +1 -0
  128. package/dist/utils/http.utils.d.ts +7 -0
  129. package/dist/utils/http.utils.d.ts.map +1 -0
  130. package/dist/utils/http.utils.js +16 -0
  131. package/dist/utils/http.utils.js.map +1 -0
  132. package/dist/utils/http.utils.spec.d.ts +2 -0
  133. package/dist/utils/http.utils.spec.d.ts.map +1 -0
  134. package/dist/utils/http.utils.spec.js +17 -0
  135. package/dist/utils/http.utils.spec.js.map +1 -0
  136. package/dist/utils/index.d.ts +5 -0
  137. package/dist/utils/index.d.ts.map +1 -0
  138. package/dist/utils/index.js +5 -0
  139. package/dist/utils/index.js.map +1 -0
  140. package/dist/utils/odata-query.utils.d.ts +19 -0
  141. package/dist/utils/odata-query.utils.d.ts.map +1 -0
  142. package/dist/utils/odata-query.utils.js +60 -0
  143. package/dist/utils/odata-query.utils.js.map +1 -0
  144. package/dist/utils/odata-query.utils.spec.d.ts +2 -0
  145. package/dist/utils/odata-query.utils.spec.d.ts.map +1 -0
  146. package/dist/utils/odata-query.utils.spec.js +51 -0
  147. package/dist/utils/odata-query.utils.spec.js.map +1 -0
  148. package/package.json +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Cheto (ChetoDB)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.es.md ADDED
@@ -0,0 +1,143 @@
1
+ # Business Central SDK Core (Español)
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/@chetodb/business-central.svg)](https://www.npmjs.com/package/@chetodb/business-central)
4
+ [![NPM Downloads](https://img.shields.io/npm/dm/@chetodb/business-central.svg)](https://www.npmjs.com/package/@chetodb/business-central)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ **SDK de TypeScript moderno, resiliente y con tipos seguros para Microsoft Dynamics 365 Business Central.**
8
+
9
+ [English (README.md)](./README.md) | [**Volver al Repositorio Principal**](../../README.es.md)
10
+
11
+ ---
12
+
13
+ Este es el núcleo (Core SDK) del proyecto, diseñado para ser ligero, independiente de framework y extremadamente robusto en entornos de producción.
14
+
15
+ ### ✨ Características clave
16
+
17
+ - 🔄 **Resiliencia Nativa**: Reintentos automáticos con *backoff exponencial* para errores transitorios.
18
+ - 🔑 **Rotación de Claves**: Soporta múltiples `azureKeys` para gestionar fallos de credenciales de forma automática.
19
+ - 🏗️ **Fluent OData**: Constructor de filtros tipado (`BcFilter`).
20
+ - 📦 **Pure ESM**: Construido aprovechando los estándares modernos de módulos de JavaScript.
21
+
22
+ ### 🚀 Instalación
23
+
24
+ ```bash
25
+ pnpm add @chetodb/business-central
26
+ ```
27
+
28
+ ### 🛠️ Uso básico
29
+
30
+ ```ts
31
+ import { BcFilter, BusinessCentralClient } from '@chetodb/business-central';
32
+
33
+ const client = new BusinessCentralClient({
34
+ tenantId: 'your-tenant-id',
35
+ companyName: 'CRONUS',
36
+ azureKeys: [
37
+ { name: 'main', clientId: '...', clientSecret: '...' }
38
+ ],
39
+ });
40
+
41
+ // Consulta tipada con filtros complejos
42
+ interface Customer {
43
+ id: string;
44
+ displayName: string;
45
+ }
46
+
47
+ const customers = await client.get<Customer>('customers', {
48
+ filter: BcFilter.where('balance').gt(0),
49
+ select: ['id', 'displayName'],
50
+ top: 10,
51
+ });
52
+ ```
53
+
54
+ ### 📖 Referencia de API y Ejemplos
55
+
56
+ El `BusinessCentralClient` proporciona un conjunto completo de métodos para interactuar con la API de BC.
57
+
58
+ #### `get<T>(endpoint, options)`
59
+
60
+ Recupera registros con paginación automática y soporte OData.
61
+
62
+ ```ts
63
+ // Ejemplo: Usando BcFilter y opciones estándar de OData
64
+ const filter = new BcFilter().eq('Item_No', 'item-001');
65
+ const items = await client.get('item', {
66
+ filter: filter,
67
+ top: 5
68
+ });
69
+ ```
70
+
71
+ #### `post<T>(endpoint, body)`
72
+
73
+ Crea un nuevo registro.
74
+
75
+ ```ts
76
+ const newItem = {
77
+ Item_No: 'item-001',
78
+ Description: 'SDK-TEST'
79
+ };
80
+ const created = await client.post('item', newItem);
81
+ ```
82
+
83
+ #### `patch<T>(endpoint, criteria, data)`
84
+
85
+ Actualiza parcialmente un registro existente. Gestiona automáticamente el predicado de clave OData e incluye `If-Match: *` por defecto.
86
+
87
+ ```ts
88
+ const criteria = { Item_No: 'item-001', Description: 'SDK-TEST' };
89
+ const newData = { Description: 'Actualizado mediante SDK' };
90
+
91
+ await client.patch('item', criteria, newData);
92
+ ```
93
+
94
+ #### `put<T>(endpoint, criteria, data)`
95
+
96
+ Reemplaza un registro existente por completo. También incluye `If-Match: *` por defecto.
97
+
98
+ ```ts
99
+ const criteria = { Item_No: 'item-001', Description: 'SDK-TEST' };
100
+ const fullData = {
101
+ Item_No: 'item-001',
102
+ Description: 'Reemplazo total mediante SDK'
103
+ };
104
+
105
+ await client.put('item', criteria, fullData);
106
+ ```
107
+
108
+ #### `delete(endpoint, criteria)`
109
+
110
+ Elimina un registro basado en criterios de clave.
111
+
112
+ ```ts
113
+ const criteria = { Item_No: 'item-001', Description: 'SDK-TEST' };
114
+ await client.delete('item', criteria);
115
+ ```
116
+
117
+ #### `executeAction<T>(endpoint, data)`
118
+
119
+ Ejecuta una acción OData **no vinculada** (unbound). Nota: En Business Central, las acciones no vinculadas usan una ruta específica (`ODataV4/NombreAccion`).
120
+
121
+ ```ts
122
+ await client.executeAction('MiAccionPersonalizada', {
123
+ documentNo: '123'
124
+ });
125
+ ```
126
+
127
+ ### ⚙️ Configuración avanzada
128
+
129
+ ```ts
130
+ const client = new BusinessCentralClient({
131
+ requestOptions: {
132
+ maxRetries: 5,
133
+ timeout: 30000,
134
+ },
135
+ debugOptions: {
136
+ duration: true,
137
+ }
138
+ });
139
+ ```
140
+
141
+ ---
142
+
143
+ - **Licencia**: [MIT](./LICENSE) © 2026 David Cheto (ChetoDB)
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Business Central SDK Core
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/@chetodb/business-central.svg)](https://www.npmjs.com/package/@chetodb/business-central)
4
+ [![NPM Downloads](https://img.shields.io/npm/dm/@chetodb/business-central.svg)](https://www.npmjs.com/package/@chetodb/business-central)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ **Modern, resilient, and type-safe TypeScript SDK for Microsoft Dynamics 365 Business Central.**
8
+
9
+ [Español (README.es.md)](./README.es.md) | [**Return to Monorepo Root**](../../README.md)
10
+
11
+ ---
12
+
13
+ This is the Core SDK of the project, designed to be lightweight, framework-agnostic, and extremely robust for production environments.
14
+
15
+ ### ✨ Key Features
16
+
17
+ - 🔄 **Native Resilience**: Automatic retries with *exponential backoff* for transient errors.
18
+ - 🔑 **Key Rotation**: Supports multiple `azureKeys` to handle failed credentials.
19
+ - 🏗️ **Fluent OData**: Type-safe filter builder (`BcFilter`).
20
+ - 📦 **Pure ESM**: Built leveraging modern JavaScript module standards.
21
+
22
+ ### 🚀 Installation
23
+
24
+ ```bash
25
+ pnpm add @chetodb/business-central
26
+ ```
27
+
28
+ ### 🛠️ Basic Usage
29
+
30
+ ```ts
31
+ import { BcFilter, BusinessCentralClient } from '@chetodb/business-central';
32
+
33
+ const client = new BusinessCentralClient({
34
+ tenantId: 'your-tenant-id',
35
+ companyName: 'CRONUS',
36
+ azureKeys: [
37
+ { name: 'main', clientId: '...', clientSecret: '...' }
38
+ ],
39
+ });
40
+
41
+ // Type-safe query with complex filters
42
+ interface Customer {
43
+ id: string;
44
+ displayName: string;
45
+ }
46
+
47
+ const customers = await client.get<Customer>('customers', {
48
+ filter: BcFilter.where('balance').gt(0),
49
+ select: ['id', 'displayName'],
50
+ top: 10,
51
+ });
52
+ ```
53
+
54
+ ### 📖 API Reference & Examples
55
+
56
+ The `BusinessCentralClient` provides a full set of methods for interacting with the BC API.
57
+
58
+ #### `get<T>(endpoint, options)`
59
+
60
+ Retrieves records with automatic pagination and OData support.
61
+
62
+ ```ts
63
+ // Example: Using BcFilter and standard OData options
64
+ const filter = new BcFilter().eq('Item_No', 'item-001');
65
+ const items = await client.get('item', {
66
+ filter: filter,
67
+ top: 5
68
+ });
69
+ ```
70
+
71
+ #### `post<T>(endpoint, body)`
72
+
73
+ Creates a new record.
74
+
75
+ ```ts
76
+ const newItem = {
77
+ Item_No: 'item-001',
78
+ Description: 'SDK-TEST'
79
+ };
80
+ const created = await client.post('item', newItem);
81
+ ```
82
+
83
+ #### `patch<T>(endpoint, criteria, data)`
84
+
85
+ Partially updates an existing record. It handles the OData key predicate and includes `If-Match: *` by default.
86
+
87
+ ```ts
88
+ const criteria = { Item_No: 'item-001', Description: 'SDK-TEST' };
89
+ const newData = { Description: 'Updated via SDK' };
90
+
91
+ await client.patch('item', criteria, newData);
92
+ ```
93
+
94
+ #### `put<T>(endpoint, criteria, data)`
95
+
96
+ Replaces an existing record entirely. It also includes `If-Match: *` by default.
97
+
98
+ ```ts
99
+ const criteria = { Item_No: 'item-001', Description: 'SDK-TEST' };
100
+ const fullData = {
101
+ Item_No: 'item-001',
102
+ Description: 'Full replacement via SDK'
103
+ };
104
+
105
+ await client.put('item', criteria, fullData);
106
+ ```
107
+
108
+ #### `delete(endpoint, criteria)`
109
+
110
+ Deletes a record based on key criteria.
111
+
112
+ ```ts
113
+ const criteria = { Item_No: 'item-001', Description: 'SDK-TEST' };
114
+ await client.delete('item', criteria);
115
+ ```
116
+
117
+ #### `executeAction<T>(endpoint, data)`
118
+
119
+ Executes an **unbound** OData action. Note: In Business Central, unbound actions use a specific route (`ODataV4/ActionName`).
120
+
121
+ ```ts
122
+ await client.executeAction('MyCustomAction', {
123
+ documentNo: '123'
124
+ });
125
+ ```
126
+
127
+ ### ⚙️ Advanced Configuration
128
+
129
+ ```ts
130
+ const client = new BusinessCentralClient({
131
+ requestOptions: {
132
+ maxRetries: 5,
133
+ timeout: 30000,
134
+ },
135
+ debugOptions: {
136
+ duration: true,
137
+ }
138
+ });
139
+ ```
140
+
141
+ ---
142
+
143
+ - **License**: [MIT](./LICENSE) © 2026 David Cheto (ChetoDB)
@@ -0,0 +1,49 @@
1
+ import type { AzureKey } from '../types/azure-key.types.js';
2
+ /**
3
+ * Represents a cached OAuth2 token with expiration tracking.
4
+ */
5
+ export interface TokenInfo {
6
+ token: string;
7
+ expiresAt: Date;
8
+ lastUpdate: Date;
9
+ }
10
+ /**
11
+ * Raw token response from Azure AD / Entra ID.
12
+ */
13
+ export interface TokenResponse {
14
+ access_token: string;
15
+ expires_in: number;
16
+ token_type: string;
17
+ }
18
+ /**
19
+ * Contract for authentication providers.
20
+ * Implement this interface to provide custom authentication strategies.
21
+ */
22
+ export interface AuthProvider {
23
+ /**
24
+ * Retrieves a valid Bearer token.
25
+ *
26
+ * @param forceRefresh - If true, ignores any cached token and requests a new one.
27
+ * @returns A promise that resolves to the token string, or null if creation fails.
28
+ */
29
+ getToken(forceRefresh?: boolean): Promise<string | null>;
30
+ /**
31
+ * Retrieves a full Authorization header value (e.g., 'Bearer xxx').
32
+ *
33
+ * @param forceRefresh - If true, ignores any cached token and requests a new one.
34
+ * @returns A promise that resolves to the complete header string, or null if creation fails.
35
+ */
36
+ getAuthHeader(forceRefresh?: boolean): Promise<string | null>;
37
+ /**
38
+ * Rotates to the next available Azure Key in the configuration.
39
+ * Used automatically by the client when it encounters rate limits (429) or token errors.
40
+ */
41
+ rotateKey(): void;
42
+ /**
43
+ * Returns the Azure Key currently being used by the provider.
44
+ *
45
+ * @returns The active ActiveKey object containing clientId and name.
46
+ */
47
+ getCurrentKey(): AzureKey;
48
+ }
49
+ //# sourceMappingURL=auth.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.types.d.ts","sourceRoot":"","sources":["../../src/auth/auth.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEzD;;;;;OAKG;IACH,aAAa,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE9D;;;OAGG;IACH,SAAS,IAAI,IAAI,CAAC;IAElB;;;;OAIG;IACH,aAAa,IAAI,QAAQ,CAAC;CAC3B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.types.js","sourceRoot":"","sources":["../../src/auth/auth.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export type { AuthProvider, TokenInfo, TokenResponse } from './auth.types.js';
2
+ export { OAuth2Provider } from './oauth2.provider.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { OAuth2Provider } from './oauth2.provider.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { AzureKey } from '../types/azure-key.types.js';
2
+ import type { BcClientOptions } from '../types/client.types.js';
3
+ import type { AuthProvider } from './auth.types.js';
4
+ /**
5
+ * OAuth2 Client Credentials authentication provider for Azure AD / Entra ID.
6
+ *
7
+ * Features:
8
+ * - In-memory token cache with automatic refresh (5 min before expiry)
9
+ * - Round-robin key rotation on rate limiting (429) or auth failures
10
+ * - Retry with exponential backoff on token acquisition
11
+ */
12
+ export declare class OAuth2Provider implements AuthProvider {
13
+ private readonly options;
14
+ private currentToken;
15
+ private currentKey;
16
+ private readonly urlBuilder;
17
+ private readonly scope;
18
+ private readonly grantType;
19
+ private readonly keys;
20
+ /**
21
+ * Initializes the OAuth2 Provider with the SDK options.
22
+ *
23
+ * @param options - The fully resolved SDK configuration options.
24
+ * @throws Error if `options.azureKeys` is empty or undefined.
25
+ */
26
+ constructor(options: Required<BcClientOptions>);
27
+ /**
28
+ * Returns a valid access token. Uses cached token if still valid,
29
+ * otherwise requests a new one from Azure AD.
30
+ *
31
+ * @param forceRefresh - If true, bypasses the cache and forces a new token request.
32
+ * @returns The raw access token string, or null if the request fails repeatedly.
33
+ */
34
+ getToken(forceRefresh?: boolean): Promise<string | null>;
35
+ /**
36
+ * Returns a full 'Bearer xxx' authorization header value.
37
+ *
38
+ * @param forceRefresh - If true, bypasses the cache and forces a new token request.
39
+ * @returns The fully formatted Authorization header string, or null if token acquisition fails.
40
+ */
41
+ getAuthHeader(forceRefresh?: boolean): Promise<string | null>;
42
+ /** Rotates to the next Azure Key in round-robin fashion. */
43
+ rotateKey(): void;
44
+ /** Returns the currently active Azure Key. */
45
+ getCurrentKey(): AzureKey;
46
+ }
47
+ //# sourceMappingURL=oauth2.provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2.provider.d.ts","sourceRoot":"","sources":["../../src/auth/oauth2.provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,iBAAiB,CAAC;AAE9E;;;;;;;GAOG;AACH,qBAAa,cAAe,YAAW,YAAY;IAerC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAdpC,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,UAAU,CAAW;IAE7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAElC;;;;;OAKG;gBAC0B,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC;IAsB/D;;;;;;OAMG;IACG,QAAQ,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqE5D;;;;;OAKG;IACG,aAAa,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKjE,4DAA4D;IAC5D,SAAS,IAAI,IAAI;IAajB,8CAA8C;IAC9C,aAAa,IAAI,QAAQ;CAG1B"}
@@ -0,0 +1,131 @@
1
+ import { UrlBuilder } from '../http/url-builder.js';
2
+ import { dateAddSeconds, dateFormat, dateIsBefore } from '../utils/date.utils.js';
3
+ /**
4
+ * OAuth2 Client Credentials authentication provider for Azure AD / Entra ID.
5
+ *
6
+ * Features:
7
+ * - In-memory token cache with automatic refresh (5 min before expiry)
8
+ * - Round-robin key rotation on rate limiting (429) or auth failures
9
+ * - Retry with exponential backoff on token acquisition
10
+ */
11
+ export class OAuth2Provider {
12
+ options;
13
+ currentToken = null;
14
+ currentKey;
15
+ urlBuilder;
16
+ scope;
17
+ grantType;
18
+ keys;
19
+ /**
20
+ * Initializes the OAuth2 Provider with the SDK options.
21
+ *
22
+ * @param options - The fully resolved SDK configuration options.
23
+ * @throws Error if `options.azureKeys` is empty or undefined.
24
+ */
25
+ constructor(options) {
26
+ this.options = options;
27
+ if (!options.azureKeys || options.azureKeys.length === 0) {
28
+ throw new Error('[BC - Auth] No Azure Keys provided. At least one key is required.');
29
+ }
30
+ this.keys = options.azureKeys;
31
+ this.currentKey = this.keys[0];
32
+ this.scope = options.azureConfig.scope;
33
+ this.grantType = options.azureConfig.grantType;
34
+ this.urlBuilder = new UrlBuilder({
35
+ basicUrl: options.azureConfig.basicUrl,
36
+ apiVersion: options.apiVersion,
37
+ tenantId: options.tenantId,
38
+ environment: options.environment,
39
+ companyName: options.companyName,
40
+ tokenUrl: options.azureConfig.tokenUrl,
41
+ });
42
+ console.debug(`[BC - Auth] Connecting to environment: ${options.environment}`);
43
+ }
44
+ /**
45
+ * Returns a valid access token. Uses cached token if still valid,
46
+ * otherwise requests a new one from Azure AD.
47
+ *
48
+ * @param forceRefresh - If true, bypasses the cache and forces a new token request.
49
+ * @returns The raw access token string, or null if the request fails repeatedly.
50
+ */
51
+ async getToken(forceRefresh = false) {
52
+ if (!forceRefresh &&
53
+ this.currentToken?.token &&
54
+ dateIsBefore(new Date(), this.currentToken.expiresAt)) {
55
+ return this.currentToken.token;
56
+ }
57
+ const maxRetries = 3;
58
+ let currentRetry = 0;
59
+ while (currentRetry < maxRetries) {
60
+ try {
61
+ const tokenUrl = this.urlBuilder.tokenUrl();
62
+ const response = await fetch(tokenUrl, {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/x-www-form-urlencoded',
66
+ Authorization: `Basic ${btoa(`${this.currentKey.clientId}:${this.currentKey.clientSecret}`)}`,
67
+ },
68
+ body: new URLSearchParams({
69
+ grant_type: this.grantType,
70
+ scope: this.scope,
71
+ }).toString(),
72
+ signal: AbortSignal.timeout(10000),
73
+ });
74
+ if (!response.ok) {
75
+ throw new Error(`Token request failed: ${response.status} ${response.statusText}`);
76
+ }
77
+ const data = (await response.json());
78
+ // Cache token, refreshing 5 minutes (300s) before actual expiry
79
+ this.currentToken = {
80
+ token: data.access_token,
81
+ expiresAt: dateAddSeconds(new Date(), data.expires_in - 300),
82
+ lastUpdate: new Date(),
83
+ };
84
+ console.debug(`[BC - Auth] New token acquired (Key: ${this.currentKey.name}). Expires: ${dateFormat(this.currentToken.expiresAt)}`);
85
+ return this.currentToken.token;
86
+ }
87
+ catch (error) {
88
+ currentRetry++;
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ console.warn(`[BC - Auth] Token attempt ${currentRetry}/${maxRetries} failed: ${message}`);
91
+ if (currentRetry < maxRetries) {
92
+ // On rate limit or auth error, try the next key
93
+ if (this.keys.length > 1) {
94
+ this.rotateKey();
95
+ }
96
+ const delay = 2 ** currentRetry * 1000;
97
+ console.debug(`[BC - Auth] Retrying token in ${delay / 1000}s...`);
98
+ await new Promise((resolve) => setTimeout(resolve, delay));
99
+ }
100
+ }
101
+ }
102
+ console.error(`[BC - Auth] Failed to acquire token after ${maxRetries} attempts.`);
103
+ return null;
104
+ }
105
+ /**
106
+ * Returns a full 'Bearer xxx' authorization header value.
107
+ *
108
+ * @param forceRefresh - If true, bypasses the cache and forces a new token request.
109
+ * @returns The fully formatted Authorization header string, or null if token acquisition fails.
110
+ */
111
+ async getAuthHeader(forceRefresh = false) {
112
+ const token = await this.getToken(forceRefresh);
113
+ return token ? `Bearer ${token}` : null;
114
+ }
115
+ /** Rotates to the next Azure Key in round-robin fashion. */
116
+ rotateKey() {
117
+ if (this.keys.length <= 1)
118
+ return;
119
+ const currentIndex = this.keys.findIndex((k) => k.name === this.currentKey.name);
120
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % this.keys.length;
121
+ const previousName = this.currentKey.name;
122
+ this.currentKey = this.keys[nextIndex];
123
+ this.currentToken = null;
124
+ console.debug(`[BC - Auth] Rotating key: [${previousName}] → [${this.currentKey.name}]`);
125
+ }
126
+ /** Returns the currently active Azure Key. */
127
+ getCurrentKey() {
128
+ return this.currentKey;
129
+ }
130
+ }
131
+ //# sourceMappingURL=oauth2.provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2.provider.js","sourceRoot":"","sources":["../../src/auth/oauth2.provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=oauth2.provider.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2.provider.spec.d.ts","sourceRoot":"","sources":["../../src/auth/oauth2.provider.spec.ts"],"names":[],"mappings":""}