@ibertran/fortytwo-intra-client 3.1.4 → 4.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.
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,39 +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)
47
- throwOnError: boolean; // Throw errors instead of returning them (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)
48
46
  }
49
47
  ```
50
48
 
51
49
  ### Example with Custom Configuration
52
50
 
53
51
  ```typescript
54
- const client = new FortytwoIntraClient('your_client_id', 'your_client_secret', {
55
- rateLimitMaxRequests: 1, // 1 request per 1200ms
56
- rateLimitPerMilliseconds: 1200, // Time window
57
- maxRetry: 3, // Retry up to 3 times
58
- logLine: false, // Disable logging
59
- errLogBody: false, // Disable error body logging
60
- throwOnError: false, // Return errors instead of throwing
61
- 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
62
59
  });
63
60
  ```
64
61
 
@@ -68,11 +65,11 @@ const client = new FortytwoIntraClient('your_client_id', 'your_client_secret', {
68
65
 
69
66
  ```typescript
70
67
  // Get all campuses
71
- const campuses = await client.get('campus');
68
+ const campuses = await client.get("campus");
72
69
  console.log(`Found ${campuses.length} campuses`);
73
70
 
74
71
  // Get a specific campus
75
- const lyonCampus = await client.get('campus/9');
72
+ const lyonCampus = await client.get("campus/9");
76
73
  console.log(`${lyonCampus.name}: ${lyonCampus.users_count} users`);
77
74
  ```
78
75
 
@@ -80,11 +77,11 @@ console.log(`${lyonCampus.name}: ${lyonCampus.users_count} users`);
80
77
 
81
78
  ```typescript
82
79
  // Create a team for a project
83
- const team = await client.post('teams', {
84
- body: {
85
- name: 'awesome-team',
86
- project_id: 42
87
- }
80
+ const team = await client.post("teams", {
81
+ body: {
82
+ name: "awesome-team",
83
+ project_id: 42,
84
+ },
88
85
  });
89
86
  ```
90
87
 
@@ -92,12 +89,12 @@ const team = await client.post('teams', {
92
89
 
93
90
  ```typescript
94
91
  // Update a team name
95
- const updatedTeam = await client.patch('teams/123', {
96
- body: {
92
+ const updatedTeam = await client.patch("teams/123", {
93
+ body: {
97
94
  team: {
98
- name: "updated-team-name"
99
- }
100
- }
95
+ name: "updated-team-name",
96
+ },
97
+ },
101
98
  });
102
99
  ```
103
100
 
@@ -105,7 +102,7 @@ const updatedTeam = await client.patch('teams/123', {
105
102
 
106
103
  ```typescript
107
104
  // Delete a team
108
- await client.delete('teams/123');
105
+ await client.delete("teams/123");
109
106
  ```
110
107
 
111
108
  ### Get All Pages (Pagination Helper)
@@ -114,12 +111,12 @@ The `getAll` method automatically handles pagination and returns all results:
114
111
 
115
112
  ```typescript
116
113
  // Get all users from 42Lyon (handles pagination automatically)
117
- const lyonUsers = await client.getAll('campus/9/users');
114
+ const lyonUsers = await client.getAll("campus/9/users");
118
115
  console.log(`42Lyon campus has ${lyonUsers.length} users`);
119
116
 
120
117
  // Get all projects with custom page size
121
- const allProjects = await client.getAll('projects', {
122
- perPage: 50 // 50 items per page (default: 100)
118
+ const allProjects = await client.getAll("projects", {
119
+ perPage: 50, // 50 items per page (default: 100)
123
120
  });
124
121
  ```
125
122
 
@@ -129,16 +126,16 @@ const allProjects = await client.getAll('projects', {
129
126
 
130
127
  ```typescript
131
128
  // 1. Get OAuth authorization URL
132
- const authUrl = client.getOAuthUrl('http://localhost:3000/callback');
129
+ const { url: authUrl } = client.getOAuthUrl();
133
130
  // Redirect user to authUrl
134
131
 
135
132
  // 2. Exchange authorization code for tokens
136
- const tokens = await client.exchangeOAuthCode(code, 'http://localhost:3000/callback');
133
+ const tokens = await client.exchangeOAuthCode(code, "http://localhost:3000/callback");
137
134
  // tokens contains: access_token, refresh_token, expires_in, etc.
138
135
 
139
136
  // 3. Use tokens for authenticated requests (USER-SPECIFIC DATA)
140
- const currentUser = await client.get('users/me', {
141
- token: tokens
137
+ const currentUser = await client.get("users/me", {
138
+ token: tokens,
142
139
  });
143
140
  console.log(`Welcome ${currentUser.displayname}!`);
144
141
  ```
@@ -147,12 +144,14 @@ console.log(`Welcome ${currentUser.displayname}!`);
147
144
 
148
145
  ```typescript
149
146
  // Set redirect URI in constructor
150
- const client = new IntraApiProxy('client_id', 'client_secret', {
151
- redirect_uri: 'http://localhost:3000/callback'
147
+ const client = new FortytwoIntraClient("client_id", "client_secret", {
148
+ redirect_uri: "http://localhost:3000/callback",
152
149
  });
153
150
 
154
151
  // Or pass it to getOAuthUrl
155
- const authUrl = client.getOAuthUrl('http://localhost:3000/callback');
152
+ const { url: authUrl } = client.getOAuthUrl({
153
+ redirect_uri: "http://localhost:3000/callback",
154
+ });
156
155
  ```
157
156
 
158
157
  ## Utility Methods
@@ -161,13 +160,13 @@ const authUrl = client.getOAuthUrl('http://localhost:3000/callback');
161
160
 
162
161
  ```typescript
163
162
  // Create a URL object for the API
164
- const userUrl = client.URL('users/jdoe');
163
+ const userUrl = client.URL("users/jdoe");
165
164
  console.log(userUrl.toString()); // "https://api.intra.42.fr/v2/users/jdoe"
166
165
 
167
166
  // Build URLs for specific endpoints
168
- const campusUrl = client.URL('campus/1/users');
169
- const projectUrl = client.URL('projects/libft');
170
- 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");
171
170
  ```
172
171
 
173
172
  ## Error Handling
@@ -176,16 +175,16 @@ The library includes automatic retry logic for rate limiting (429) and authentic
176
175
 
177
176
  ```typescript
178
177
  try {
179
- const user = await client.get('users/jdoe');
180
- console.log(`Found user: ${user.displayname}`);
178
+ const user = await client.get("users/jdoe");
179
+ console.log(`Found user: ${user.displayname}`);
181
180
  } catch (error) {
182
- if (error.status === 404) {
183
- console.error('User not found');
184
- } else if (error.status === 403) {
185
- console.error('Access forbidden - check your token permissions');
186
- } else {
187
- console.error('Request failed after retries:', error.message);
188
- }
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
+ }
189
188
  }
190
189
  ```
191
190
 
@@ -211,60 +210,60 @@ Disable logging by setting `logLine: false` in the configuration. Control error
211
210
  This library is written in TypeScript and includes full type definitions:
212
211
 
213
212
  ```typescript
214
- import { FortytwoIntraClient } from '@ibertran/fortytwo-intra-client';
213
+ import { FortytwoIntraClient } from "@ibertran/fortytwo-intra-client";
215
214
 
216
215
  // Types are automatically inferred
217
- const client = new FortytwoIntraClient('client_id', 'client_secret');
216
+ const client = new FortytwoIntraClient("client_id", "client_secret");
218
217
  ```
219
218
 
220
219
  ### OAuth Flow Example
221
220
 
222
221
  ```typescript
223
- import express from 'express';
224
- import { FortytwoIntraClient } from '@ibertran/fortytwo-intra-client';
222
+ import express from "express";
223
+ import { FortytwoIntraClient } from "@ibertran/fortytwo-intra-client";
225
224
 
226
225
  const app = express();
227
226
  const client = new FortytwoIntraClient(
228
- process.env.INTRA_CLIENT_ID!,
229
- process.env.INTRA_CLIENT_SECRET!,
230
- {
231
- redirect_uri: 'http://localhost:3000/callback',
232
- oauth_scope: ['public']
233
- }
227
+ process.env.INTRA_CLIENT_ID!,
228
+ process.env.INTRA_CLIENT_SECRET!,
229
+ {
230
+ redirect_uri: "http://localhost:3000/callback",
231
+ scopes: ["public"],
232
+ },
234
233
  );
235
234
 
236
235
  // Redirect to 42 OAuth
237
- app.get('/login', (req, res) => {
238
- const authUrl = client.getOAuthUrl();
239
- res.redirect(authUrl);
236
+ app.get("/login", (req, res) => {
237
+ const { url: authUrl } = client.getOAuthUrl();
238
+ res.redirect(authUrl);
240
239
  });
241
240
 
242
241
  // Handle OAuth callback
243
- app.get('/callback', async (req, res) => {
244
- try {
245
- const { code } = req.query;
246
- const token = await client.exchangeOAuthCode(code as string);
247
-
248
- // Use tokens to make authenticated requests (accessing personal data)
249
- const userData = await client.get('users/me', { token: token });
250
-
251
- res.json({
252
- user: {
253
- login: userData.login,
254
- displayname: userData.displayname,
255
- email: userData.email,
256
- level: userData.cursus_users[0]?.level || 0,
257
- wallet: userData.wallet,
258
- campus: userData.campus[0]?.name
259
- },
260
- });
261
- } catch (error) {
262
- res.status(500).json({ error: error.message });
263
- }
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
+ }
264
263
  });
265
264
 
266
265
  app.listen(3000, () => {
267
- console.log('Server running on http://localhost:3000');
268
- 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");
269
268
  });
270
269
  ```
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { inputOptions } from "./types";
2
+ import { FortytwoIntraClientError, isFortytwoIntraClientError } from "./lib/errors";
3
+ import { z } from "zod";
2
4
  export interface FortytwoIntraClientConf {
3
5
  redirect_uri: string | null;
4
6
  base_url: string;
@@ -11,7 +13,6 @@ export interface FortytwoIntraClientConf {
11
13
  maxRetry: number;
12
14
  logLine: boolean;
13
15
  errLogBody: boolean;
14
- throwOnError: boolean;
15
16
  }
16
17
  export declare class FortytwoIntraClient {
17
18
  private client_id;
@@ -29,7 +30,6 @@ export declare class FortytwoIntraClient {
29
30
  private maxRetry;
30
31
  private logLine;
31
32
  private errLogBody;
32
- private throwOnError;
33
33
  private access_token;
34
34
  constructor(client_id: string, client_secret: string, conf: Partial<FortytwoIntraClientConf>);
35
35
  private generateToken;
@@ -38,19 +38,38 @@ export declare class FortytwoIntraClient {
38
38
  private logSuccess;
39
39
  private logError;
40
40
  get(endpoint: URL | string, options?: Omit<inputOptions, "body" | "perPage" | "maxPage">): Promise<any>;
41
+ get<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body" | "perPage" | "maxPage"> & {
42
+ schema: S;
43
+ }): Promise<z.infer<S>>;
41
44
  post(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
45
+ post<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPage"> & {
46
+ schema: S;
47
+ }): Promise<z.infer<S>>;
42
48
  put(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
49
+ put<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPage"> & {
50
+ schema: S;
51
+ }): Promise<z.infer<S>>;
43
52
  patch(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
53
+ patch<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPage"> & {
54
+ schema: S;
55
+ }): Promise<z.infer<S>>;
44
56
  delete(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPage">): Promise<any>;
57
+ delete<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPage"> & {
58
+ schema: S;
59
+ }): Promise<z.infer<S>>;
45
60
  getAll(endpoint: URL | string, options?: Omit<inputOptions, "body">): Promise<any>;
61
+ getAll<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body"> & {
62
+ schema: S;
63
+ }): Promise<z.infer<S>>;
46
64
  URL(endpoint: string): URL;
47
65
  getOAuthUrl(options?: {
48
66
  redirect_uri?: string;
49
67
  state?: string;
50
68
  }): {
51
69
  url: string;
52
- state: string;
70
+ state: string | null;
53
71
  };
54
72
  exchangeOAuthCode(code: string, redirect_uri?: string): Promise<any>;
55
73
  tokenInfos(options?: Omit<inputOptions, "body" | "perPage" | "maxPage">): Promise<any>;
56
74
  }
75
+ export { FortytwoIntraClientError, isFortytwoIntraClientError };
package/dist/index.js CHANGED
@@ -1,14 +1,49 @@
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
- exports.FortytwoIntraClient = void 0;
7
- const axios_1 = __importDefault(require("axios"));
39
+ exports.isFortytwoIntraClientError = exports.FortytwoIntraClientError = exports.FortytwoIntraClient = void 0;
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
- const crypto_1 = __importDefault(require("crypto"));
44
+ const errors_1 = require("./lib/errors");
45
+ Object.defineProperty(exports, "FortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.FortytwoIntraClientError; } });
46
+ Object.defineProperty(exports, "isFortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.isFortytwoIntraClientError; } });
12
47
  const defaultConf = {
13
48
  redirect_uri: null,
14
49
  base_url: "https://api.intra.42.fr/v2/",
@@ -21,7 +56,6 @@ const defaultConf = {
21
56
  maxRetry: 5,
22
57
  logLine: true,
23
58
  errLogBody: true,
24
- throwOnError: true,
25
59
  };
26
60
  class FortytwoIntraClient {
27
61
  client_id;
@@ -39,7 +73,6 @@ class FortytwoIntraClient {
39
73
  maxRetry;
40
74
  logLine;
41
75
  errLogBody;
42
- throwOnError;
43
76
  access_token;
44
77
  constructor(client_id, client_secret, conf) {
45
78
  this.client_id = client_id;
@@ -63,7 +96,6 @@ class FortytwoIntraClient {
63
96
  this.maxRetry = config.maxRetry;
64
97
  this.logLine = config.logLine;
65
98
  this.errLogBody = config.errLogBody;
66
- this.throwOnError = config.throwOnError;
67
99
  this.access_token = null;
68
100
  }
69
101
  async generateToken() {
@@ -112,7 +144,7 @@ class FortytwoIntraClient {
112
144
  return res;
113
145
  }
114
146
  catch (err) {
115
- if (err.isAxiosError) {
147
+ if ((0, axios_1.isAxiosError)(err)) {
116
148
  this.logError(err, options);
117
149
  const { attempt, maxRetry } = options;
118
150
  const status = err.response?.status;
@@ -124,8 +156,7 @@ class FortytwoIntraClient {
124
156
  return this.reqHandler(url, options);
125
157
  }
126
158
  else {
127
- if (this.throwOnError)
128
- throw err;
159
+ throw new errors_1.FortytwoIntraClientError(err);
129
160
  }
130
161
  }
131
162
  else {
@@ -152,7 +183,6 @@ class FortytwoIntraClient {
152
183
  console.log(line, logBody ? body : "");
153
184
  }
154
185
  }
155
- // Public methods
156
186
  async get(endpoint, options = {}) {
157
187
  if (endpoint instanceof URL === false) {
158
188
  endpoint = new URL(endpoint, this.base_url);
@@ -165,7 +195,10 @@ class FortytwoIntraClient {
165
195
  errLogBody: this.errLogBody,
166
196
  ...options,
167
197
  });
168
- return res?.data;
198
+ if (options.schema) {
199
+ return options.schema.parse(res.data);
200
+ }
201
+ return res.data;
169
202
  }
170
203
  async post(endpoint, options = {}) {
171
204
  if (endpoint instanceof URL === false) {
@@ -179,7 +212,10 @@ class FortytwoIntraClient {
179
212
  errLogBody: this.errLogBody,
180
213
  ...options,
181
214
  });
182
- return res?.data;
215
+ if (options.schema) {
216
+ return options.schema.parse(res.data);
217
+ }
218
+ return res.data;
183
219
  }
184
220
  async put(endpoint, options = {}) {
185
221
  if (endpoint instanceof URL === false) {
@@ -193,7 +229,10 @@ class FortytwoIntraClient {
193
229
  errLogBody: this.errLogBody,
194
230
  ...options,
195
231
  });
196
- return res?.data;
232
+ if (options.schema) {
233
+ return options.schema.parse(res.data);
234
+ }
235
+ return res.data;
197
236
  }
198
237
  async patch(endpoint, options = {}) {
199
238
  if (endpoint instanceof URL === false) {
@@ -207,7 +246,10 @@ class FortytwoIntraClient {
207
246
  errLogBody: this.errLogBody,
208
247
  ...options,
209
248
  });
210
- return res?.data;
249
+ if (options.schema) {
250
+ return options.schema.parse(res.data);
251
+ }
252
+ return res.data;
211
253
  }
212
254
  async delete(endpoint, options = {}) {
213
255
  if (endpoint instanceof URL === false) {
@@ -221,7 +263,10 @@ class FortytwoIntraClient {
221
263
  errLogBody: this.errLogBody,
222
264
  ...options,
223
265
  });
224
- return res?.data;
266
+ if (options.schema) {
267
+ return options.schema.parse(res.data);
268
+ }
269
+ return res.data;
225
270
  }
226
271
  async getAll(endpoint, options = {}) {
227
272
  if (endpoint instanceof URL === false) {
@@ -229,7 +274,7 @@ class FortytwoIntraClient {
229
274
  }
230
275
  const perPage = options.perPage || 100;
231
276
  let url = new URL(endpoint);
232
- const initialRes = await this.reqHandler(url, {
277
+ const firstPage = await this.reqHandler(url, {
233
278
  method: "GET",
234
279
  attempt: 0,
235
280
  currpage: 1,
@@ -244,50 +289,62 @@ class FortytwoIntraClient {
244
289
  per_page: perPage,
245
290
  }
246
291
  });
292
+ if (!Array.isArray(firstPage.data)) {
293
+ if (options.schema) {
294
+ return options.schema.parse(firstPage.data);
295
+ }
296
+ return firstPage.data;
297
+ }
247
298
  let lastPage;
248
299
  try {
249
- lastPage = Math.min((0, pagination_1.getLastPage)(initialRes.headers["link"]), options.maxPages || Infinity);
300
+ lastPage = Math.min((0, pagination_1.getLastPage)(firstPage.headers["link"]), options.maxPages || Infinity);
250
301
  }
251
302
  catch (err) {
252
- return initialRes?.data;
303
+ if (options.schema) {
304
+ return options.schema.parse(firstPage.data);
305
+ }
306
+ return firstPage.data;
253
307
  }
254
- const promises = [];
255
- for (let i = 2; i <= lastPage; i++) {
256
- url = new URL(endpoint);
257
- promises.push(this.reqHandler(url, {
258
- method: "GET",
259
- attempt: 0,
260
- currpage: i,
261
- lastPage: lastPage,
262
- maxRetry: this.maxRetry,
263
- logLine: this.logLine,
264
- errLogBody: this.errLogBody,
265
- ...options,
266
- query: {
267
- ...options.query,
268
- page: i,
269
- per_page: perPage,
270
- }
271
- }));
308
+ const promises = Array.from({ length: lastPage - 1 }, (_, i) => i + 2)
309
+ .map(pageNumber => this.reqHandler(url, {
310
+ method: "GET",
311
+ attempt: 0,
312
+ currpage: pageNumber,
313
+ lastPage: lastPage,
314
+ maxRetry: this.maxRetry,
315
+ logLine: this.logLine,
316
+ errLogBody: this.errLogBody,
317
+ ...options,
318
+ query: {
319
+ ...options.query,
320
+ page: pageNumber,
321
+ per_page: perPage,
322
+ }
323
+ }));
324
+ const otherPages = await Promise.all(promises);
325
+ const allData = [...firstPage.data, ...otherPages.flatMap((res) => res.data)];
326
+ if (options.schema) {
327
+ return options.schema.parse(allData);
272
328
  }
273
- return Promise.all(promises).then((values) => {
274
- return initialRes?.data.concat(...values.map((value) => value.data));
275
- });
329
+ return allData;
276
330
  }
277
331
  URL(endpoint) {
278
332
  return new URL(endpoint, this.base_url);
279
333
  }
280
334
  getOAuthUrl(options = {}) {
281
335
  const redirectUri = options.redirect_uri || this.redirect_uri;
282
- if (!redirectUri)
336
+ if (!redirectUri) {
283
337
  throw new Error(`Missing redirect_uri parameter`);
284
- const state = options.state || crypto_1.default.randomBytes(32).toString('base64url');
338
+ }
285
339
  const url = new URL(this.oauth_url);
286
340
  url.searchParams.set("client_id", this.client_id);
287
341
  url.searchParams.set("redirect_uri", redirectUri);
288
342
  url.searchParams.set("response_type", "code");
289
343
  url.searchParams.set("scope", this.scopes.join(" "));
290
- url.searchParams.set("state", state);
344
+ const state = options.state ?? null;
345
+ if (state !== null) {
346
+ url.searchParams.set("state", state);
347
+ }
291
348
  return { url: url.toString(), state };
292
349
  }
293
350
  async exchangeOAuthCode(code, redirect_uri) {
@@ -298,7 +355,7 @@ class FortytwoIntraClient {
298
355
  redirect_uri: redirect_uri ? redirect_uri : this.redirect_uri,
299
356
  code: code,
300
357
  });
301
- return res?.data;
358
+ return res.data;
302
359
  }
303
360
  async tokenInfos(options = {}) {
304
361
  return this.get(this.token_info_url, options);
@@ -0,0 +1,8 @@
1
+ import { AxiosError } from "axios";
2
+ export declare class FortytwoIntraClientError extends Error {
3
+ statusCode: number;
4
+ statusText: string;
5
+ data: unknown;
6
+ constructor(error: AxiosError);
7
+ }
8
+ export declare function isFortytwoIntraClientError(error: unknown): error is FortytwoIntraClientError;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FortytwoIntraClientError = void 0;
4
+ exports.isFortytwoIntraClientError = isFortytwoIntraClientError;
5
+ class FortytwoIntraClientError extends Error {
6
+ statusCode;
7
+ statusText;
8
+ data;
9
+ constructor(error) {
10
+ const method = error.config?.method?.toUpperCase() ?? null;
11
+ const route = error.config?.url ?? null;
12
+ const statusCode = error.response?.status ?? NaN;
13
+ const statusText = error.response?.statusText ?? "Unknown";
14
+ const message = method && route
15
+ ? `${method} ${route} - HTTP ${statusCode} ${statusText}`
16
+ : error.message;
17
+ super(message);
18
+ this.name = "FortytwoIntraClientError";
19
+ this.statusCode = statusCode;
20
+ this.statusText = statusText;
21
+ this.data = error.response?.data;
22
+ }
23
+ }
24
+ exports.FortytwoIntraClientError = FortytwoIntraClientError;
25
+ function isFortytwoIntraClientError(error) {
26
+ return error instanceof FortytwoIntraClientError;
27
+ }
package/dist/lib/logs.js CHANGED
@@ -5,7 +5,6 @@ exports.getErrorLogLine = getErrorLogLine;
5
5
  const util_1 = require("util");
6
6
  const green = "\x1b[42m";
7
7
  const red = "\x1b[41m";
8
- const yellow = "\x1b[43m";
9
8
  const reset = "\x1b[0m";
10
9
  function formatQueryParams(params) {
11
10
  const { per_page, page, ...filteredParams } = params;
package/package.json CHANGED
@@ -1,26 +1,31 @@
1
1
  {
2
- "name": "@ibertran/fortytwo-intra-client",
3
- "version": "3.1.4",
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.0.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
- }