@ibertran/fortytwo-intra-client 3.2.0 → 4.1.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # FortytwoIntraClient
2
2
 
3
- A TypeScript library for interacting with the 42API with built-in rate limiting, automatic token management, and retry logic.
3
+ A TypeScript library for interacting with the 42 API with built-in rate limiting, automatic token management, and retry logic.
4
4
 
5
5
  ## Installation
6
6
 
@@ -11,13 +11,12 @@ npm install @ibertran/fortytwo-intra-client
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { FortytwoIntraClient } from '@ibertran/fortytwo-intra-client';
15
-
16
- const client = new FortytwoIntraClient('your_client_id', 'your_client_secret');
14
+ import { FortytwoIntraClient } from "@ibertran/fortytwo-intra-client";
17
15
 
16
+ const client = new FortytwoIntraClient("your_client_id", "your_client_secret");
18
17
 
19
18
  // Get a specific user by login
20
- const user = await client.get('users/ibertran');
19
+ const user = await client.get("users/ibertran");
21
20
  console.log(user);
22
21
  ```
23
22
 
@@ -26,37 +25,37 @@ console.log(user);
26
25
  The `FortytwoIntraClient` class accepts three parameters:
27
26
 
28
27
  1. `client_id` (required): Your 42 API client ID
29
- 2. `client_secret` (required): Your 42 API client secret
28
+ 2. `client_secret` (required): Your 42 API client secret
30
29
  3. `config` (optional): Configuration object
31
30
 
32
31
  ### Configuration Options
33
32
 
34
33
  ```typescript
35
34
  interface FortytwoIntraClientConf {
36
- redirect_uri: string | null; // OAuth redirect URI (default: null)
37
- base_url: string; // API base URL (default: "https://api.intra.42.fr/v2/")
38
- token_url: string; // Token endpoint (default: "https://api.intra.42.fr/oauth/token")
39
- oauth_url: string; // OAuth endpoint (default: "https://api.intra.42.fr/oauth/authorize")
40
- token_info_url: string; // Token info endpoint (default: "https://api.intra.42.fr/oauth/token/info")
41
- scopes: string[]; // OAuth scopes (default: ["public"])
42
- rateLimitMaxRequests: number; // Max requests per time window (default: 2)
43
- rateLimitPerMilliseconds: number; // Time window in milliseconds (default: 1200)
44
- maxRetry: number; // Max retry attempts (default: 5)
45
- logLine: boolean; // Enable request logging (default: true)
46
- errLogBody: boolean; // Log error response bodies (default: true)
35
+ redirect_uri: string | null; // OAuth redirect URI (default: null)
36
+ base_url: string; // API base URL (default: "https://api.intra.42.fr/v2/")
37
+ token_url: string; // Token endpoint (default: "https://api.intra.42.fr/oauth/token")
38
+ oauth_url: string; // OAuth endpoint (default: "https://api.intra.42.fr/oauth/authorize")
39
+ token_info_url: string; // Token info endpoint (default: "https://api.intra.42.fr/oauth/token/info")
40
+ scopes: string[]; // OAuth scopes (default: ["public"])
41
+ rateLimitMaxRequests: number; // Max requests per time window (default: 2)
42
+ rateLimitPerMilliseconds: number; // Time window in milliseconds (default: 1200)
43
+ maxRetry: number; // Max retry attempts (default: 5)
44
+ logLine: boolean; // Enable request logging (default: true)
45
+ errLogBody: boolean; // Log error response bodies (default: true)
47
46
  }
48
47
  ```
49
48
 
50
49
  ### Example with Custom Configuration
51
50
 
52
51
  ```typescript
53
- const client = new FortytwoIntraClient('your_client_id', 'your_client_secret', {
54
- rateLimitMaxRequests: 1, // 1 request per 1200ms
55
- rateLimitPerMilliseconds: 1200, // Time window
56
- maxRetry: 3, // Retry up to 3 times
57
- logLine: false, // Disable logging
58
- errLogBody: false, // Disable error body logging
59
- scopes: ['public', 'projects'] // Extended scopes
52
+ const client = new FortytwoIntraClient("your_client_id", "your_client_secret", {
53
+ rateLimitMaxRequests: 1, // 1 request per 1200ms
54
+ rateLimitPerMilliseconds: 1200, // Time window
55
+ maxRetry: 3, // Retry up to 3 times
56
+ logLine: false, // Disable logging
57
+ errLogBody: false, // Disable error body logging
58
+ scopes: ["public", "projects"], // Extended scopes
60
59
  });
61
60
  ```
62
61
 
@@ -66,11 +65,11 @@ const client = new FortytwoIntraClient('your_client_id', 'your_client_secret', {
66
65
 
67
66
  ```typescript
68
67
  // Get all campuses
69
- const campuses = await client.get('campus');
68
+ const campuses = await client.get("campus");
70
69
  console.log(`Found ${campuses.length} campuses`);
71
70
 
72
71
  // Get a specific campus
73
- const lyonCampus = await client.get('campus/9');
72
+ const lyonCampus = await client.get("campus/9");
74
73
  console.log(`${lyonCampus.name}: ${lyonCampus.users_count} users`);
75
74
  ```
76
75
 
@@ -78,11 +77,11 @@ console.log(`${lyonCampus.name}: ${lyonCampus.users_count} users`);
78
77
 
79
78
  ```typescript
80
79
  // Create a team for a project
81
- const team = await client.post('teams', {
82
- body: {
83
- name: 'awesome-team',
84
- project_id: 42
85
- }
80
+ const team = await client.post("teams", {
81
+ body: {
82
+ name: "awesome-team",
83
+ project_id: 42,
84
+ },
86
85
  });
87
86
  ```
88
87
 
@@ -90,12 +89,12 @@ const team = await client.post('teams', {
90
89
 
91
90
  ```typescript
92
91
  // Update a team name
93
- const updatedTeam = await client.patch('teams/123', {
94
- body: {
92
+ const updatedTeam = await client.patch("teams/123", {
93
+ body: {
95
94
  team: {
96
- name: "updated-team-name"
97
- }
98
- }
95
+ name: "updated-team-name",
96
+ },
97
+ },
99
98
  });
100
99
  ```
101
100
 
@@ -103,7 +102,7 @@ const updatedTeam = await client.patch('teams/123', {
103
102
 
104
103
  ```typescript
105
104
  // Delete a team
106
- await client.delete('teams/123');
105
+ await client.delete("teams/123");
107
106
  ```
108
107
 
109
108
  ### Get All Pages (Pagination Helper)
@@ -112,12 +111,12 @@ The `getAll` method automatically handles pagination and returns all results:
112
111
 
113
112
  ```typescript
114
113
  // Get all users from 42Lyon (handles pagination automatically)
115
- const lyonUsers = await client.getAll('campus/9/users');
114
+ const lyonUsers = await client.getAll("campus/9/users");
116
115
  console.log(`42Lyon campus has ${lyonUsers.length} users`);
117
116
 
118
117
  // Get all projects with custom page size
119
- const allProjects = await client.getAll('projects', {
120
- perPage: 50 // 50 items per page (default: 100)
118
+ const allProjects = await client.getAll("projects", {
119
+ perPage: 50, // 50 items per page (default: 100)
121
120
  });
122
121
  ```
123
122
 
@@ -127,16 +126,16 @@ const allProjects = await client.getAll('projects', {
127
126
 
128
127
  ```typescript
129
128
  // 1. Get OAuth authorization URL
130
- const authUrl = client.getOAuthUrl('http://localhost:3000/callback');
129
+ const { url: authUrl } = client.getOAuthUrl();
131
130
  // Redirect user to authUrl
132
131
 
133
132
  // 2. Exchange authorization code for tokens
134
- const tokens = await client.exchangeOAuthCode(code, 'http://localhost:3000/callback');
133
+ const tokens = await client.exchangeOAuthCode(code, "http://localhost:3000/callback");
135
134
  // tokens contains: access_token, refresh_token, expires_in, etc.
136
135
 
137
136
  // 3. Use tokens for authenticated requests (USER-SPECIFIC DATA)
138
- const currentUser = await client.get('users/me', {
139
- token: tokens
137
+ const currentUser = await client.get("users/me", {
138
+ token: tokens,
140
139
  });
141
140
  console.log(`Welcome ${currentUser.displayname}!`);
142
141
  ```
@@ -145,12 +144,14 @@ console.log(`Welcome ${currentUser.displayname}!`);
145
144
 
146
145
  ```typescript
147
146
  // Set redirect URI in constructor
148
- const client = new IntraApiProxy('client_id', 'client_secret', {
149
- redirect_uri: 'http://localhost:3000/callback'
147
+ const client = new FortytwoIntraClient("client_id", "client_secret", {
148
+ redirect_uri: "http://localhost:3000/callback",
150
149
  });
151
150
 
152
151
  // Or pass it to getOAuthUrl
153
- const authUrl = client.getOAuthUrl('http://localhost:3000/callback');
152
+ const { url: authUrl } = client.getOAuthUrl({
153
+ redirect_uri: "http://localhost:3000/callback",
154
+ });
154
155
  ```
155
156
 
156
157
  ## Utility Methods
@@ -159,13 +160,13 @@ const authUrl = client.getOAuthUrl('http://localhost:3000/callback');
159
160
 
160
161
  ```typescript
161
162
  // Create a URL object for the API
162
- const userUrl = client.URL('users/jdoe');
163
+ const userUrl = client.URL("users/jdoe");
163
164
  console.log(userUrl.toString()); // "https://api.intra.42.fr/v2/users/jdoe"
164
165
 
165
166
  // Build URLs for specific endpoints
166
- const campusUrl = client.URL('campus/1/users');
167
- const projectUrl = client.URL('projects/libft');
168
- const coalitionUrl = client.URL('coalitions/1/users');
167
+ const campusUrl = client.URL("campus/1/users");
168
+ const projectUrl = client.URL("projects/libft");
169
+ const coalitionUrl = client.URL("coalitions/1/users");
169
170
  ```
170
171
 
171
172
  ## Error Handling
@@ -174,16 +175,16 @@ The library includes automatic retry logic for rate limiting (429) and authentic
174
175
 
175
176
  ```typescript
176
177
  try {
177
- const user = await client.get('users/jdoe');
178
- console.log(`Found user: ${user.displayname}`);
178
+ const user = await client.get("users/jdoe");
179
+ console.log(`Found user: ${user.displayname}`);
179
180
  } catch (error) {
180
- if (error.status === 404) {
181
- console.error('User not found');
182
- } else if (error.status === 403) {
183
- console.error('Access forbidden - check your token permissions');
184
- } else {
185
- console.error('Request failed after retries:', error.message);
186
- }
181
+ if (error.statusCode === 404) {
182
+ console.error("User not found");
183
+ } else if (error.statusCode === 403) {
184
+ console.error("Access forbidden - check your token permissions");
185
+ } else {
186
+ console.error("Request failed after retries:", error.message);
187
+ }
187
188
  }
188
189
  ```
189
190
 
@@ -209,60 +210,60 @@ Disable logging by setting `logLine: false` in the configuration. Control error
209
210
  This library is written in TypeScript and includes full type definitions:
210
211
 
211
212
  ```typescript
212
- import { FortytwoIntraClient } from '@ibertran/fortytwo-intra-client';
213
+ import { FortytwoIntraClient } from "@ibertran/fortytwo-intra-client";
213
214
 
214
215
  // Types are automatically inferred
215
- const client = new FortytwoIntraClient('client_id', 'client_secret');
216
+ const client = new FortytwoIntraClient("client_id", "client_secret");
216
217
  ```
217
218
 
218
219
  ### OAuth Flow Example
219
220
 
220
221
  ```typescript
221
- import express from 'express';
222
- import { FortytwoIntraClient } from '@ibertran/fortytwo-intra-client';
222
+ import express from "express";
223
+ import { FortytwoIntraClient } from "@ibertran/fortytwo-intra-client";
223
224
 
224
225
  const app = express();
225
226
  const client = new FortytwoIntraClient(
226
- process.env.INTRA_CLIENT_ID!,
227
- process.env.INTRA_CLIENT_SECRET!,
228
- {
229
- redirect_uri: 'http://localhost:3000/callback',
230
- oauth_scope: ['public']
231
- }
227
+ process.env.INTRA_CLIENT_ID!,
228
+ process.env.INTRA_CLIENT_SECRET!,
229
+ {
230
+ redirect_uri: "http://localhost:3000/callback",
231
+ scopes: ["public"],
232
+ },
232
233
  );
233
234
 
234
235
  // Redirect to 42 OAuth
235
- app.get('/login', (req, res) => {
236
- const authUrl = client.getOAuthUrl();
237
- res.redirect(authUrl);
236
+ app.get("/login", (req, res) => {
237
+ const { url: authUrl } = client.getOAuthUrl();
238
+ res.redirect(authUrl);
238
239
  });
239
240
 
240
241
  // Handle OAuth callback
241
- app.get('/callback', async (req, res) => {
242
- try {
243
- const { code } = req.query;
244
- const token = await client.exchangeOAuthCode(code as string);
245
-
246
- // Use tokens to make authenticated requests (accessing personal data)
247
- const userData = await client.get('users/me', { token: token });
248
-
249
- res.json({
250
- user: {
251
- login: userData.login,
252
- displayname: userData.displayname,
253
- email: userData.email,
254
- level: userData.cursus_users[0]?.level || 0,
255
- wallet: userData.wallet,
256
- campus: userData.campus[0]?.name
257
- },
258
- });
259
- } catch (error) {
260
- res.status(500).json({ error: error.message });
261
- }
242
+ app.get("/callback", async (req, res) => {
243
+ try {
244
+ const { code } = req.query;
245
+ const token = await client.exchangeOAuthCode(code as string);
246
+
247
+ // Use tokens to make authenticated requests (accessing personal data)
248
+ const userData = await client.get("users/me", { token: token });
249
+
250
+ res.json({
251
+ user: {
252
+ login: userData.login,
253
+ displayname: userData.displayname,
254
+ email: userData.email,
255
+ level: userData.cursus_users[0]?.level || 0,
256
+ wallet: userData.wallet,
257
+ campus: userData.campus[0]?.name,
258
+ },
259
+ });
260
+ } catch (error) {
261
+ res.status(500).json({ error: error.message });
262
+ }
262
263
  });
263
264
 
264
265
  app.listen(3000, () => {
265
- console.log('Server running on http://localhost:3000');
266
- console.log('Visit http://localhost:3000/login to start OAuth flow');
266
+ console.log("Server running on http://localhost:3000");
267
+ console.log("Visit http://localhost:3000/login to start OAuth flow");
267
268
  });
268
269
  ```
@@ -0,0 +1,7 @@
1
+ import { ZodError } from "zod";
2
+ export declare class FortytwoIntraClientValidationError extends Error {
3
+ schemaErrors: ZodError;
4
+ data: unknown;
5
+ constructor(schemaErrors: ZodError, data: unknown);
6
+ }
7
+ export declare function isFortytwoIntraClientValidationError(error: unknown): error is FortytwoIntraClientValidationError;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FortytwoIntraClientValidationError = void 0;
4
+ exports.isFortytwoIntraClientValidationError = isFortytwoIntraClientValidationError;
5
+ class FortytwoIntraClientValidationError extends Error {
6
+ schemaErrors;
7
+ data;
8
+ constructor(schemaErrors, data) {
9
+ // const errorMessages = schemaErrors.errors
10
+ // .map((error) => `${error.path.join(".")} - ${error.message}`)
11
+ // .join("; ");
12
+ const message = `Schema validation failed: `;
13
+ super(message);
14
+ this.name = "FortytwoIntraClientValidationError";
15
+ this.schemaErrors = schemaErrors;
16
+ this.data = data;
17
+ }
18
+ }
19
+ exports.FortytwoIntraClientValidationError = FortytwoIntraClientValidationError;
20
+ function isFortytwoIntraClientValidationError(error) {
21
+ return error instanceof FortytwoIntraClientValidationError;
22
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { inputOptions } from "./types";
2
2
  import { FortytwoIntraClientError, isFortytwoIntraClientError } from "./lib/errors";
3
+ import { z } from "zod";
3
4
  export interface FortytwoIntraClientConf {
4
5
  redirect_uri: string | null;
5
6
  base_url: string;
@@ -12,7 +13,6 @@ export interface FortytwoIntraClientConf {
12
13
  maxRetry: number;
13
14
  logLine: boolean;
14
15
  errLogBody: boolean;
15
- throwOnError: boolean;
16
16
  }
17
17
  export declare class FortytwoIntraClient {
18
18
  private client_id;
@@ -30,7 +30,6 @@ export declare class FortytwoIntraClient {
30
30
  private maxRetry;
31
31
  private logLine;
32
32
  private errLogBody;
33
- private throwOnError;
34
33
  private access_token;
35
34
  constructor(client_id: string, client_secret: string, conf: Partial<FortytwoIntraClientConf>);
36
35
  private generateToken;
@@ -38,21 +37,40 @@ export declare class FortytwoIntraClient {
38
37
  private reqHandler;
39
38
  private logSuccess;
40
39
  private logError;
41
- get(endpoint: URL | string, options?: Omit<inputOptions, "body" | "perPage" | "maxPage">): Promise<any>;
42
- post(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
43
- put(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
44
- patch(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
45
- delete(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
40
+ private validate;
41
+ get(endpoint: URL | string, options?: Omit<inputOptions, "body" | "perPage" | "maxPages">): Promise<any>;
42
+ get<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body" | "perPage" | "maxPages"> & {
43
+ schema: S;
44
+ }): Promise<z.infer<S>>;
45
+ post(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
46
+ post<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
47
+ schema: S;
48
+ }): Promise<z.infer<S>>;
49
+ put(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
50
+ put<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
51
+ schema: S;
52
+ }): Promise<z.infer<S>>;
53
+ patch(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
54
+ patch<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
55
+ schema: S;
56
+ }): Promise<z.infer<S>>;
57
+ delete(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
58
+ delete<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
59
+ schema: S;
60
+ }): Promise<z.infer<S>>;
46
61
  getAll(endpoint: URL | string, options?: Omit<inputOptions, "body">): Promise<any>;
62
+ getAll<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body"> & {
63
+ schema: S;
64
+ }): Promise<z.infer<S>>;
47
65
  URL(endpoint: string): URL;
48
66
  getOAuthUrl(options?: {
49
67
  redirect_uri?: string;
50
68
  state?: string;
51
69
  }): {
52
70
  url: string;
53
- state: string;
71
+ state: string | null;
54
72
  };
55
73
  exchangeOAuthCode(code: string, redirect_uri?: string): Promise<any>;
56
- tokenInfos(options?: Omit<inputOptions, "body" | "perPage" | "maxPage">): Promise<any>;
74
+ tokenInfos(options?: Omit<inputOptions, "body" | "perPage" | "maxPages">): Promise<any>;
57
75
  }
58
76
  export { FortytwoIntraClientError, isFortytwoIntraClientError };
package/dist/index.js CHANGED
@@ -1,17 +1,51 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.isFortytwoIntraClientError = exports.FortytwoIntraClientError = exports.FortytwoIntraClient = void 0;
7
- const axios_1 = __importDefault(require("axios"));
40
+ const axios_1 = __importStar(require("axios"));
8
41
  const axios_rate_limit_1 = __importDefault(require("axios-rate-limit"));
9
42
  const pagination_1 = require("./lib/pagination");
10
43
  const logs_1 = require("./lib/logs");
11
44
  const errors_1 = require("./lib/errors");
12
45
  Object.defineProperty(exports, "FortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.FortytwoIntraClientError; } });
13
46
  Object.defineProperty(exports, "isFortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.isFortytwoIntraClientError; } });
14
- const crypto_1 = __importDefault(require("crypto"));
47
+ const zod_1 = require("zod");
48
+ const validationError_1 = require("./errors/validationError");
15
49
  const defaultConf = {
16
50
  redirect_uri: null,
17
51
  base_url: "https://api.intra.42.fr/v2/",
@@ -24,7 +58,6 @@ const defaultConf = {
24
58
  maxRetry: 5,
25
59
  logLine: true,
26
60
  errLogBody: true,
27
- throwOnError: true,
28
61
  };
29
62
  class FortytwoIntraClient {
30
63
  client_id;
@@ -42,7 +75,6 @@ class FortytwoIntraClient {
42
75
  maxRetry;
43
76
  logLine;
44
77
  errLogBody;
45
- throwOnError;
46
78
  access_token;
47
79
  constructor(client_id, client_secret, conf) {
48
80
  this.client_id = client_id;
@@ -66,7 +98,6 @@ class FortytwoIntraClient {
66
98
  this.maxRetry = config.maxRetry;
67
99
  this.logLine = config.logLine;
68
100
  this.errLogBody = config.errLogBody;
69
- this.throwOnError = config.throwOnError;
70
101
  this.access_token = null;
71
102
  }
72
103
  async generateToken() {
@@ -84,9 +115,7 @@ class FortytwoIntraClient {
84
115
  this.access_token = await this.generateToken();
85
116
  }
86
117
  // Attach access_token
87
- const accessToken = options.token
88
- ? options.token.access_token
89
- : this.access_token;
118
+ const accessToken = options.token ? options.token.access_token : this.access_token;
90
119
  // Extract query parameters from URL and combine with options.query
91
120
  const urlParams = {};
92
121
  url.searchParams.forEach((value, key) => {
@@ -96,7 +125,7 @@ class FortytwoIntraClient {
96
125
  const combinedParams = { ...urlParams, ...query };
97
126
  // Create clean URL without query parameters
98
127
  const cleanUrl = new URL(url);
99
- cleanUrl.search = '';
128
+ cleanUrl.search = "";
100
129
  // Use the rate-limited axios instance
101
130
  return this.axiosInstance.request({
102
131
  method: method.toLowerCase(),
@@ -115,7 +144,7 @@ class FortytwoIntraClient {
115
144
  return res;
116
145
  }
117
146
  catch (err) {
118
- if (err.isAxiosError) {
147
+ if ((0, axios_1.isAxiosError)(err)) {
119
148
  this.logError(err, options);
120
149
  const { attempt, maxRetry } = options;
121
150
  const status = err.response?.status;
@@ -127,11 +156,7 @@ class FortytwoIntraClient {
127
156
  return this.reqHandler(url, options);
128
157
  }
129
158
  else {
130
- const clientError = (0, errors_1.simplifyAxiosError)(err);
131
- if (this.throwOnError) {
132
- throw clientError;
133
- }
134
- return clientError;
159
+ throw new errors_1.FortytwoIntraClientError(err);
135
160
  }
136
161
  }
137
162
  else {
@@ -158,7 +183,20 @@ class FortytwoIntraClient {
158
183
  console.log(line, logBody ? body : "");
159
184
  }
160
185
  }
161
- // Public methods
186
+ validate(data, schema) {
187
+ if (!schema) {
188
+ return data;
189
+ }
190
+ try {
191
+ return schema.parse(data);
192
+ }
193
+ catch (err) {
194
+ if (err instanceof zod_1.z.ZodError) {
195
+ throw new validationError_1.FortytwoIntraClientValidationError(err, data);
196
+ }
197
+ throw err;
198
+ }
199
+ }
162
200
  async get(endpoint, options = {}) {
163
201
  if (endpoint instanceof URL === false) {
164
202
  endpoint = new URL(endpoint, this.base_url);
@@ -171,7 +209,7 @@ class FortytwoIntraClient {
171
209
  errLogBody: this.errLogBody,
172
210
  ...options,
173
211
  });
174
- return res?.data;
212
+ return this.validate(res.data, options.schema);
175
213
  }
176
214
  async post(endpoint, options = {}) {
177
215
  if (endpoint instanceof URL === false) {
@@ -185,7 +223,7 @@ class FortytwoIntraClient {
185
223
  errLogBody: this.errLogBody,
186
224
  ...options,
187
225
  });
188
- return res?.data;
226
+ return this.validate(res.data, options.schema);
189
227
  }
190
228
  async put(endpoint, options = {}) {
191
229
  if (endpoint instanceof URL === false) {
@@ -199,7 +237,7 @@ class FortytwoIntraClient {
199
237
  errLogBody: this.errLogBody,
200
238
  ...options,
201
239
  });
202
- return res?.data;
240
+ return this.validate(res.data, options.schema);
203
241
  }
204
242
  async patch(endpoint, options = {}) {
205
243
  if (endpoint instanceof URL === false) {
@@ -213,7 +251,7 @@ class FortytwoIntraClient {
213
251
  errLogBody: this.errLogBody,
214
252
  ...options,
215
253
  });
216
- return res?.data;
254
+ return this.validate(res.data, options.schema);
217
255
  }
218
256
  async delete(endpoint, options = {}) {
219
257
  if (endpoint instanceof URL === false) {
@@ -227,7 +265,7 @@ class FortytwoIntraClient {
227
265
  errLogBody: this.errLogBody,
228
266
  ...options,
229
267
  });
230
- return res?.data;
268
+ return this.validate(res.data, options.schema);
231
269
  }
232
270
  async getAll(endpoint, options = {}) {
233
271
  if (endpoint instanceof URL === false) {
@@ -248,51 +286,54 @@ class FortytwoIntraClient {
248
286
  ...options.query,
249
287
  page: 1,
250
288
  per_page: perPage,
251
- }
289
+ },
252
290
  });
291
+ if (!Array.isArray(firstPage.data)) {
292
+ return this.validate(firstPage.data, options.schema);
293
+ }
253
294
  let lastPage;
254
295
  try {
255
296
  lastPage = Math.min((0, pagination_1.getLastPage)(firstPage.headers["link"]), options.maxPages || Infinity);
256
297
  }
257
298
  catch (err) {
258
- return firstPage?.data;
259
- }
260
- const promises = [];
261
- for (let i = 2; i <= lastPage; i++) {
262
- url = new URL(endpoint);
263
- promises.push(this.reqHandler(url, {
264
- method: "GET",
265
- attempt: 0,
266
- currpage: i,
267
- lastPage: lastPage,
268
- maxRetry: this.maxRetry,
269
- logLine: this.logLine,
270
- errLogBody: this.errLogBody,
271
- ...options,
272
- query: {
273
- ...options.query,
274
- page: i,
275
- per_page: perPage,
276
- }
277
- }));
299
+ return this.validate(firstPage.data, options.schema);
278
300
  }
279
- const followingPages = await Promise.all(promises);
280
- return firstPage?.data.concat(...followingPages.map((value) => value.data));
301
+ const promises = Array.from({ length: lastPage - 1 }, (_, i) => i + 2).map((pageNumber) => this.reqHandler(url, {
302
+ method: "GET",
303
+ attempt: 0,
304
+ currpage: pageNumber,
305
+ lastPage: lastPage,
306
+ maxRetry: this.maxRetry,
307
+ logLine: this.logLine,
308
+ errLogBody: this.errLogBody,
309
+ ...options,
310
+ query: {
311
+ ...options.query,
312
+ page: pageNumber,
313
+ per_page: perPage,
314
+ },
315
+ }));
316
+ const otherPages = await Promise.all(promises);
317
+ const allData = [...firstPage.data, ...otherPages.flatMap((res) => res.data)];
318
+ return this.validate(allData, options.schema);
281
319
  }
282
320
  URL(endpoint) {
283
321
  return new URL(endpoint, this.base_url);
284
322
  }
285
323
  getOAuthUrl(options = {}) {
286
324
  const redirectUri = options.redirect_uri || this.redirect_uri;
287
- if (!redirectUri)
325
+ if (!redirectUri) {
288
326
  throw new Error(`Missing redirect_uri parameter`);
289
- const state = options.state || crypto_1.default.randomBytes(32).toString('base64url');
327
+ }
290
328
  const url = new URL(this.oauth_url);
291
329
  url.searchParams.set("client_id", this.client_id);
292
330
  url.searchParams.set("redirect_uri", redirectUri);
293
331
  url.searchParams.set("response_type", "code");
294
332
  url.searchParams.set("scope", this.scopes.join(" "));
295
- url.searchParams.set("state", state);
333
+ const state = options.state ?? null;
334
+ if (state !== null) {
335
+ url.searchParams.set("state", state);
336
+ }
296
337
  return { url: url.toString(), state };
297
338
  }
298
339
  async exchangeOAuthCode(code, redirect_uri) {
@@ -303,7 +344,7 @@ class FortytwoIntraClient {
303
344
  redirect_uri: redirect_uri ? redirect_uri : this.redirect_uri,
304
345
  code: code,
305
346
  });
306
- return res?.data;
347
+ return res.data;
307
348
  }
308
349
  async tokenInfos(options = {}) {
309
350
  return this.get(this.token_info_url, options);
@@ -1,9 +1,8 @@
1
1
  import { AxiosError } from "axios";
2
2
  export declare class FortytwoIntraClientError extends Error {
3
- statusCode: number | null;
4
- statusText: string | null;
5
- data: any;
3
+ statusCode: number;
4
+ statusText: string;
5
+ data: unknown;
6
6
  constructor(error: AxiosError);
7
7
  }
8
- export declare function simplifyAxiosError(error: AxiosError): FortytwoIntraClientError;
9
- export declare function isFortytwoIntraClientError(error: any): error is FortytwoIntraClientError;
8
+ export declare function isFortytwoIntraClientError(error: unknown): error is FortytwoIntraClientError;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FortytwoIntraClientError = void 0;
4
- exports.simplifyAxiosError = simplifyAxiosError;
5
4
  exports.isFortytwoIntraClientError = isFortytwoIntraClientError;
6
5
  class FortytwoIntraClientError extends Error {
7
6
  statusCode;
@@ -12,9 +11,7 @@ class FortytwoIntraClientError extends Error {
12
11
  const route = error.config?.url ?? null;
13
12
  const statusCode = error.response?.status ?? NaN;
14
13
  const statusText = error.response?.statusText ?? "Unknown";
15
- const message = method && route
16
- ? `${method} ${route} - HTTP ${statusCode} ${statusText}`
17
- : error.message;
14
+ const message = method && route ? `${method} ${route} - HTTP ${statusCode} ${statusText}` : error.message;
18
15
  super(message);
19
16
  this.name = "FortytwoIntraClientError";
20
17
  this.statusCode = statusCode;
@@ -23,9 +20,6 @@ class FortytwoIntraClientError extends Error {
23
20
  }
24
21
  }
25
22
  exports.FortytwoIntraClientError = FortytwoIntraClientError;
26
- function simplifyAxiosError(error) {
27
- return new FortytwoIntraClientError(error);
28
- }
29
23
  function isFortytwoIntraClientError(error) {
30
24
  return error instanceof FortytwoIntraClientError;
31
25
  }
package/dist/lib/logs.js CHANGED
@@ -15,7 +15,7 @@ function formatQueryParams(params) {
15
15
  depth: Infinity,
16
16
  compact: true,
17
17
  breakLength: Infinity,
18
- colors: true
18
+ colors: true,
19
19
  });
20
20
  }
21
21
  function getLogLine(res, options) {
@@ -5,9 +5,7 @@ function getLastPage(header) {
5
5
  if (!header) {
6
6
  throw Error("No pagination link header");
7
7
  }
8
- const paginationlinks = header
9
- .split(",")
10
- .reduce((links, part) => {
8
+ const paginationlinks = header.split(",").reduce((links, part) => {
11
9
  const match = part.match(/<([^>]+)>\s*;\s*rel="([^"]+)"/);
12
10
  if (match) {
13
11
  const url = match[1];
package/package.json CHANGED
@@ -1,26 +1,31 @@
1
1
  {
2
- "name": "@ibertran/fortytwo-intra-client",
3
- "version": "3.2.0",
4
- "main": "dist/index.js",
5
- "types": "dist/index.d.ts",
6
- "author": {
7
- "name": "Ian BERTRAND"
8
- },
9
- "scripts": {
10
- "build": "tsc"
11
- },
12
- "dependencies": {
13
- "axios": "^1.6.2",
14
- "axios-rate-limit": "^1.3.0"
15
- },
16
- "devDependencies": {
17
- "typescript": "^5.9.3",
18
- "ts-node": "^10.9.2",
19
- "dotenv": "^16.6.1",
20
- "express": "^4.18.2"
21
- },
22
- "files": [
23
- "dist",
24
- "README.md"
25
- ]
2
+ "name": "@ibertran/fortytwo-intra-client",
3
+ "version": "4.1.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "author": {
7
+ "name": "Ian BERTRAND"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "format": "prettier --write .",
12
+ "prettier": "prettier --check ."
13
+ },
14
+ "dependencies": {
15
+ "axios": "^1.15.2",
16
+ "axios-rate-limit": "^1.9.0",
17
+ "zod": "^4.3.6"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^25.6.0",
21
+ "dotenv": "^17.4.2",
22
+ "express": "^5.2.1",
23
+ "prettier": "^3.8.3",
24
+ "ts-node": "^10.9.2",
25
+ "typescript": "^6.0.3"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ]
26
31
  }
@@ -1,9 +0,0 @@
1
- declare class HttpError extends Error {
2
- status: number | null;
3
- url: string | null;
4
- details: any;
5
- constructor(err: any);
6
- }
7
- export declare function simplifySuperagentError(err: any): HttpError;
8
- export declare function isSuperAgentError(err: any): boolean;
9
- export {};
package/dist/HttpError.js DELETED
@@ -1,25 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.simplifySuperagentError = simplifySuperagentError;
4
- exports.isSuperAgentError = isSuperAgentError;
5
- class HttpError extends Error {
6
- status;
7
- url;
8
- details;
9
- constructor(err) {
10
- super(err.message || "Unknown API error");
11
- this.name = "HttpError";
12
- this.status = err.status || null;
13
- this.url = err.response?.request?.url || null;
14
- this.details = err.response?.body || null;
15
- }
16
- }
17
- function simplifySuperagentError(err) {
18
- if (!err) {
19
- return new HttpError({ message: "Unknown error" });
20
- }
21
- return new HttpError(err);
22
- }
23
- function isSuperAgentError(err) {
24
- return (err && typeof err === "object" && ("status" in err || "response" in err));
25
- }
package/dist/utils.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import * as superagent from "superagent";
2
- import { Method } from "./types";
3
- export declare function initRequest(method: Method, url: URL): superagent.Request;
4
- export declare function getLastPage(header: string | undefined): number;
package/dist/utils.js DELETED
@@ -1,79 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.initRequest = initRequest;
37
- exports.getLastPage = getLastPage;
38
- const superagent = __importStar(require("superagent"));
39
- function initRequest(method, url) {
40
- const endpoint = url.toString();
41
- switch (method) {
42
- case "GET":
43
- return superagent.get(endpoint);
44
- case "POST":
45
- return superagent.post(endpoint);
46
- case "PATCH":
47
- return superagent.patch(endpoint);
48
- case "DELETE":
49
- return superagent.delete(endpoint);
50
- default:
51
- throw Error("Invalid HTTP method");
52
- }
53
- }
54
- function getLastPage(header) {
55
- if (!header) {
56
- throw Error("No pagination link header");
57
- }
58
- const paginationlinks = header
59
- .split(",")
60
- .reduce((links, part) => {
61
- const match = part.match(/<([^>]+)>\s*;\s*rel="([^"]+)"/);
62
- if (match) {
63
- const url = match[1];
64
- const rel = match[2];
65
- links[rel] = url;
66
- }
67
- return links;
68
- }, {});
69
- if (!paginationlinks.last) {
70
- throw Error("Missing last page link");
71
- }
72
- const lastPageUrl = new URL(paginationlinks.last);
73
- const rawLastPage = lastPageUrl.searchParams.get("page");
74
- const lastPage = rawLastPage ? parseInt(rawLastPage, 10) : undefined;
75
- if (!lastPage || isNaN(lastPage)) {
76
- throw Error("Invalid last page link");
77
- }
78
- return lastPage;
79
- }