-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathsdk.ts
More file actions
351 lines (314 loc) · 11.7 KB
/
sdk.ts
File metadata and controls
351 lines (314 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
import createDebug from 'debug';
import { AuthManager } from './auth';
import { CollectionTypeManager, SingleTypeManager } from './content-types';
import { StrapiError, StrapiInitializationError } from './errors';
import { HttpClient } from './http';
import { AuthInterceptors, HttpInterceptors } from './interceptors';
import { StrapiConfigValidator } from './validators';
import type { HttpClientConfig } from './http';
const debug = createDebug('strapi:core');
export interface StrapiConfig {
/** The base URL of the Strapi content API, required for all SDK operations. */
baseURL: string;
/** Optional authentication configuration, which specifies a strategy and its details. */
auth?: AuthConfig;
}
/**
* Describes an authentication strategy used in the SDK configuration.
*
* @template T The type of options for the authentication strategy.
*/
export interface AuthConfig<T = unknown> {
/** The identifier of the authentication method */
strategy: string;
/** Configuration details for the specified strategy */
options?: T;
}
/**
* Class representing the Strapi SDK to interface with a Strapi backend.
*
* This class integrates setting up configuration, validation, and handling
* HTTP requests with authentication.
*
* It serves as the main interface through which users interact with
* their Strapi installation programmatically.
*/
export class Strapi {
/** @internal */
private readonly _config: StrapiConfig;
/** @internal */
private readonly _validator: StrapiConfigValidator;
/** @internal */
private readonly _authManager: AuthManager;
/** @internal */
private readonly _httpClient: HttpClient;
/** @internal */
constructor(
// Properties
config: StrapiConfig,
// Dependencies
validator: StrapiConfigValidator = new StrapiConfigValidator(),
authManager: AuthManager = new AuthManager(),
// Lazy dependencies
httpClientFactory?: (config: HttpClientConfig) => HttpClient
) {
// Properties
this._config = config;
// Dependencies
this._validator = validator;
this._authManager = authManager;
debug('started the initialization process');
// Validation
this.preflightValidation();
debug('user config passed the preflight validation');
// The HTTP client depends on the preflightValidation for the baseURL validity.
// It could be instantiated before but would throw an invalid URL error
// instead of the SDK itself throwing an initialization exception.
this._httpClient = httpClientFactory
? httpClientFactory({ baseURL: config.baseURL })
: new HttpClient({ baseURL: config.baseURL });
this.init();
debug('finished the sdk initialization process');
}
/**
* Performs preliminary validation of the SDK configuration.
*
* This method ensures that the provided configuration for the SDK is valid by using the
* internal SDK validator. It is invoked during the initialization process to confirm that
* all necessary parts are correctly configured before effectively using the SDK.
*
* @throws {StrapiInitializationError} If the configuration validation fails, indicating an issue with the SDK initialization process.
*
* @example
* // Creating a new instance of the SDK which triggers preflightValidation
* const config = {
* baseURL: 'https://example.com',
* auth: {
* strategy: 'jwt',
* options: { token: 'your-token-here' }
* }
* };
* const sdk = new Strapi(config);
*
* // The preflightValidation is automatically called within the constructor
* // to ensure the provided config is valid prior to any further setup.
*
* @note This method is private and only called internally during SDK initialization.
*
* @internal
*/
private preflightValidation() {
try {
debug('validating the configuration');
this._validator.validateConfig(this._config);
} catch (e) {
throw new StrapiInitializationError(e);
}
}
/**
* Initializes the configuration settings for the SDK.
*
* @internal
*/
private init() {
debug('init modules');
this.initHttp();
this.initAuth();
}
/**
* Initializes the HTTP client configuration for the SDK.
*
* Sets up necessary HTTP interceptors to ensure consistent behavior:
* - Adds default HTTP request headers.
* - Configures error handling for HTTP responses.
*
* It basically ensures that all outgoing HTTP requests include standard headers
* and that errors are properly converted into meaningful exceptions for easier debugging.
*
* @note
* This method is private and should only be invoked internally during the SDK initialization process.
*
* @internal
*/
private initHttp() {
debug('init http module');
// Automatically sets default headers for all HTTP requests.
this._httpClient.interceptors.request.use(HttpInterceptors.setDefaultHeaders());
// Handle HTTP response errors and transform them into
// more specific and meaningful exceptions (subclasses of `HTTPError`)
this._httpClient.interceptors.response.use(HttpInterceptors.transformErrors());
}
/**
* Initializes the authentication configuration for the SDK.
*
* Sets up authentication strategies and required HTTP interceptors to:
* - Handle user authentication through the configured strategy.
* - Automatically attach authentication data (for example, tokens) to outgoing HTTP requests.
* - Handle authentication errors (for example, unauthorized responses) consistently.
*
* @note
* This method is private and should only be invoked internally during the SDK initialization process.
*
* @internal
*/
private initAuth() {
debug('init auth module');
// If an auth configuration is defined, use it to configure the auth manager
if (this.auth) {
const { strategy, options } = this.auth;
debug('setting up the auth strategy using %o', strategy);
try {
this._authManager.setStrategy(strategy, options);
} catch (e) {
throw new StrapiInitializationError(
e,
`Failed to initialize the SDK auth manager: ${e instanceof StrapiError ? e.cause : e}`
);
}
}
this._httpClient.interceptors.request
// Ensures the "user" is pre-authenticated before an HTTP request is sent.
.use(AuthInterceptors.ensurePreAuthentication(this._authManager, this._httpClient))
// Authenticates outgoing HTTP requests by injecting authentication-specific headers.
.use(AuthInterceptors.authenticateRequests(this._authManager));
this._httpClient.interceptors.response
// Notifies the authentication manager upon receiving an unauthorized HTTP response or error.
.use(...AuthInterceptors.notifyOnUnauthorizedResponse(this._authManager));
}
/**
* Retrieves the authentication configuration for the Strapi SDK.
*
* @note This is a private property used internally within the SDK for configuring authentication in the HTTP layer.
*
* @internal
*/
private get auth() {
return this._config.auth;
}
/**
* Retrieves the base URL of the Strapi SDK instance.
*
* This getter returns the `baseURL` property stored within the SDK's configuration object.
*
* The base URL is used as the starting point for all HTTP requests initiated through the SDK.
*
* @returns The current base URL configured in the SDK.
* This URL typically represents the root endpoint of the Strapi service the SDK interfaces with.
*
* @example
* const config = { baseURL: 'http://localhost:1337/api' };
* const sdk = new Strapi(config);
*
* console.log(sdk.baseURL); // Output: http://localhost:1337
*/
public get baseURL(): string {
return this._config.baseURL;
}
/**
* Executes an HTTP fetch request to a specified endpoint using the SDK HTTP client.
*
* This method ensures authentication is handled before issuing requests and sets the necessary headers.
*
* @param url - The endpoint to fetch from, appended to the base URL of the SDK.
* @param [init] - Optional initialization options for the request, such as headers or method type.
*
* @example
* ```typescript
* // Create the SDK instance
* const config = { baseURL: 'http://localhost:1337/api' };
* const sdk = new Strapi(config);
*
* // Perform a custom fetch query
* const response = await sdk.fetch('/categories');
*
* // Parse the categories into a readable JSON object
* const categories = await response.json();
*
* // Log the categories
* console.log(categories);
* ```
*
* @note
* - The method automatically handles authentication by checking if the user is authenticated and attempts to authenticate if not.
* - The base URL is prepended to the provided endpoint path.
*/
fetch(url: string, init?: RequestInit) {
return this._httpClient.request(url, init);
}
/**
* Returns a {@link CollectionTypeManager} instance to interact with the specified collection-type routes in the
* Strapi app.
*
* This instance provides methods for performing operations on the associated documents: create, read, update, delete.
*
* @param resource - The plural name of the collection to interact with.
* This should match the collection name as defined in the Strapi app.
*
* @returns An instance of {@link CollectionTypeManager} targeting the given {@link resource} name.
*
* @example
* ```typescript
* // Initialize the SDK with required configuration
* const config = { baseURL: 'http://localhost:1337/api' };
* const sdk = new Strapi(config);
*
* // Retrieve a CollectionTypeManager for the 'articles' resource
* const articles = sdk.collection('articles');
*
* // Example: find all articles
* const allArticles = await articles.find();
*
* // Example: find a single article by ID
* const singleArticle = await articles.findOne('936c6dc0-f2ec-46c3-ac6d-c0f2ec46c396');
*
* // Example: create a new article
* const newArticle = await articles.create({ title: 'New Article' });
*
* // Example: update an existing article
* const updatedArticle = await articles.update('90169631-7033-4963-9696-317033a96341', { title: 'Updated Title' });
*
* // Example: delete an article
* await articles.delete('dde61ffb-00a6-4cc7-a61f-fb00a63cc740');
* ```
*
* @see CollectionTypeManager
* @see Strapi
*/
collection(resource: string) {
return new CollectionTypeManager(resource, this._httpClient);
}
/**
* Returns a {@link SingleTypeManager} instance to interact with the specified single-type routes in the Strapi app.
*
* This instance provides methods for managing the associated single-type document: read, update, delete.
*
* @param resource - The singular name of the single-type resource to interact with.
* This should match the single-type name as defined in the Strapi app.
*
* @returns An instance of {@link SingleTypeManager} targeting the given {@link resource} name.
*
* @example
* ```typescript
* // Initialize the SDK with required configuration
* const sdk = new Strapi({ baseURL: 'http://localhost:1337/api' });
*
* // Retrieve a SingleTypeManager for the 'homepage' resource
* const homepage = sdk.single('homepage');
*
* // Example: fetch the homepage content in Spanish
* const homepageContent = await homepage.find({ locale: 'es' });
*
* // Example: update the homepage content
* const updatedHomepage = await homepage.update({ title: 'Updated Homepage Title' });
*
* // Example: delete the homepage content
* await homepage.delete();
* ```
*
* @see SingleTypeManager
* @see Strapi
*/
single(resource: string) {
return new SingleTypeManager(resource, this._httpClient);
}
}