ofsc-utility 1.0.47 → 1.0.48

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/CHANGELOG.md CHANGED
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
10
10
  - version 1.0.46
11
11
 
12
12
  ### Added
13
+ - Update version and add resource management APIs
14
+ - version 1.0.47 and add resource APIs
13
15
  - release v1.0.44 with getResource function and updated keywords
14
16
  - version 1.0.43 with built-in resilience and exponential backoff
15
17
  - Add -c flag to skip CHANGELOG updates and auto-update logic
@@ -6,3 +6,4 @@ export declare function getworkSkillsOfResource(clientId: string, clientSecret:
6
6
  data: any;
7
7
  }>;
8
8
  export declare function getResourcebyId(resourceId: string, clientId: string, clientSecret: string, instanceUrl: string, initialToken?: string): Promise<ResourceResponse>;
9
+ export declare function updateResourcebyId(resourceId: string, clientId: string, clientSecret: string, instanceUrl: string, initialToken: string | undefined, payload: {}): Promise<ResourceResponse>;
@@ -40,6 +40,7 @@ exports.downloadAllResourcesCSV = downloadAllResourcesCSV;
40
40
  exports.AllResources = AllResources;
41
41
  exports.getworkSkillsOfResource = getworkSkillsOfResource;
42
42
  exports.getResourcebyId = getResourcebyId;
43
+ exports.updateResourcebyId = updateResourcebyId;
43
44
  const fs = __importStar(require("fs"));
44
45
  const node_fetch_1 = __importDefault(require("node-fetch"));
45
46
  const index_1 = require("../oauthTokenService/index");
@@ -149,3 +150,11 @@ async function getResourcebyId(resourceId, clientId, clientSecret, instanceUrl,
149
150
  };
150
151
  return fetchResources(0, initialToken);
151
152
  }
153
+ async function updateResourcebyId(resourceId, clientId, clientSecret, instanceUrl, initialToken = "", payload) {
154
+ const fetchResources = async (offset, token) => {
155
+ const url = `https://${instanceUrl}.fs.ocs.oraclecloud.com/rest/ofscCore/v1/resources/${encodeURIComponent(resourceId)}`;
156
+ const res = await (0, utilities_1.fetchPatchWithRetry)(url, clientId, clientSecret, instanceUrl, token, payload);
157
+ return res.data;
158
+ };
159
+ return fetchResources(0, initialToken);
160
+ }
@@ -13,6 +13,14 @@ export declare const fetchWithRetry: (url: string, clientId: string, clientSecre
13
13
  data: any;
14
14
  token: string;
15
15
  }>;
16
+ export declare const fetchPatchWithRetry: (url: string, clientId: string, clientSecret: string, instanceUrl: string, token: string, body: any, retries?: number, baseDelay?: number) => Promise<{
17
+ data: any;
18
+ token: string;
19
+ }>;
20
+ export declare const fetchPostWithRetry: (url: string, clientId: string, clientSecret: string, instanceUrl: string, token: string, body: any, retries?: number, baseDelay?: number) => Promise<{
21
+ data: any;
22
+ token: string;
23
+ }>;
16
24
  export declare function saveCsv<T extends Record<string, any>>(rows: T[], filePath: string): void;
17
25
  export declare function xmlNodeToObjects(xmlString: string, parentNodeName: string): Record<string, string>[];
18
26
  type SheetRow = Record<string, unknown>;
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.fetchWithRetry = void 0;
39
+ exports.fetchPostWithRetry = exports.fetchPatchWithRetry = exports.fetchWithRetry = void 0;
40
40
  exports.saveCsv = saveCsv;
41
41
  exports.xmlNodeToObjects = xmlNodeToObjects;
42
42
  exports.createExcelFile = createExcelFile;
@@ -122,6 +122,138 @@ const fetchWithRetry = async (url, clientId, clientSecret, instanceUrl, token, r
122
122
  }
123
123
  };
124
124
  exports.fetchWithRetry = fetchWithRetry;
125
+ const fetchPatchWithRetry = async (url, clientId, clientSecret, instanceUrl, token, body, retries = 5, baseDelay = 500) => {
126
+ const doFetch = async (bearer) => {
127
+ return fetch(url, {
128
+ method: "PATCH",
129
+ headers: {
130
+ Authorization: `Bearer ${bearer}`,
131
+ Accept: "application/json",
132
+ "Content-Type": "application/json"
133
+ },
134
+ body: JSON.stringify(body)
135
+ });
136
+ };
137
+ console.log(`Patching ${url}`);
138
+ let currentToken = token;
139
+ let tokenRefreshed = false;
140
+ let remainingRetries = retries;
141
+ let delay = baseDelay;
142
+ while (true) {
143
+ let res = await doFetch(currentToken);
144
+ if (res.status === 401 && !tokenRefreshed) {
145
+ console.warn("Token expired — renewing token…");
146
+ currentToken = await (0, oauthTokenService_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
147
+ tokenRefreshed = true;
148
+ continue;
149
+ }
150
+ const responseText = await res.text();
151
+ const isNoRouteToHost = res.status === 400 &&
152
+ responseText.includes("NoRouteToHostException");
153
+ const isRetryableStatus = res.status === 400 ||
154
+ res.status === 429 ||
155
+ res.status === 502 ||
156
+ res.status === 503 ||
157
+ res.status === 504;
158
+ if ((isRetryableStatus || isNoRouteToHost) &&
159
+ remainingRetries > 0) {
160
+ const retryAfter = res.headers.get("Retry-After");
161
+ let retryDelay = delay;
162
+ if (retryAfter) {
163
+ const retryAfterSeconds = Number(retryAfter);
164
+ if (!Number.isNaN(retryAfterSeconds)) {
165
+ retryDelay = retryAfterSeconds * 1000;
166
+ }
167
+ }
168
+ console.warn(`Retrying in ${retryDelay}ms... (${remainingRetries} retries left)`);
169
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
170
+ remainingRetries--;
171
+ delay *= 2;
172
+ continue;
173
+ }
174
+ if (!res.ok) {
175
+ throw new Error(`Request failed: ${res.status} ${res.statusText}\n${responseText}`);
176
+ }
177
+ let data;
178
+ try {
179
+ data = JSON.parse(responseText);
180
+ }
181
+ catch {
182
+ throw new Error(`Invalid JSON response from ${url}\n${responseText}`);
183
+ }
184
+ return {
185
+ data,
186
+ token: currentToken
187
+ };
188
+ }
189
+ };
190
+ exports.fetchPatchWithRetry = fetchPatchWithRetry;
191
+ const fetchPostWithRetry = async (url, clientId, clientSecret, instanceUrl, token, body, retries = 5, baseDelay = 500) => {
192
+ const doFetch = async (bearer) => {
193
+ return fetch(url, {
194
+ method: "POST",
195
+ headers: {
196
+ Authorization: `Bearer ${bearer}`,
197
+ Accept: "application/json",
198
+ "Content-Type": "application/json"
199
+ },
200
+ body: JSON.stringify(body)
201
+ });
202
+ };
203
+ console.log(`Posting ${url}`);
204
+ let currentToken = token;
205
+ let tokenRefreshed = false;
206
+ let remainingRetries = retries;
207
+ let delay = baseDelay;
208
+ while (true) {
209
+ let res = await doFetch(currentToken);
210
+ if (res.status === 401 && !tokenRefreshed) {
211
+ console.warn("Token expired — renewing token…");
212
+ currentToken = await (0, oauthTokenService_1.getOAuthToken)(clientId, clientSecret, instanceUrl);
213
+ tokenRefreshed = true;
214
+ continue;
215
+ }
216
+ const responseText = await res.text();
217
+ const isNoRouteToHost = res.status === 400 &&
218
+ responseText.includes("NoRouteToHostException");
219
+ const isRetryableStatus = res.status === 400 ||
220
+ res.status === 429 ||
221
+ res.status === 502 ||
222
+ res.status === 503 ||
223
+ res.status === 504;
224
+ if ((isRetryableStatus || isNoRouteToHost) &&
225
+ remainingRetries > 0) {
226
+ const retryAfter = res.headers.get("Retry-After");
227
+ let retryDelay = delay;
228
+ if (retryAfter) {
229
+ const retryAfterSeconds = Number(retryAfter);
230
+ if (!Number.isNaN(retryAfterSeconds)) {
231
+ retryDelay = retryAfterSeconds * 1000;
232
+ }
233
+ }
234
+ console.warn(`Retrying in ${retryDelay}ms... (${remainingRetries} retries left)`);
235
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
236
+ remainingRetries--;
237
+ delay *= 2;
238
+ continue;
239
+ }
240
+ if (!res.ok) {
241
+ throw new Error(`Request failed: ${res.status} ${res.statusText}\n${responseText}`);
242
+ }
243
+ let data;
244
+ try {
245
+ data = JSON.parse(responseText);
246
+ }
247
+ catch {
248
+ throw new Error(`Invalid JSON response from ${url}\n${responseText}`);
249
+ }
250
+ return {
251
+ data,
252
+ token: currentToken
253
+ };
254
+ }
255
+ };
256
+ exports.fetchPostWithRetry = fetchPostWithRetry;
125
257
  const fs_1 = __importDefault(require("fs"));
126
258
  const path_1 = __importDefault(require("path"));
127
259
  function saveCsv(rows, filePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofsc-utility",
3
- "version": "1.0.47",
3
+ "version": "1.0.48",
4
4
  "description": "TypeScript helpers for Oracle Field Service Cloud (OFSC): events, resources, inventories, metadata and CSV/Excel utilities.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -172,6 +172,210 @@ await ofs.downloadAllInactiveUsersCSV(
172
172
  );
173
173
  ```
174
174
 
175
+ ### Resource APIs
176
+
177
+ The resource helpers let you export, paginate, inspect, and update OFSC resources.
178
+
179
+ #### `downloadAllResourcesCSV`
180
+
181
+ Exports every resource in the OFSC resource collection to a local `./resources.csv` file.
182
+
183
+ ```js
184
+ await ofs.downloadAllResourcesCSV(
185
+ process.env.CLIENT_ID,
186
+ process.env.CLIENT_SECRET,
187
+ process.env.INSTANCE_NAME,
188
+ );
189
+ ```
190
+
191
+ What it does:
192
+
193
+ - Calls the OFSC `/resources` endpoint in pages of 100 records.
194
+ - Aggregates all returned items across all pages.
195
+ - Builds a union of all field names from the API payloads.
196
+ - Excludes nested relationship fields such as `links`, `inventories`, `users`,
197
+ `workZones`, `workSkills`, and `workSchedules` from the header set.
198
+ - Writes the result to `./resources.csv` in the current working directory.
199
+ - Serializes arrays under the `keys` field as pipe-delimited strings and JSON-serializes object values for CSV compatibility.
200
+
201
+ Signature:
202
+
203
+ ```ts
204
+ downloadAllResourcesCSV(
205
+ clientId: string,
206
+ clientSecret: string,
207
+ instanceUrl: string,
208
+ ): Promise<void>
209
+ ```
210
+
211
+ #### `AllResources`
212
+
213
+ Fetches all resources across all pages and returns them as a single flattened array.
214
+
215
+ ```js
216
+ const resources = await ofs.AllResources(
217
+ process.env.CLIENT_ID,
218
+ process.env.CLIENT_SECRET,
219
+ process.env.INSTANCE_NAME,
220
+ );
221
+
222
+ console.log(`Found ${resources.length} resources`);
223
+ ```
224
+
225
+ This helper is useful when you need to iterate through every resource object in memory, for analytics, reporting, or bulk transformations.
226
+
227
+ Signature:
228
+
229
+ ```ts
230
+ AllResources(
231
+ clientId: string,
232
+ clientSecret: string,
233
+ instanceUrl: string,
234
+ initialToken?: string,
235
+ ): Promise<ResourceResponse[]>
236
+ ```
237
+
238
+ Notes:
239
+
240
+ - It paginates automatically until all results are fetched.
241
+ - It returns the flattened `items` collection from every page rather than the raw API wrapper object.
242
+ - The optional `initialToken` parameter can be supplied if you already have a valid bearer token available and want to reuse it.
243
+
244
+ #### `getworkSkillsOfResource`
245
+
246
+ Retrieves all work skills assigned to a specific resource.
247
+
248
+ ```js
249
+ const result = await ofs.getworkSkillsOfResource(
250
+ process.env.CLIENT_ID,
251
+ process.env.CLIENT_SECRET,
252
+ process.env.INSTANCE_NAME,
253
+ 12345,
254
+ );
255
+
256
+ console.log(result.data);
257
+ console.log(result.token);
258
+ ```
259
+
260
+
261
+ Signature:
262
+
263
+ ```ts
264
+ getworkSkillsOfResource(
265
+ clientId: string,
266
+ clientSecret: string,
267
+ instanceUrl: string,
268
+ resourceId: number,
269
+ token?: string,
270
+ ): Promise<{ token: string; data: any }>
271
+ ```
272
+
273
+ Return shape:
274
+
275
+ ```ts
276
+ {
277
+ token: string,
278
+ data: any
279
+ }
280
+ ```
281
+
282
+ The `data` value is the `items` array returned by OFSC for the resource's work skills.
283
+
284
+ #### `getResourcebyId`
285
+
286
+ Fetches a single resource by its ID.
287
+
288
+ ```js
289
+ const resource = await ofs.getResourcebyId(
290
+ "resource-123",
291
+ process.env.CLIENT_ID,
292
+ process.env.CLIENT_SECRET,
293
+ process.env.INSTANCE_NAME,
294
+ );
295
+
296
+ console.log(resource);
297
+ ```
298
+
299
+
300
+
301
+ Signature:
302
+
303
+ ```ts
304
+ getResourcebyId(
305
+ resourceId: string,
306
+ clientId: string,
307
+ clientSecret: string,
308
+ instanceUrl: string,
309
+ initialToken?: string,
310
+ ): Promise<ResourceResponse>
311
+ ```
312
+
313
+ The response is the raw OFSC resource payload returned by the API, so you can inspect its `items`, `totalResults`, and related metadata with the same structure returned by OFSC.
314
+
315
+ #### `updateResourcebyId`
316
+
317
+ Updates a resource record by ID using a PATCH request.
318
+
319
+ ```js
320
+ const payload = {
321
+ name: "Updated Resource Name",
322
+ status: "ACTIVE",
323
+ };
324
+
325
+ const response = await ofs.updateResourcebyId(
326
+ "resource-123",
327
+ process.env.CLIENT_ID,
328
+ process.env.CLIENT_SECRET,
329
+ process.env.INSTANCE_NAME,
330
+ "",
331
+ payload,
332
+ );
333
+
334
+ console.log(response);
335
+ ```
336
+
337
+ Signature:
338
+
339
+ ```ts
340
+ updateResourcebyId(
341
+ resourceId: string,
342
+ clientId: string,
343
+ clientSecret: string,
344
+ instanceUrl: string,
345
+ initialToken?: string,
346
+ payload: object,
347
+ ): Promise<ResourceResponse>
348
+ ```
349
+
350
+ Notes:
351
+
352
+ - The payload is sent as a PATCH body to the OFSC resource update endpoint.
353
+ - It uses the same retry-wrapper logic as the other OFSC API methods so transient gateway and 5xx failures are retried automatically.
354
+ - The returned value is the API response payload from OFSC.
355
+
356
+ #### Example: working with a resource and its work skills
357
+
358
+ ```js
359
+ const resourceId = "resource-123";
360
+
361
+ const resource = await ofs.getResourcebyId(
362
+ resourceId,
363
+ process.env.CLIENT_ID,
364
+ process.env.CLIENT_SECRET,
365
+ process.env.INSTANCE_NAME,
366
+ );
367
+
368
+ const workSkills = await ofs.getworkSkillsOfResource(
369
+ process.env.CLIENT_ID,
370
+ process.env.CLIENT_SECRET,
371
+ process.env.INSTANCE_NAME,
372
+ Number(resourceId),
373
+ );
374
+
375
+ console.log("Resource:", resource);
376
+ console.log("Work skills:", workSkills.data);
377
+ ```
378
+
175
379
  #### Download inactive users
176
380
 
177
381
  `downloadAllInactiveUsersCSV` downloads all users from the OFSC Users API, filters
@@ -335,6 +539,96 @@ node scripts/test-download-inactive-users-data.js
335
539
 
336
540
  ### Resource related methods
337
541
 
542
+ #### Get all resources
543
+
544
+ `AllResources` retrieves all resources from the OFSC Resources API. Results are
545
+ fetched in pages of 100 records until every resource has been returned.
546
+
547
+ ```js
548
+ const resources = await ofs.AllResources(
549
+ process.env.CLIENT_ID,
550
+ process.env.CLIENT_SECRET,
551
+ process.env.INSTANCE_NAME,
552
+ );
553
+
554
+ console.log(`Loaded ${resources.length} resources`);
555
+ ```
556
+
557
+ **Function signature**
558
+
559
+ ```ts
560
+ AllResources(
561
+ clientId: string,
562
+ clientSecret: string,
563
+ instanceUrl: string,
564
+ initialToken?: string,
565
+ ): Promise<ResourceResponse[]>
566
+ ```
567
+
568
+ `initialToken` is optional. The helper refreshes the token as needed while
569
+ requesting subsequent pages.
570
+
571
+ #### Get a resource by ID
572
+
573
+ `getResourcebyId` fetches a single resource by its resource ID and returns the
574
+ OFSC response envelope.
575
+
576
+ ```js
577
+ const resource = await ofs.getResourcebyId(
578
+ "5457",
579
+ process.env.CLIENT_ID,
580
+ process.env.CLIENT_SECRET,
581
+ process.env.INSTANCE_NAME,
582
+ );
583
+
584
+ console.log(resource.items);
585
+ ```
586
+
587
+ **Function signature**
588
+
589
+ ```ts
590
+ getResourcebyId(
591
+ resourceId: string,
592
+ clientId: string,
593
+ clientSecret: string,
594
+ instanceUrl: string,
595
+ initialToken?: string,
596
+ ): Promise<ResourceResponse>
597
+ ```
598
+
599
+ The returned response contains `items`, `offset`, `limit`, and `totalResults`.
600
+ `initialToken` is optional.
601
+
602
+ #### Get resource work skills
603
+
604
+ `getworkSkillsOfResource` retrieves the work skills assigned to a resource.
605
+ The result includes the token returned by the request and the skills in `data`.
606
+
607
+ ```js
608
+ const workSkills = await ofs.getworkSkillsOfResource(
609
+ process.env.CLIENT_ID,
610
+ process.env.CLIENT_SECRET,
611
+ process.env.INSTANCE_NAME,
612
+ 5457,
613
+ );
614
+
615
+ console.log(workSkills.data);
616
+ ```
617
+
618
+ **Function signature**
619
+
620
+ ```ts
621
+ getworkSkillsOfResource(
622
+ clientId: string,
623
+ clientSecret: string,
624
+ instanceUrl: string,
625
+ resourceId: number,
626
+ token?: string,
627
+ ): Promise<{ token: string; data: any }>
628
+ ```
629
+
630
+ `token` is optional. `data` contains the work-skill items returned by OFSC.
631
+
338
632
  ```js
339
633
  await ofs.generateAllOnHandInventoryOfAllResourcesCSV(
340
634
  process.env.CLIENT_ID,