From 94d46a47560b9202949f2eae9b0cf0f9ab482808 Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 00:43:54 +0530 Subject: [PATCH 01/18] feat(sapsuccessfactors): add sap successfactors plugin --- packages/corsair/core/constants.ts | 3 + packages/sapsuccessfactors/AGENT.md | 701 +++++++ packages/sapsuccessfactors/api.test.ts | 582 ++++++ packages/sapsuccessfactors/client.ts | 77 + packages/sapsuccessfactors/endpoints/a.ts | 26 + .../endpoints/application.ts | 24 + .../sapsuccessfactors/endpoints/approve.ts | 23 + .../sapsuccessfactors/endpoints/background.ts | 44 + .../endpoints/calibration.ts | 122 ++ .../sapsuccessfactors/endpoints/candidates.ts | 24 + packages/sapsuccessfactors/endpoints/cdp.ts | 39 + .../sapsuccessfactors/endpoints/current.ts | 24 + .../sapsuccessfactors/endpoints/custom.ts | 29 + packages/sapsuccessfactors/endpoints/emp.ts | 84 + .../sapsuccessfactors/endpoints/employee.ts | 44 + .../sapsuccessfactors/endpoints/feedback.ts | 27 + packages/sapsuccessfactors/endpoints/fo.ts | 162 ++ packages/sapsuccessfactors/endpoints/form.ts | 24 + packages/sapsuccessfactors/endpoints/give.ts | 26 + packages/sapsuccessfactors/endpoints/goal.ts | 24 + packages/sapsuccessfactors/endpoints/goals.ts | 29 + packages/sapsuccessfactors/endpoints/index.ts | 178 ++ .../sapsuccessfactors/endpoints/internal.ts | 23 + .../sapsuccessfactors/endpoints/interview.ts | 24 + packages/sapsuccessfactors/endpoints/job.ts | 64 + .../sapsuccessfactors/endpoints/learning.ts | 26 + .../sapsuccessfactors/endpoints/metadata.ts | 23 + .../sapsuccessfactors/endpoints/nomination.ts | 24 + packages/sapsuccessfactors/endpoints/odata.ts | 84 + packages/sapsuccessfactors/endpoints/onb2.ts | 24 + .../sapsuccessfactors/endpoints/onboardee.ts | 26 + .../sapsuccessfactors/endpoints/pending.ts | 27 + packages/sapsuccessfactors/endpoints/per.ts | 69 + .../sapsuccessfactors/endpoints/picklist.ts | 43 + .../sapsuccessfactors/endpoints/position.ts | 23 + packages/sapsuccessfactors/endpoints/query.ts | 47 + .../sapsuccessfactors/endpoints/successor.ts | 26 + .../sapsuccessfactors/endpoints/talent.ts | 24 + .../sapsuccessfactors/endpoints/temporary.ts | 24 + packages/sapsuccessfactors/endpoints/time.ts | 24 + packages/sapsuccessfactors/endpoints/types.ts | 1783 +++++++++++++++++ packages/sapsuccessfactors/endpoints/users.ts | 23 + packages/sapsuccessfactors/endpoints/work.ts | 23 + packages/sapsuccessfactors/error-handlers.ts | 31 + packages/sapsuccessfactors/index.ts | 951 +++++++++ packages/sapsuccessfactors/package.json | 44 + packages/sapsuccessfactors/schema.test.ts | 57 + packages/sapsuccessfactors/schema/database.ts | 7 + packages/sapsuccessfactors/schema/index.ts | 4 + packages/sapsuccessfactors/tsconfig.json | 20 + packages/sapsuccessfactors/tsup.config.ts | 15 + packages/sapsuccessfactors/webhooks/index.ts | 3 + .../webhooks/oauth-tenant-link.ts | 31 + .../webhooks/tenant-matcher.ts | 25 + packages/sapsuccessfactors/webhooks/types.ts | 56 + pnpm-lock.yaml | 24 + 56 files changed, 6038 insertions(+) create mode 100644 packages/sapsuccessfactors/AGENT.md create mode 100644 packages/sapsuccessfactors/api.test.ts create mode 100644 packages/sapsuccessfactors/client.ts create mode 100644 packages/sapsuccessfactors/endpoints/a.ts create mode 100644 packages/sapsuccessfactors/endpoints/application.ts create mode 100644 packages/sapsuccessfactors/endpoints/approve.ts create mode 100644 packages/sapsuccessfactors/endpoints/background.ts create mode 100644 packages/sapsuccessfactors/endpoints/calibration.ts create mode 100644 packages/sapsuccessfactors/endpoints/candidates.ts create mode 100644 packages/sapsuccessfactors/endpoints/cdp.ts create mode 100644 packages/sapsuccessfactors/endpoints/current.ts create mode 100644 packages/sapsuccessfactors/endpoints/custom.ts create mode 100644 packages/sapsuccessfactors/endpoints/emp.ts create mode 100644 packages/sapsuccessfactors/endpoints/employee.ts create mode 100644 packages/sapsuccessfactors/endpoints/feedback.ts create mode 100644 packages/sapsuccessfactors/endpoints/fo.ts create mode 100644 packages/sapsuccessfactors/endpoints/form.ts create mode 100644 packages/sapsuccessfactors/endpoints/give.ts create mode 100644 packages/sapsuccessfactors/endpoints/goal.ts create mode 100644 packages/sapsuccessfactors/endpoints/goals.ts create mode 100644 packages/sapsuccessfactors/endpoints/index.ts create mode 100644 packages/sapsuccessfactors/endpoints/internal.ts create mode 100644 packages/sapsuccessfactors/endpoints/interview.ts create mode 100644 packages/sapsuccessfactors/endpoints/job.ts create mode 100644 packages/sapsuccessfactors/endpoints/learning.ts create mode 100644 packages/sapsuccessfactors/endpoints/metadata.ts create mode 100644 packages/sapsuccessfactors/endpoints/nomination.ts create mode 100644 packages/sapsuccessfactors/endpoints/odata.ts create mode 100644 packages/sapsuccessfactors/endpoints/onb2.ts create mode 100644 packages/sapsuccessfactors/endpoints/onboardee.ts create mode 100644 packages/sapsuccessfactors/endpoints/pending.ts create mode 100644 packages/sapsuccessfactors/endpoints/per.ts create mode 100644 packages/sapsuccessfactors/endpoints/picklist.ts create mode 100644 packages/sapsuccessfactors/endpoints/position.ts create mode 100644 packages/sapsuccessfactors/endpoints/query.ts create mode 100644 packages/sapsuccessfactors/endpoints/successor.ts create mode 100644 packages/sapsuccessfactors/endpoints/talent.ts create mode 100644 packages/sapsuccessfactors/endpoints/temporary.ts create mode 100644 packages/sapsuccessfactors/endpoints/time.ts create mode 100644 packages/sapsuccessfactors/endpoints/types.ts create mode 100644 packages/sapsuccessfactors/endpoints/users.ts create mode 100644 packages/sapsuccessfactors/endpoints/work.ts create mode 100644 packages/sapsuccessfactors/error-handlers.ts create mode 100644 packages/sapsuccessfactors/index.ts create mode 100644 packages/sapsuccessfactors/package.json create mode 100644 packages/sapsuccessfactors/schema.test.ts create mode 100644 packages/sapsuccessfactors/schema/database.ts create mode 100644 packages/sapsuccessfactors/schema/index.ts create mode 100644 packages/sapsuccessfactors/tsconfig.json create mode 100644 packages/sapsuccessfactors/tsup.config.ts create mode 100644 packages/sapsuccessfactors/webhooks/index.ts create mode 100644 packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts create mode 100644 packages/sapsuccessfactors/webhooks/tenant-matcher.ts create mode 100644 packages/sapsuccessfactors/webhooks/types.ts diff --git a/packages/corsair/core/constants.ts b/packages/corsair/core/constants.ts index fbbf973f2..fc72a43ff 100644 --- a/packages/corsair/core/constants.ts +++ b/packages/corsair/core/constants.ts @@ -166,6 +166,7 @@ export const BaseProviders = [ 'resend', 'retailed', 'salesforce', + 'sapsuccessfactors', 'securitytrails', 'sentry', 'serpapi', @@ -361,6 +362,7 @@ export const ProviderDisplayNames = { resend: 'Resend', retailed: 'Retailed', salesforce: 'Salesforce', + sapsuccessfactors: 'SapSuccessfactors', securitytrails: 'SecurityTrails', sentry: 'Sentry', serpapi: 'Serpapi', @@ -563,6 +565,7 @@ export type AllProviders = | 'resend' | 'retailed' | 'salesforce' + | 'sapsuccessfactors' | 'securitytrails' | 'sentry' | 'serpapi' diff --git a/packages/sapsuccessfactors/AGENT.md b/packages/sapsuccessfactors/AGENT.md new file mode 100644 index 000000000..cd011a6ff --- /dev/null +++ b/packages/sapsuccessfactors/AGENT.md @@ -0,0 +1,701 @@ +# SapSuccessfactors Plugin — Agent Completion Guide + +> **Auto-generated from scraped API spec.** The Zod schemas, types, and plugin wiring +> are complete. Your job is to fill in the actual HTTP details and write tests. + +## About this integration + +Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. + +- **Auth mode:** `API_KEY` → mapped to Corsair `api_key` +- **Key field:** `api_key` (API Key / Bearer Token) +- **Total operations:** 64 + +--- + +## Step 1 — Find the docs + +Search for: **"SapSuccessfactors API documentation"** or **"SapSuccessfactors developer docs"** + +You're looking for: +1. The **base API URL** (e.g., `https://api.sapsuccessfactors.com/v1`) +2. The **authentication format** — how the key is passed (header name, query param, Bearer prefix) +3. The **endpoint paths** for each operation below + +--- + +## Step 2 — Fill in `client.ts` + +Open `client.ts` and: + +- [ ] Replace `https://api.TODO_sapsuccessfactors.com` with the real base URL +- [ ] Update the `HEADERS` block to use the correct auth format + +Common patterns to look for in the docs: +``` +Authorization: Bearer {api_key} ← most common +X-Api-Key: {api_key} ← also common +?api_key={api_key} ← query param (add to query object instead) +Authorization: Basic base64(key:) ← for BASIC auth +``` + +--- + +## Step 3 — Fill in each endpoint + +The functions are in `endpoints/{group}.ts`. Each has a `TODO_PATH` and `TODO_METHOD` placeholder. +Replace them with the real path and method from the docs. + +### All operations (64 total) + +| Endpoint | Name | Risk | Description | +|---|---|---|---| +| `approve.approveCalibrationSession` | Approve Calibration Session | `write` | Finalize a calibration session that is In Progress or Approving | +| `calibration.getCalibrationSessionById` | Get Calibration Session By ID | `read` | Get a specific calibration session by session ID | +| `calibration.getCalibrationSessions` | Get Calibration Sessions | `read` | Query all calibration sessions the current user can access | +| `calibration.getCalibrationSubjectById` | Get Calibration Subject By ID | `read` | Query a subject's competency ratings within a calibration session | +| `calibration.getCalibrationSubjectRatings` | Get Calibration Subject Ratings | `read` | Query a subject's ratings/competency ratings/comments by session ID | +| `calibration.updateCalibrationSubjectRatings` | Update Calibration Subject Ratings | `write` | Update a subject's competency ratings in a calibration session | +| `odata.getOdataMetadataCalibSessionService` | Get Calibration Session Metadata | `read` | Get OData metadata / available entity sets for CalSession | +| `odata.getOdataMetadataOnboardingAddl` | Get Onboarding Additional Services Metadata | `read` | Get metadata for Onboarding Additional Services (incl | +| `odata.getOdataMetadataForNominationService` | Get Nomination Service Metadata | `read` | Get OData metadata for the Nomination service | +| `odata.getOdataUserMetadata` | Get User Entity Metadata | `read` | Retrieve OData metadata for the User entity | +| `odata.getOdataMetadataClockInclockOut` | Get Clock In/Out Integration Metadata | `read` | Get OData metadata for the Clock In/Clock Out Integration service | +| `onboardee.createOnboardee` | Create Onboardee | `write` | Create a new onboardee in Onboarding 2 | +| `onb2.getOnb2Process` | Get Onboarding 2.0 Processes | `read` | Retrieve Onboarding 2 | +| `internal.updateInternalUsernameNewHiresAfter` | Update Username Post Hiring | `write` | Update a new hire's internal username after MPH submit, pre day-1 | +| `a.createAFeedbackRequest` | Create a Feedback Request | `write` | Request performance feedback from one employee about another | +| `feedback.getFeedbackRecordsServiceAvailable` | Get Feedback Records | `read` | Query continuous feedback records (OData v4) | +| `pending.getPendingFeedbackRequestsFeedback` | Get Pending Feedback Requests | `read` | Query pending feedback requests | +| `give.giveFeedbackOrRespondToAFeedbackRequest` | Give Feedback or Respond to Feedback Request | `write` | Give feedback or respond to a feedback request (up to 3 Q&A pairs) | +| `metadata.refreshMetadataContFeedbackService` | Refresh Metadata for Continuous Feedback | `write` | Refresh the metadata cache for the Continuous Feedback service | +| `successor.createUpdateSuccessorNomination` | Create or Update Successor Nomination | `write` | Create/update a successor nomination for a position or talent pool | +| `nomination.deleteNominationPositionTalentPool` | Delete Nomination | `destructive` | Remove a nominee from a position or talent pool nomination | +| `talent.getTalentPool` | Get Talent Pool | `read` | Retrieve talent pool records including members and nominations | +| `application.getApplicationInterview` | Get Application Interview | `read` | Retrieve interview info from Interview Central (first 1000 records; filter by applicationId) | +| `interview.getInterviewOverallAssessment` | Get Interview Overall Assessment | `read` | Retrieve overall interview ratings, recommendations, and comments | +| `job.getJobApplication` | Get Job Application | `read` | Retrieve job application records linking candidates to requisitions | +| `job.getJobRequisition` | Get Job Requisition | `read` | Retrieve job requisition records from Recruiting Management | +| `job.getJobReqScreeningQuestion` | Get Job Requisition Screening Questions | `read` | Retrieve screening questions for a job requisition | +| `candidates.listCandidates` | List Candidates | `read` | Retrieve a list of candidates | +| `fo.getFoBusinessUnit` | Get FOBusinessUnit | `read` | Retrieve business unit records for org structure hierarchy | +| `fo.getFoCompany` | Get FOCompany Records | `read` | Retrieve company records (display_name, legal_name, entityOID) | +| `fo.getFoCostCenter` | Get Foundation Object Cost Centers | `read` | Retrieve cost center records for org structure | +| `fo.getFoDepartment` | Get FODepartment Records | `read` | Retrieve department records (team/group org structure) | +| `fo.getFoJobCode` | Get Foundation Object Job Codes | `read` | Retrieve job code records with associated position metadata | +| `fo.getFoJobFunction` | Get Job Functions | `read` | Retrieve job function records for categorizing job roles | +| `fo.getFoLocation` | Get Foundation Object Location | `read` | Retrieve work location records (names, status, timezones, address) | +| `fo.getFoPayGroup` | Get FOPayGroup | `read` | Retrieve pay group records for compensation/payroll groupings | +| `position.getPosition` | Get Position | `read` | Retrieve position management records (structure and hierarchy) | +| `custom.getCustomMdfObject` | Get Custom MDF Object | `read` | Retrieve custom MDF objects (names begin with cust_) | +| `picklist.getPicklist` | Get Picklist | `read` | Retrieve picklist definitions (selectable value lists) | +| `picklist.getPicklistOption` | Get Picklist Option | `read` | Retrieve picklist option values with localized labels | +| `current.getCurrentUser` | Get Current User | `read` | Retrieve the currently authenticated user's information | +| `users.listUsers` | List Users | `read` | Retrieve a list of all employee users | +| `per.getPerPersonById` | Get Person by ID | `read` | Retrieve core person info for an employee by external person ID | +| `per.listPerPerson` | List Person Records | `read` | Retrieve person records (latest active record per person) | +| `per.getPerPersonal` | Get Personal Information Records | `read` | Retrieve biographical info, emergency contacts, social/email data | +| `background.getBackgroundEducation` | Get Background Education | `read` | Retrieve background education records (key: backgroundElementId) | +| `background.getBackgroundMobility` | Get Background Mobility | `read` | Retrieve relocation willingness / geographic mobility preferences | +| `emp.listEmpEmployment` | List Employee Employment Records | `read` | Retrieve employment records (start dates, types, assignment classes) | +| `emp.getEmpEmploymentTermination` | Get Employee Employment Termination | `read` | Retrieve termination records (date, reason) | +| `emp.getEmpPayCompRecurring` | Get Recurring Pay Components | `read` | Retrieve recurring pay components (salary, allowances, benefits) | +| `emp.getEmpPayCompNonRecurring` | Get Non-Recurring Pay Components | `read` | Retrieve non-recurring pay components (bonuses, one-time payments) | +| `work.getWorkOrder` | Get Work Order | `read` | Retrieve work order records for contingent worker management | +| `goal.getGoalPlanTemplate` | Get Goal Plan Template | `read` | Retrieve goal plan template configuration (structure via DTD file) | +| `goals.getGoalsByPlan` | Get Goals By Plan | `read` | Retrieve goals for a specific plan (e | +| `form.getFormContent` | Get Form Content | `read` | Retrieve performance form content (filter by template ID, modified date) | +| `learning.createLearningActivitiesBulk` | Create Learning Activities Bulk | `write` | Create learning activities linked to dev goals in bulk (3rd-party LMS) | +| `cdp.getCdpLearningMetadata` | Get CDP Learning Metadata | `read` | Get metadata for the Career Development Planning Learning service | +| `cdp.refreshCdpLearningMetadata` | Refresh CDP Learning Metadata | `write` | Refresh metadata for the CDP Learning service | +| `employee.getEmployeeTime` | Get Employee Time | `read` | Retrieve employee time entries incl | +| `employee.getEmployeeTimesheet` | Get Employee Timesheet | `read` | Retrieve timesheet records: attendance, overtime, on-call, allowances | +| `temporary.getTemporaryTimeInformation` | Get Temporary Time Information | `read` | Retrieve temporary work schedules assigned to employees | +| `time.getTimeAccountSnapshot` | Get Time Account Snapshot | `read` | Retrieve time account balances for leave liability / payroll as-of a date | +| `query.queryAllAvailableClockClockOut` | Query All Available Clock In/Clock Out Groups | `read` | Retrieve all configured clock in/clock out groups | +| `query.queryClockClockOutGroupCodeTime` | Query Clock In/Clock Out Group By Code | `read` | Retrieve one clock in/out group by code, optionally with time event types | + +--- + +### `approve.approveCalibrationSession` — Approve Calibration Session +- **Description:** Finalize a calibration session that is In Progress or Approving. +- **File:** `endpoints/approve.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/approves` or `/approve/approveCalibrationSession`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `calibration.getCalibrationSessionById` — Get Calibration Session By ID +- **Description:** Get a specific calibration session by session ID. +- **File:** `endpoints/calibration.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSessionById`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `calibration.getCalibrationSessions` — Get Calibration Sessions +- **Description:** Query all calibration sessions the current user can access. +- **File:** `endpoints/calibration.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSessions`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `calibration.getCalibrationSubjectById` — Get Calibration Subject By ID +- **Description:** Query a subject's competency ratings within a calibration session. +- **File:** `endpoints/calibration.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSubjectById`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `calibration.getCalibrationSubjectRatings` — Get Calibration Subject Ratings +- **Description:** Query a subject's ratings/competency ratings/comments by session ID. +- **File:** `endpoints/calibration.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSubjectRatings`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `calibration.updateCalibrationSubjectRatings` — Update Calibration Subject Ratings +- **Description:** Update a subject's competency ratings in a calibration session. +- **File:** `endpoints/calibration.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/updateCalibrationSubjectRatings`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `odata.getOdataMetadataCalibSessionService` — Get Calibration Session Metadata +- **Description:** Get OData metadata / available entity sets for CalSession.svc. +- **File:** `endpoints/odata.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataCalibSessionService`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `odata.getOdataMetadataOnboardingAddl` — Get Onboarding Additional Services Metadata +- **Description:** Get metadata for Onboarding Additional Services (incl. username update ops). +- **File:** `endpoints/odata.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataOnboardingAddl`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `odata.getOdataMetadataForNominationService` — Get Nomination Service Metadata +- **Description:** Get OData metadata for the Nomination service. +- **File:** `endpoints/odata.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataForNominationService`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `odata.getOdataUserMetadata` — Get User Entity Metadata +- **Description:** Retrieve OData metadata for the User entity. +- **File:** `endpoints/odata.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataUserMetadata`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `odata.getOdataMetadataClockInclockOut` — Get Clock In/Out Integration Metadata +- **Description:** Get OData metadata for the Clock In/Clock Out Integration service. +- **File:** `endpoints/odata.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataClockInclockOut`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `onboardee.createOnboardee` — Create Onboardee +- **Description:** Create a new onboardee in Onboarding 2.0 (new hire or rehire). +- **File:** `endpoints/onboardee.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/onboardees` or `/onboardee/createOnboardee`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `onb2.getOnb2Process` — Get Onboarding 2.0 Processes +- **Description:** Retrieve Onboarding 2.0 process records for new hires. +- **File:** `endpoints/onb2.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/onb2s` or `/onb2/getOnb2Process`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `internal.updateInternalUsernameNewHiresAfter` — Update Username Post Hiring +- **Description:** Update a new hire's internal username after MPH submit, pre day-1. +- **File:** `endpoints/internal.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/internals` or `/internal/updateInternalUsernameNewHiresAfter`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `a.createAFeedbackRequest` — Create a Feedback Request +- **Description:** Request performance feedback from one employee about another. +- **File:** `endpoints/a.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/as` or `/a/createAFeedbackRequest`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `feedback.getFeedbackRecordsServiceAvailable` — Get Feedback Records +- **Description:** Query continuous feedback records (OData v4). +- **File:** `endpoints/feedback.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/feedbacks` or `/feedback/getFeedbackRecordsServiceAvailable`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `pending.getPendingFeedbackRequestsFeedback` — Get Pending Feedback Requests +- **Description:** Query pending feedback requests. +- **File:** `endpoints/pending.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/pendings` or `/pending/getPendingFeedbackRequestsFeedback`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `give.giveFeedbackOrRespondToAFeedbackRequest` — Give Feedback or Respond to Feedback Request +- **Description:** Give feedback or respond to a feedback request (up to 3 Q&A pairs). +- **File:** `endpoints/give.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/gives` or `/give/giveFeedbackOrRespondToAFeedbackRequest`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `metadata.refreshMetadataContFeedbackService` — Refresh Metadata for Continuous Feedback +- **Description:** Refresh the metadata cache for the Continuous Feedback service. +- **File:** `endpoints/metadata.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/metadatas` or `/metadata/refreshMetadataContFeedbackService`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `successor.createUpdateSuccessorNomination` — Create or Update Successor Nomination +- **Description:** Create/update a successor nomination for a position or talent pool. +- **File:** `endpoints/successor.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/successors` or `/successor/createUpdateSuccessorNomination`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `nomination.deleteNominationPositionTalentPool` — Delete Nomination +- **Description:** Remove a nominee from a position or talent pool nomination. +- **File:** `endpoints/nomination.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/nominations` or `/nomination/deleteNominationPositionTalentPool`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `talent.getTalentPool` — Get Talent Pool +- **Description:** Retrieve talent pool records including members and nominations. +- **File:** `endpoints/talent.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/talents` or `/talent/getTalentPool`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `application.getApplicationInterview` — Get Application Interview +- **Description:** Retrieve interview info from Interview Central (first 1000 records; filter by applicationId). +- **File:** `endpoints/application.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/applications` or `/application/getApplicationInterview`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `interview.getInterviewOverallAssessment` — Get Interview Overall Assessment +- **Description:** Retrieve overall interview ratings, recommendations, and comments. +- **File:** `endpoints/interview.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/interviews` or `/interview/getInterviewOverallAssessment`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `job.getJobApplication` — Get Job Application +- **Description:** Retrieve job application records linking candidates to requisitions. +- **File:** `endpoints/job.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/jobs` or `/job/getJobApplication`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `job.getJobRequisition` — Get Job Requisition +- **Description:** Retrieve job requisition records from Recruiting Management. +- **File:** `endpoints/job.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/jobs` or `/job/getJobRequisition`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `job.getJobReqScreeningQuestion` — Get Job Requisition Screening Questions +- **Description:** Retrieve screening questions for a job requisition. +- **File:** `endpoints/job.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/jobs` or `/job/getJobReqScreeningQuestion`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `candidates.listCandidates` — List Candidates +- **Description:** Retrieve a list of candidates. +- **File:** `endpoints/candidates.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/candidatess` or `/candidates/listCandidates`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoBusinessUnit` — Get FOBusinessUnit +- **Description:** Retrieve business unit records for org structure hierarchy. +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoBusinessUnit`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoCompany` — Get FOCompany Records +- **Description:** Retrieve company records (display_name, legal_name, entityOID). +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoCompany`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoCostCenter` — Get Foundation Object Cost Centers +- **Description:** Retrieve cost center records for org structure. +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoCostCenter`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoDepartment` — Get FODepartment Records +- **Description:** Retrieve department records (team/group org structure). +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoDepartment`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoJobCode` — Get Foundation Object Job Codes +- **Description:** Retrieve job code records with associated position metadata. +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoJobCode`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoJobFunction` — Get Job Functions +- **Description:** Retrieve job function records for categorizing job roles. +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoJobFunction`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoLocation` — Get Foundation Object Location +- **Description:** Retrieve work location records (names, status, timezones, address). +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoLocation`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `fo.getFoPayGroup` — Get FOPayGroup +- **Description:** Retrieve pay group records for compensation/payroll groupings. +- **File:** `endpoints/fo.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoPayGroup`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `position.getPosition` — Get Position +- **Description:** Retrieve position management records (structure and hierarchy). +- **File:** `endpoints/position.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/positions` or `/position/getPosition`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `custom.getCustomMdfObject` — Get Custom MDF Object +- **Description:** Retrieve custom MDF objects (names begin with cust_). +- **File:** `endpoints/custom.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/customs` or `/custom/getCustomMdfObject`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `picklist.getPicklist` — Get Picklist +- **Description:** Retrieve picklist definitions (selectable value lists). +- **File:** `endpoints/picklist.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/picklists` or `/picklist/getPicklist`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `picklist.getPicklistOption` — Get Picklist Option +- **Description:** Retrieve picklist option values with localized labels. +- **File:** `endpoints/picklist.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/picklists` or `/picklist/getPicklistOption`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `current.getCurrentUser` — Get Current User +- **Description:** Retrieve the currently authenticated user's information. +- **File:** `endpoints/current.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/currents` or `/current/getCurrentUser`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `users.listUsers` — List Users +- **Description:** Retrieve a list of all employee users. +- **File:** `endpoints/users.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/userss` or `/users/listUsers`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `per.getPerPersonById` — Get Person by ID +- **Description:** Retrieve core person info for an employee by external person ID. +- **File:** `endpoints/per.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/pers` or `/per/getPerPersonById`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `per.listPerPerson` — List Person Records +- **Description:** Retrieve person records (latest active record per person). +- **File:** `endpoints/per.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/pers` or `/per/listPerPerson`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `per.getPerPersonal` — Get Personal Information Records +- **Description:** Retrieve biographical info, emergency contacts, social/email data. +- **File:** `endpoints/per.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/pers` or `/per/getPerPersonal`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `background.getBackgroundEducation` — Get Background Education +- **Description:** Retrieve background education records (key: backgroundElementId). +- **File:** `endpoints/background.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/backgrounds` or `/background/getBackgroundEducation`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `background.getBackgroundMobility` — Get Background Mobility +- **Description:** Retrieve relocation willingness / geographic mobility preferences. +- **File:** `endpoints/background.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/backgrounds` or `/background/getBackgroundMobility`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `emp.listEmpEmployment` — List Employee Employment Records +- **Description:** Retrieve employment records (start dates, types, assignment classes). +- **File:** `endpoints/emp.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/listEmpEmployment`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `emp.getEmpEmploymentTermination` — Get Employee Employment Termination +- **Description:** Retrieve termination records (date, reason). +- **File:** `endpoints/emp.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/getEmpEmploymentTermination`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `emp.getEmpPayCompRecurring` — Get Recurring Pay Components +- **Description:** Retrieve recurring pay components (salary, allowances, benefits). +- **File:** `endpoints/emp.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/getEmpPayCompRecurring`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `emp.getEmpPayCompNonRecurring` — Get Non-Recurring Pay Components +- **Description:** Retrieve non-recurring pay components (bonuses, one-time payments). +- **File:** `endpoints/emp.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/getEmpPayCompNonRecurring`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `work.getWorkOrder` — Get Work Order +- **Description:** Retrieve work order records for contingent worker management. +- **File:** `endpoints/work.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/works` or `/work/getWorkOrder`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `goal.getGoalPlanTemplate` — Get Goal Plan Template +- **Description:** Retrieve goal plan template configuration (structure via DTD file). +- **File:** `endpoints/goal.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/goals` or `/goal/getGoalPlanTemplate`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `goals.getGoalsByPlan` — Get Goals By Plan +- **Description:** Retrieve goals for a specific plan (e.g. Goal_11), optionally by userId. +- **File:** `endpoints/goals.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/goalss` or `/goals/getGoalsByPlan`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `form.getFormContent` — Get Form Content +- **Description:** Retrieve performance form content (filter by template ID, modified date). +- **File:** `endpoints/form.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/forms` or `/form/getFormContent`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `learning.createLearningActivitiesBulk` — Create Learning Activities Bulk +- **Description:** Create learning activities linked to dev goals in bulk (3rd-party LMS). +- **File:** `endpoints/learning.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/learnings` or `/learning/createLearningActivitiesBulk`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `cdp.getCdpLearningMetadata` — Get CDP Learning Metadata +- **Description:** Get metadata for the Career Development Planning Learning service. +- **File:** `endpoints/cdp.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/cdps` or `/cdp/getCdpLearningMetadata`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `cdp.refreshCdpLearningMetadata` — Refresh CDP Learning Metadata +- **Description:** Refresh metadata for the CDP Learning service. +- **File:** `endpoints/cdp.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/cdps` or `/cdp/refreshCdpLearningMetadata`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `employee.getEmployeeTime` — Get Employee Time +- **Description:** Retrieve employee time entries incl. time off (filter by userId/status/type/date). +- **File:** `endpoints/employee.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/employees` or `/employee/getEmployeeTime`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `employee.getEmployeeTimesheet` — Get Employee Timesheet +- **Description:** Retrieve timesheet records: attendance, overtime, on-call, allowances. +- **File:** `endpoints/employee.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/employees` or `/employee/getEmployeeTimesheet`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `temporary.getTemporaryTimeInformation` — Get Temporary Time Information +- **Description:** Retrieve temporary work schedules assigned to employees. +- **File:** `endpoints/temporary.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/temporarys` or `/temporary/getTemporaryTimeInformation`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `time.getTimeAccountSnapshot` — Get Time Account Snapshot +- **Description:** Retrieve time account balances for leave liability / payroll as-of a date. +- **File:** `endpoints/time.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/times` or `/time/getTimeAccountSnapshot`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `query.queryAllAvailableClockClockOut` — Query All Available Clock In/Clock Out Groups +- **Description:** Retrieve all configured clock in/clock out groups. +- **File:** `endpoints/query.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/querys` or `/query/queryAllAvailableClockClockOut`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +### `query.queryClockClockOutGroupCodeTime` — Query Clock In/Clock Out Group By Code +- **Description:** Retrieve one clock in/out group by code, optionally with time event types. +- **File:** `endpoints/query.ts` +- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) +- [ ] Set the correct endpoint path (e.g., `/v1/querys` or `/query/queryClockClockOutGroupCodeTime`) +- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** +- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs + +--- + +## Step 4 — Webhooks + + +This integration does not have documented webhook triggers in the scraped spec. + +Check the docs to confirm: +- [ ] Does SapSuccessfactors support webhooks? If yes, add them following the Resend plugin as a reference (`packages/resend/webhooks/`). +- [ ] If no webhooks, the empty `webhooksNested` in `index.ts` is correct. + +## Webhook tenant routing + +Corsair routes multi-tenant webhooks using three linked pieces. **linkType must match across all three:** + +| Piece | File | Purpose | +|---|---|---| +| `pluginTenantWebhookMatcher` | `webhooks/tenant-matcher.ts` | Extract id from incoming webhook | +| `authConfig.{authType}.account` | `index.ts` | Field name stored on `corsair_accounts.config` | +| `oauthWebhookTenantLinkResolver` | `webhooks/oauth-tenant-link.ts` | Populate field after OAuth (if applicable) | + +- [ ] Rename `tenant_external_id` to the provider's real field (e.g. `team_id`, `installation_id`) +- [ ] Update `match{Plugin}TenantWebhook` to parse the webhook payload (return `null` for handshakes) +- [ ] Update `{plugin}AuthConfig` account fields to use the same linkType +- [ ] If OAuth: implement `resolve{Plugin}OAuthWebhookTenantLink` (token response and/or post-OAuth API call) +- [ ] Wire `pluginTenantWebhookMatcher` and `oauthWebhookTenantLinkResolver` on the plugin return object +- [ ] Reference: `packages/slack/webhooks/tenant-matcher.ts` and `packages/slack/webhooks/oauth-tenant-link.ts` + + + +--- + +## Step 5 — Typecheck + +```bash +cd packages/sapsuccessfactors && pnpm typecheck +# or from the root: +pnpm typecheck +``` + +Fix any TypeScript errors before moving on. + +--- + +## Step 6 — Write tests + +Create a `tests/` directory in this package. Write at minimum: + +1. **Schema validation tests** — confirm the Zod schemas accept valid payloads and reject invalid ones +2. **Endpoint stub tests** — mock `makeSapsuccessfactorsRequest` and verify the correct path/method/params are passed +3. **At least one happy-path integration test** if you have access to a SapSuccessfactors sandbox/test account + +Reference: look at existing test files in `packages/resend/` or `packages/slack/` for patterns. + +--- + +## Step 7 — Register in your corsair instance + +After the plugin is complete, add it to your app's `corsair.ts`: + +```ts +import { sapsuccessfactors } from '@corsair-dev/sapsuccessfactors'; + +export const corsair = createCorsair({ + plugins: [ + sapsuccessfactors({ key: process.env.SAPSUCCESSFACTORS_API_KEY }), + // ... other plugins + ], +}); +``` diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts new file mode 100644 index 000000000..98391f8f2 --- /dev/null +++ b/packages/sapsuccessfactors/api.test.ts @@ -0,0 +1,582 @@ +declare const describe: (name: string, fn: () => void) => void; +declare const it: (name: string, fn: () => Promise | void) => void; +declare const expect: (val: any) => any; +declare const beforeEach: (fn: () => void) => void; +declare const jest: any; + +import { request } from 'corsair/http'; +import { sapsuccessfactors } from './index'; + +jest.mock('corsair/http', () => ({ + request: jest + .fn() + .mockResolvedValue({ d: { results: [{ id: 'test-123' }] } }), + ApiError: class ApiError extends Error { + constructor( + public status: number, + message: string, + ) { + super(message); + } + }, +})); + +const mockedRequest = request as any; + +describe('SapSuccessfactors Plugin', () => { + const plugin = sapsuccessfactors({ key: 'test-api-token' }); + const mockCtx = { + key: 'test-api-token', + authType: 'api_key' as const, + db: {}, + log: jest.fn(), + } as any; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('initializes plugin with correct id and configuration', () => { + expect(plugin.id).toBe('sapsuccessfactors'); + expect(plugin.authConfig).toBeDefined(); + expect(plugin.endpoints).toBeDefined(); + }); + + it('calls approve.approveCalibrationSession endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.approve + ?.approveCalibrationSession; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls calibration.getCalibrationSessionById endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.calibration + ?.getCalibrationSessionById; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls calibration.getCalibrationSessions endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.calibration + ?.getCalibrationSessions; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls calibration.getCalibrationSubjectById endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.calibration + ?.getCalibrationSubjectById; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls calibration.getCalibrationSubjectRatings endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.calibration + ?.getCalibrationSubjectRatings; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls calibration.updateCalibrationSubjectRatings endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.calibration + ?.updateCalibrationSubjectRatings; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls odata.getOdataMetadataCalibSessionService endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.odata + ?.getOdataMetadataCalibSessionService; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls odata.getOdataMetadataOnboardingAddl endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.odata + ?.getOdataMetadataOnboardingAddl; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls odata.getOdataMetadataForNominationService endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.odata + ?.getOdataMetadataForNominationService; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls odata.getOdataUserMetadata endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.odata?.getOdataUserMetadata; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls odata.getOdataMetadataClockInclockOut endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.odata + ?.getOdataMetadataClockInclockOut; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls onboardee.createOnboardee endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.onboardee?.createOnboardee; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls onb2.getOnb2Process endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.onb2?.getOnb2Process; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls internal.updateInternalUsernameNewHiresAfter endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.internal + ?.updateInternalUsernameNewHiresAfter; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls a.createAFeedbackRequest endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.a?.createAFeedbackRequest; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls feedback.getFeedbackRecordsServiceAvailable endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.feedback + ?.getFeedbackRecordsServiceAvailable; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls pending.getPendingFeedbackRequestsFeedback endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.pending + ?.getPendingFeedbackRequestsFeedback; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls give.giveFeedbackOrRespondToAFeedbackRequest endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.give + ?.giveFeedbackOrRespondToAFeedbackRequest; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls metadata.refreshMetadataContFeedbackService endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.metadata + ?.refreshMetadataContFeedbackService; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls successor.createUpdateSuccessorNomination endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.successor + ?.createUpdateSuccessorNomination; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls nomination.deleteNominationPositionTalentPool endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.nomination + ?.deleteNominationPositionTalentPool; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls talent.getTalentPool endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.talent?.getTalentPool; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls application.getApplicationInterview endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.application + ?.getApplicationInterview; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls interview.getInterviewOverallAssessment endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.interview + ?.getInterviewOverallAssessment; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls job.getJobApplication endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.job?.getJobApplication; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls job.getJobRequisition endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.job?.getJobRequisition; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls job.getJobReqScreeningQuestion endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.job?.getJobReqScreeningQuestion; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls candidates.listCandidates endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.candidates?.listCandidates; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoBusinessUnit endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoBusinessUnit; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoCompany endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoCompany; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoCostCenter endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoCostCenter; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoDepartment endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoDepartment; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoJobCode endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoJobCode; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoJobFunction endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoJobFunction; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoLocation endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoLocation; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls fo.getFoPayGroup endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.fo?.getFoPayGroup; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls position.getPosition endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.position?.getPosition; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls custom.getCustomMdfObject endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.custom?.getCustomMdfObject; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls picklist.getPicklist endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.picklist?.getPicklist; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls picklist.getPicklistOption endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.picklist?.getPicklistOption; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls current.getCurrentUser endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.current?.getCurrentUser; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls users.listUsers endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.users?.listUsers; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls per.getPerPersonById endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.per?.getPerPersonById; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls per.listPerPerson endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.per?.listPerPerson; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls per.getPerPersonal endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.per?.getPerPersonal; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls background.getBackgroundEducation endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.background + ?.getBackgroundEducation; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls background.getBackgroundMobility endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.background + ?.getBackgroundMobility; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls emp.listEmpEmployment endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.emp?.listEmpEmployment; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls emp.getEmpEmploymentTermination endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.emp + ?.getEmpEmploymentTermination; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls emp.getEmpPayCompRecurring endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.emp?.getEmpPayCompRecurring; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls emp.getEmpPayCompNonRecurring endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.emp?.getEmpPayCompNonRecurring; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls work.getWorkOrder endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.work?.getWorkOrder; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls goal.getGoalPlanTemplate endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.goal?.getGoalPlanTemplate; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls goals.getGoalsByPlan endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.goals?.getGoalsByPlan; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls form.getFormContent endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.form?.getFormContent; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls learning.createLearningActivitiesBulk endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.learning + ?.createLearningActivitiesBulk; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls cdp.getCdpLearningMetadata endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.cdp?.getCdpLearningMetadata; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls cdp.refreshCdpLearningMetadata endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.cdp?.refreshCdpLearningMetadata; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls employee.getEmployeeTime endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.employee?.getEmployeeTime; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls employee.getEmployeeTimesheet endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.employee?.getEmployeeTimesheet; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls temporary.getTemporaryTimeInformation endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.temporary + ?.getTemporaryTimeInformation; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls time.getTimeAccountSnapshot endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.time?.getTimeAccountSnapshot; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls query.queryAllAvailableClockClockOut endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.query + ?.queryAllAvailableClockClockOut; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); + + it('calls query.queryClockClockOutGroupCodeTime endpoint correctly', async () => { + const endpoint = (plugin.endpoints as any)?.query + ?.queryClockClockOutGroupCodeTime; + expect(endpoint).toBeDefined(); + const res = await endpoint(mockCtx, {} as any); + expect(res).toBeDefined(); + expect(mockedRequest).toHaveBeenCalled(); + }); +}); diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts new file mode 100644 index 000000000..e349eb67a --- /dev/null +++ b/packages/sapsuccessfactors/client.ts @@ -0,0 +1,77 @@ +import type { + ApiRequestOptions, + OpenAPIConfig, + RateLimitConfig, +} from 'corsair/http'; +import { ApiError, request } from 'corsair/http'; + +export class SapsuccessfactorsAPIError extends Error { + constructor( + message: string, + public readonly code?: string, + ) { + super(message); + this.name = 'SapsuccessfactorsAPIError'; + } +} + +const SAP_SUCCESSFACTORS_API_BASE = 'https://api10.successfactors.com'; + +const SAP_SUCCESSFACTORS_RATE_LIMIT_CONFIG: RateLimitConfig = { + enabled: true, + maxRetries: 3, + initialRetryDelay: 1000, + backoffMultiplier: 2, + headerNames: { + retryAfter: 'Retry-After', + }, +}; + +export async function makeSapsuccessfactorsRequest( + endpoint: string, + apiKey: string, + options: { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + body?: Record; + query?: Record; + } = {}, +): Promise { + const { method = 'GET', body, query } = options; + + const config: OpenAPIConfig = { + BASE: SAP_SUCCESSFACTORS_API_BASE, + VERSION: '1.0.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'omit', + TOKEN: apiKey, + HEADERS: { + 'Content-Type': 'application/json', + Authorization: + apiKey.startsWith('Basic ') || apiKey.startsWith('Bearer ') + ? apiKey + : `Bearer ${apiKey}`, + }, + }; + + const requestOptions: ApiRequestOptions = { + method, + url: endpoint.startsWith('/') ? endpoint : `/${endpoint}`, + body: + method === 'POST' || method === 'PUT' || method === 'PATCH' + ? body + : undefined, + mediaType: 'application/json; charset=utf-8', + query: method === 'GET' ? query : undefined, + }; + + try { + return await request(config, requestOptions, { + rateLimitConfig: SAP_SUCCESSFACTORS_RATE_LIMIT_CONFIG, + }); + } catch (error) { + if (error instanceof ApiError) throw error; + if (error instanceof Error) + throw new SapsuccessfactorsAPIError(error.message); + throw new SapsuccessfactorsAPIError('Unknown error'); + } +} diff --git a/packages/sapsuccessfactors/endpoints/a.ts b/packages/sapsuccessfactors/endpoints/a.ts new file mode 100644 index 000000000..f7220e6cf --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/a.ts @@ -0,0 +1,26 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Create a Feedback Request +// Request performance feedback from one employee about another. +export const createAFeedbackRequest: SapsuccessfactorsEndpoints['createAFeedbackRequest'] = + async (ctx, input) => { + const { body, ...rest } = (input ?? {}) as { + body?: Record; + }; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['createAFeedbackRequest'] + >('odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', ctx.key, { + method: 'POST', + body: (body ?? rest) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.a.createAFeedbackRequest', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/application.ts b/packages/sapsuccessfactors/endpoints/application.ts new file mode 100644 index 000000000..80b095aca --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/application.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Application Interview +// Retrieve interview info from Interview Central (first 1000 records; filter by applicationId). +export const getApplicationInterview: SapsuccessfactorsEndpoints['getApplicationInterview'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getApplicationInterview'] + >('odata/v2/ApplicationInterview', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.application.getApplicationInterview', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/approve.ts b/packages/sapsuccessfactors/endpoints/approve.ts new file mode 100644 index 000000000..3487e4d66 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/approve.ts @@ -0,0 +1,23 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Approve Calibration Session +// Finalize a calibration session that is In Progress or Approving. +export const approveCalibrationSession: SapsuccessfactorsEndpoints['approveCalibrationSession'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['approveCalibrationSession'] + >('odata/v4/CalSession.svc/Approve', ctx.key, { + method: 'POST', + body: (input ?? {}) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.approve.approveCalibrationSession', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/background.ts b/packages/sapsuccessfactors/endpoints/background.ts new file mode 100644 index 000000000..00460352d --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/background.ts @@ -0,0 +1,44 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Background Education +// Retrieve background education records (key: backgroundElementId). +export const getBackgroundEducation: SapsuccessfactorsEndpoints['getBackgroundEducation'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getBackgroundEducation'] + >('odata/v2/BackgroundEducation', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.background.getBackgroundEducation', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Background Mobility +// Retrieve relocation willingness / geographic mobility preferences. +export const getBackgroundMobility: SapsuccessfactorsEndpoints['getBackgroundMobility'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getBackgroundMobility'] + >('odata/v2/BackgroundMobility', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.background.getBackgroundMobility', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/calibration.ts b/packages/sapsuccessfactors/endpoints/calibration.ts new file mode 100644 index 000000000..079c082c0 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/calibration.ts @@ -0,0 +1,122 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Calibration Session By ID +// Get a specific calibration session by session ID. +export const getCalibrationSessionById: SapsuccessfactorsEndpoints['getCalibrationSessionById'] = + async (ctx, input) => { + const { session_id, ...query } = (input ?? {}) as { session_id?: string }; + const resourcePath = session_id + ? `odata/v4/CalSession.svc/CalibrationSession('${session_id}')` + : 'odata/v4/CalSession.svc/CalibrationSession'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCalibrationSessionById'] + >(resourcePath, ctx.key, { + method: 'GET', + query: query as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.calibration.getCalibrationSessionById', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Calibration Sessions +// Query all calibration sessions the current user can access. +export const getCalibrationSessions: SapsuccessfactorsEndpoints['getCalibrationSessions'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCalibrationSessions'] + >('odata/v4/CalSession.svc/CalibrationSession', ctx.key, { + method: 'GET', + query, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.calibration.getCalibrationSessions', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Calibration Subject By ID +// Query a subject's competency ratings within a calibration session. +export const getCalibrationSubjectById: SapsuccessfactorsEndpoints['getCalibrationSubjectById'] = + async (ctx, input) => { + const { subject_id, ...query } = (input ?? {}) as { subject_id?: string }; + const resourcePath = subject_id + ? `odata/v4/CalSession.svc/CalibrationSubject('${subject_id}')` + : 'odata/v4/CalSession.svc/CalibrationSubject'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCalibrationSubjectById'] + >(resourcePath, ctx.key, { + method: 'GET', + query: query as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.calibration.getCalibrationSubjectById', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Calibration Subject Ratings +// Query a subject's ratings/competency ratings/comments by session ID. +export const getCalibrationSubjectRatings: SapsuccessfactorsEndpoints['getCalibrationSubjectRatings'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCalibrationSubjectRatings'] + >('odata/v4/CalSession.svc/CalibrationSubject', ctx.key, { + method: 'GET', + query, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.calibration.getCalibrationSubjectRatings', + input ?? {}, + 'completed', + ); + return response; + }; + +// Update Calibration Subject Ratings +// Update a subject's competency ratings in a calibration session. +export const updateCalibrationSubjectRatings: SapsuccessfactorsEndpoints['updateCalibrationSubjectRatings'] = + async (ctx, input) => { + const { subject_id, body, ...rest } = (input ?? {}) as { + subject_id?: string; + body?: Record; + }; + const resourcePath = subject_id + ? `odata/v4/CalSession.svc/CalibrationSubject(${subject_id})` + : 'odata/v4/CalSession.svc/CalibrationSubject'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['updateCalibrationSubjectRatings'] + >(resourcePath, ctx.key, { + method: 'PATCH', + body: (body ?? rest) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.calibration.updateCalibrationSubjectRatings', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/candidates.ts b/packages/sapsuccessfactors/endpoints/candidates.ts new file mode 100644 index 000000000..370999b6a --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/candidates.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// List Candidates +// Retrieve a list of candidates. +export const listCandidates: SapsuccessfactorsEndpoints['listCandidates'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['listCandidates'] + >('odata/v2/Candidate', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.candidates.listCandidates', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/cdp.ts b/packages/sapsuccessfactors/endpoints/cdp.ts new file mode 100644 index 000000000..981ade6e4 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/cdp.ts @@ -0,0 +1,39 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get CDP Learning Metadata +// Get metadata for the Career Development Planning Learning service. +export const getCdpLearningMetadata: SapsuccessfactorsEndpoints['getCdpLearningMetadata'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCdpLearningMetadata'] + >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.cdp.getCdpLearningMetadata', + input ?? {}, + 'completed', + ); + return response; + }; + +// Refresh CDP Learning Metadata +// Refresh metadata for the CDP Learning service. +export const refreshCdpLearningMetadata: SapsuccessfactorsEndpoints['refreshCdpLearningMetadata'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['refreshCdpLearningMetadata'] + >('odata/v2/RefreshCDPLearningMetadata', ctx.key, { + method: 'POST', + body: (input ?? {}) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.cdp.refreshCdpLearningMetadata', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/current.ts b/packages/sapsuccessfactors/endpoints/current.ts new file mode 100644 index 000000000..293ad3fc4 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/current.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Current User +// Retrieve the currently authenticated user's information. +export const getCurrentUser: SapsuccessfactorsEndpoints['getCurrentUser'] = + async (ctx, input) => { + const query = (input ?? {}) as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCurrentUser'] + >('odata/v2/User', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.current.getCurrentUser', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/custom.ts b/packages/sapsuccessfactors/endpoints/custom.ts new file mode 100644 index 000000000..a0a3a50d3 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/custom.ts @@ -0,0 +1,29 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Custom MDF Object +// Retrieve custom MDF objects (names begin with cust_). +export const getCustomMdfObject: SapsuccessfactorsEndpoints['getCustomMdfObject'] = + async (ctx, input) => { + const { custom_object, ...rest } = (input ?? {}) as { + custom_object?: string; + }; + const resourcePath = custom_object + ? `odata/v2/${custom_object}` + : 'odata/v2/custom_objects'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getCustomMdfObject'] + >(resourcePath, ctx.key, { + method: 'GET', + query: rest as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.custom.getCustomMdfObject', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/emp.ts b/packages/sapsuccessfactors/endpoints/emp.ts new file mode 100644 index 000000000..8f41f5231 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/emp.ts @@ -0,0 +1,84 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// List Employee Employment Records +// Retrieve employment records (start dates, types, assignment classes). +export const listEmpEmployment: SapsuccessfactorsEndpoints['listEmpEmployment'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['listEmpEmployment'] + >('odata/v2/EmpEmployment', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.emp.listEmpEmployment', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Employee Employment Termination +// Retrieve termination records (date, reason). +export const getEmpEmploymentTermination: SapsuccessfactorsEndpoints['getEmpEmploymentTermination'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getEmpEmploymentTermination'] + >('odata/v2/EmpEmploymentTermination', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.emp.getEmpEmploymentTermination', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Recurring Pay Components +// Retrieve recurring pay components (salary, allowances, benefits). +export const getEmpPayCompRecurring: SapsuccessfactorsEndpoints['getEmpPayCompRecurring'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getEmpPayCompRecurring'] + >('odata/v2/EmpPayCompRecurring', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.emp.getEmpPayCompRecurring', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Non-Recurring Pay Components +// Retrieve non-recurring pay components (bonuses, one-time payments). +export const getEmpPayCompNonRecurring: SapsuccessfactorsEndpoints['getEmpPayCompNonRecurring'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getEmpPayCompNonRecurring'] + >('odata/v2/EmpPayCompNonRecurring', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.emp.getEmpPayCompNonRecurring', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/employee.ts b/packages/sapsuccessfactors/endpoints/employee.ts new file mode 100644 index 000000000..9db60f2f4 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/employee.ts @@ -0,0 +1,44 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Employee Time +// Retrieve employee time entries incl. time off (filter by userId/status/type/date). +export const getEmployeeTime: SapsuccessfactorsEndpoints['getEmployeeTime'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getEmployeeTime'] + >('odata/v2/EmployeeTime', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.employee.getEmployeeTime', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Employee Timesheet +// Retrieve timesheet records: attendance, overtime, on-call, allowances. +export const getEmployeeTimesheet: SapsuccessfactorsEndpoints['getEmployeeTimesheet'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getEmployeeTimesheet'] + >('odata/v2/EmployeeTimeSheet', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.employee.getEmployeeTimesheet', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/feedback.ts b/packages/sapsuccessfactors/endpoints/feedback.ts new file mode 100644 index 000000000..e71850017 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/feedback.ts @@ -0,0 +1,27 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Feedback Records +// Query continuous feedback records (OData v4). +export const getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoints['getFeedbackRecordsServiceAvailable'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFeedbackRecordsServiceAvailable'] + >('odata/v4/ContinuousPerformanceManagement.svc/Feedback', ctx.key, { + method: 'GET', + query, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.feedback.getFeedbackRecordsServiceAvailable', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/fo.ts b/packages/sapsuccessfactors/endpoints/fo.ts new file mode 100644 index 000000000..2e68326f6 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/fo.ts @@ -0,0 +1,162 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get FOBusinessUnit +// Retrieve business unit records for org structure hierarchy. +export const getFoBusinessUnit: SapsuccessfactorsEndpoints['getFoBusinessUnit'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoBusinessUnit'] + >('odata/v2/FOBusinessUnit', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoBusinessUnit', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get FOCompany Records +// Retrieve company records (display_name, legal_name, entityOID). +export const getFoCompany: SapsuccessfactorsEndpoints['getFoCompany'] = async ( + ctx, + input, +) => { + const query = input as Record; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoCompany'] + >('odata/v2/FOCompany', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoCompany', + input ?? {}, + 'completed', + ); + return response; +}; + +// Get Foundation Object Cost Centers +// Retrieve cost center records for org structure. +export const getFoCostCenter: SapsuccessfactorsEndpoints['getFoCostCenter'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoCostCenter'] + >('odata/v2/FOCostCenter', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoCostCenter', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get FODepartment Records +// Retrieve department records (team/group org structure). +export const getFoDepartment: SapsuccessfactorsEndpoints['getFoDepartment'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoDepartment'] + >('odata/v2/FODepartment', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoDepartment', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Foundation Object Job Codes +// Retrieve job code records with associated position metadata. +export const getFoJobCode: SapsuccessfactorsEndpoints['getFoJobCode'] = async ( + ctx, + input, +) => { + const query = input as Record; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoJobCode'] + >('odata/v2/FOJobCode', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoJobCode', + input ?? {}, + 'completed', + ); + return response; +}; + +// Get Job Functions +// Retrieve job function records for categorizing job roles. +export const getFoJobFunction: SapsuccessfactorsEndpoints['getFoJobFunction'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoJobFunction'] + >('odata/v2/FOJobFunction', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoJobFunction', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Foundation Object Location +// Retrieve work location records (names, status, timezones, address). +export const getFoLocation: SapsuccessfactorsEndpoints['getFoLocation'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoLocation'] + >('odata/v2/FOLocation', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoLocation', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get FOPayGroup +// Retrieve pay group records for compensation/payroll groupings. +export const getFoPayGroup: SapsuccessfactorsEndpoints['getFoPayGroup'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFoPayGroup'] + >('odata/v2/FOPayGroup', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.fo.getFoPayGroup', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/form.ts b/packages/sapsuccessfactors/endpoints/form.ts new file mode 100644 index 000000000..459badff7 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/form.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Form Content +// Retrieve performance form content (filter by template ID, modified date). +export const getFormContent: SapsuccessfactorsEndpoints['getFormContent'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getFormContent'] + >('odata/v2/FormContent', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.form.getFormContent', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/give.ts b/packages/sapsuccessfactors/endpoints/give.ts new file mode 100644 index 000000000..cb6240cac --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/give.ts @@ -0,0 +1,26 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Give Feedback or Respond to Feedback Request +// Give feedback or respond to a feedback request (up to 3 Q&A pairs). +export const giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoints['giveFeedbackOrRespondToAFeedbackRequest'] = + async (ctx, input) => { + const { body, ...rest } = (input ?? {}) as { + body?: Record; + }; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['giveFeedbackOrRespondToAFeedbackRequest'] + >('odata/v4/ContinuousPerformanceManagement.svc/Feedback', ctx.key, { + method: 'POST', + body: (body ?? rest) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.give.giveFeedbackOrRespondToAFeedbackRequest', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/goal.ts b/packages/sapsuccessfactors/endpoints/goal.ts new file mode 100644 index 000000000..2afdb5b2a --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/goal.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Goal Plan Template +// Retrieve goal plan template configuration (structure via DTD file). +export const getGoalPlanTemplate: SapsuccessfactorsEndpoints['getGoalPlanTemplate'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getGoalPlanTemplate'] + >('odata/v2/GoalPlanTemplate', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.goal.getGoalPlanTemplate', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/goals.ts b/packages/sapsuccessfactors/endpoints/goals.ts new file mode 100644 index 000000000..0ef557b9e --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/goals.ts @@ -0,0 +1,29 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Goals By Plan +// Retrieve goals for a specific plan (e.g. Goal_11), optionally by userId. +export const getGoalsByPlan: SapsuccessfactorsEndpoints['getGoalsByPlan'] = + async (ctx, input) => { + const { goal_plan_id, ...rest } = (input ?? {}) as { + goal_plan_id?: string; + }; + const resourcePath = goal_plan_id + ? `odata/v2/Goal_${goal_plan_id}` + : 'odata/v2/Goal'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getGoalsByPlan'] + >(resourcePath, ctx.key, { + method: 'GET', + query: rest as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.goals.getGoalsByPlan', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/index.ts b/packages/sapsuccessfactors/endpoints/index.ts new file mode 100644 index 000000000..dbf2895b4 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/index.ts @@ -0,0 +1,178 @@ +import { approveCalibrationSession } from './approve'; +export const Approve = { approveCalibrationSession }; + +import { + getCalibrationSessionById, + getCalibrationSessions, + getCalibrationSubjectById, + getCalibrationSubjectRatings, + updateCalibrationSubjectRatings, +} from './calibration'; +export const Calibration = { + getCalibrationSessionById, + getCalibrationSessions, + getCalibrationSubjectById, + getCalibrationSubjectRatings, + updateCalibrationSubjectRatings, +}; + +import { + getOdataMetadataCalibSessionService, + getOdataMetadataClockInclockOut, + getOdataMetadataForNominationService, + getOdataMetadataOnboardingAddl, + getOdataUserMetadata, +} from './odata'; +export const Odata = { + getOdataMetadataCalibSessionService, + getOdataMetadataOnboardingAddl, + getOdataMetadataForNominationService, + getOdataUserMetadata, + getOdataMetadataClockInclockOut, +}; + +import { createOnboardee } from './onboardee'; +export const Onboardee = { createOnboardee }; + +import { getOnb2Process } from './onb2'; +export const Onb2 = { getOnb2Process }; + +import { updateInternalUsernameNewHiresAfter } from './internal'; +export const Internal = { updateInternalUsernameNewHiresAfter }; + +import { createAFeedbackRequest } from './a'; +export const A = { createAFeedbackRequest }; + +import { getFeedbackRecordsServiceAvailable } from './feedback'; +export const Feedback = { getFeedbackRecordsServiceAvailable }; + +import { getPendingFeedbackRequestsFeedback } from './pending'; +export const Pending = { getPendingFeedbackRequestsFeedback }; + +import { giveFeedbackOrRespondToAFeedbackRequest } from './give'; +export const Give = { giveFeedbackOrRespondToAFeedbackRequest }; + +import { refreshMetadataContFeedbackService } from './metadata'; +export const Metadata = { refreshMetadataContFeedbackService }; + +import { createUpdateSuccessorNomination } from './successor'; +export const Successor = { createUpdateSuccessorNomination }; + +import { deleteNominationPositionTalentPool } from './nomination'; +export const Nomination = { deleteNominationPositionTalentPool }; + +import { getTalentPool } from './talent'; +export const Talent = { getTalentPool }; + +import { getApplicationInterview } from './application'; +export const Application = { getApplicationInterview }; + +import { getInterviewOverallAssessment } from './interview'; +export const Interview = { getInterviewOverallAssessment }; + +import { + getJobApplication, + getJobReqScreeningQuestion, + getJobRequisition, +} from './job'; +export const Job = { + getJobApplication, + getJobRequisition, + getJobReqScreeningQuestion, +}; + +import { listCandidates } from './candidates'; +export const Candidates = { listCandidates }; + +import { + getFoBusinessUnit, + getFoCompany, + getFoCostCenter, + getFoDepartment, + getFoJobCode, + getFoJobFunction, + getFoLocation, + getFoPayGroup, +} from './fo'; +export const Fo = { + getFoBusinessUnit, + getFoCompany, + getFoCostCenter, + getFoDepartment, + getFoJobCode, + getFoJobFunction, + getFoLocation, + getFoPayGroup, +}; + +import { getPosition } from './position'; +export const Position = { getPosition }; + +import { getCustomMdfObject } from './custom'; +export const Custom = { getCustomMdfObject }; + +import { getPicklist, getPicklistOption } from './picklist'; +export const Picklist = { getPicklist, getPicklistOption }; + +import { getCurrentUser } from './current'; +export const Current = { getCurrentUser }; + +import { listUsers } from './users'; +export const Users = { listUsers }; + +import { getPerPersonal, getPerPersonById, listPerPerson } from './per'; +export const Per = { getPerPersonById, listPerPerson, getPerPersonal }; + +import { getBackgroundEducation, getBackgroundMobility } from './background'; +export const Background = { getBackgroundEducation, getBackgroundMobility }; + +import { + getEmpEmploymentTermination, + getEmpPayCompNonRecurring, + getEmpPayCompRecurring, + listEmpEmployment, +} from './emp'; +export const Emp = { + listEmpEmployment, + getEmpEmploymentTermination, + getEmpPayCompRecurring, + getEmpPayCompNonRecurring, +}; + +import { getWorkOrder } from './work'; +export const Work = { getWorkOrder }; + +import { getGoalPlanTemplate } from './goal'; +export const Goal = { getGoalPlanTemplate }; + +import { getGoalsByPlan } from './goals'; +export const Goals = { getGoalsByPlan }; + +import { getFormContent } from './form'; +export const Form = { getFormContent }; + +import { createLearningActivitiesBulk } from './learning'; +export const Learning = { createLearningActivitiesBulk }; + +import { getCdpLearningMetadata, refreshCdpLearningMetadata } from './cdp'; +export const Cdp = { getCdpLearningMetadata, refreshCdpLearningMetadata }; + +import { getEmployeeTime, getEmployeeTimesheet } from './employee'; +export const Employee = { getEmployeeTime, getEmployeeTimesheet }; + +import { getTemporaryTimeInformation } from './temporary'; +export const Temporary = { getTemporaryTimeInformation }; + +import { getTimeAccountSnapshot } from './time'; +export const Time = { getTimeAccountSnapshot }; + +import { + queryAllAvailableClockClockOut, + queryClockClockOutGroupCodeTime, +} from './query'; +export const Query = { + queryAllAvailableClockClockOut, + queryClockClockOutGroupCodeTime, +}; + +export * from './types'; diff --git a/packages/sapsuccessfactors/endpoints/internal.ts b/packages/sapsuccessfactors/endpoints/internal.ts new file mode 100644 index 000000000..d98dc9990 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/internal.ts @@ -0,0 +1,23 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Update Username Post Hiring +// Update a new hire's internal username after MPH submit, pre day-1. +export const updateInternalUsernameNewHiresAfter: SapsuccessfactorsEndpoints['updateInternalUsernameNewHiresAfter'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['updateInternalUsernameNewHiresAfter'] + >('odata/v2/updateUserNamePostHiring', ctx.key, { + method: 'POST', + body: (input ?? {}) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.internal.updateInternalUsernameNewHiresAfter', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/interview.ts b/packages/sapsuccessfactors/endpoints/interview.ts new file mode 100644 index 000000000..650e48e77 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/interview.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Interview Overall Assessment +// Retrieve overall interview ratings, recommendations, and comments. +export const getInterviewOverallAssessment: SapsuccessfactorsEndpoints['getInterviewOverallAssessment'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getInterviewOverallAssessment'] + >('odata/v2/OverallInterviewAssessment', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.interview.getInterviewOverallAssessment', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/job.ts b/packages/sapsuccessfactors/endpoints/job.ts new file mode 100644 index 000000000..1a7f0c7fb --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/job.ts @@ -0,0 +1,64 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Job Application +// Retrieve job application records linking candidates to requisitions. +export const getJobApplication: SapsuccessfactorsEndpoints['getJobApplication'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getJobApplication'] + >('odata/v2/JobApplication', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.job.getJobApplication', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Job Requisition +// Retrieve job requisition records from Recruiting Management. +export const getJobRequisition: SapsuccessfactorsEndpoints['getJobRequisition'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getJobRequisition'] + >('odata/v2/JobRequisition', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.job.getJobRequisition', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Job Requisition Screening Questions +// Retrieve screening questions for a job requisition. +export const getJobReqScreeningQuestion: SapsuccessfactorsEndpoints['getJobReqScreeningQuestion'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getJobReqScreeningQuestion'] + >('odata/v2/JobReqScreeningQuestion', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.job.getJobReqScreeningQuestion', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/learning.ts b/packages/sapsuccessfactors/endpoints/learning.ts new file mode 100644 index 000000000..a9bbd7fe4 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/learning.ts @@ -0,0 +1,26 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Create Learning Activities Bulk +// Create learning activities linked to dev goals in bulk (3rd-party LMS). +export const createLearningActivitiesBulk: SapsuccessfactorsEndpoints['createLearningActivitiesBulk'] = + async (ctx, input) => { + const { body, ...rest } = (input ?? {}) as { + body?: Record; + }; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['createLearningActivitiesBulk'] + >('odata/v2/LearningActivity', ctx.key, { + method: 'POST', + body: (body ?? rest) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.learning.createLearningActivitiesBulk', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/metadata.ts b/packages/sapsuccessfactors/endpoints/metadata.ts new file mode 100644 index 000000000..5ff8704d4 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/metadata.ts @@ -0,0 +1,23 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Refresh Metadata for Continuous Feedback +// Refresh the metadata cache for the Continuous Feedback service. +export const refreshMetadataContFeedbackService: SapsuccessfactorsEndpoints['refreshMetadataContFeedbackService'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['refreshMetadataContFeedbackService'] + >('odata/v4/ContinuousPerformanceManagement.svc/RefreshMetadata', ctx.key, { + method: 'POST', + body: (input ?? {}) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.metadata.refreshMetadataContFeedbackService', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/nomination.ts b/packages/sapsuccessfactors/endpoints/nomination.ts new file mode 100644 index 000000000..a946a6452 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/nomination.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Delete Nomination +// Remove a nominee from a position or talent pool nomination. +export const deleteNominationPositionTalentPool: SapsuccessfactorsEndpoints['deleteNominationPositionTalentPool'] = + async (ctx, input) => { + const { nomination_id } = (input ?? {}) as { nomination_id?: string }; + const resourcePath = nomination_id + ? `odata/v4/NominationService.svc/Nomination(${nomination_id})` + : 'odata/v4/NominationService.svc/Nomination'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['deleteNominationPositionTalentPool'] + >(resourcePath, ctx.key, { method: 'DELETE' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.nomination.deleteNominationPositionTalentPool', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/odata.ts b/packages/sapsuccessfactors/endpoints/odata.ts new file mode 100644 index 000000000..4587a69c9 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/odata.ts @@ -0,0 +1,84 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Calibration Session Metadata +// Get OData metadata / available entity sets for CalSession.svc. +export const getOdataMetadataCalibSessionService: SapsuccessfactorsEndpoints['getOdataMetadataCalibSessionService'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getOdataMetadataCalibSessionService'] + >('odata/v4/CalSession.svc/$metadata', ctx.key, { method: 'GET' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.odata.getOdataMetadataCalibSessionService', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Onboarding Additional Services Metadata +// Get metadata for Onboarding Additional Services (incl. username update ops). +export const getOdataMetadataOnboardingAddl: SapsuccessfactorsEndpoints['getOdataMetadataOnboardingAddl'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getOdataMetadataOnboardingAddl'] + >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.odata.getOdataMetadataOnboardingAddl', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Nomination Service Metadata +// Get OData metadata for the Nomination service. +export const getOdataMetadataForNominationService: SapsuccessfactorsEndpoints['getOdataMetadataForNominationService'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getOdataMetadataForNominationService'] + >('odata/v4/NominationService.svc/$metadata', ctx.key, { method: 'GET' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.odata.getOdataMetadataForNominationService', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get User Entity Metadata +// Retrieve OData metadata for the User entity. +export const getOdataUserMetadata: SapsuccessfactorsEndpoints['getOdataUserMetadata'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getOdataUserMetadata'] + >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.odata.getOdataUserMetadata', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Clock In/Out Integration Metadata +// Get OData metadata for the Clock In/Clock Out Integration service. +export const getOdataMetadataClockInclockOut: SapsuccessfactorsEndpoints['getOdataMetadataClockInclockOut'] = + async (ctx, input) => { + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getOdataMetadataClockInclockOut'] + >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.odata.getOdataMetadataClockInclockOut', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/onb2.ts b/packages/sapsuccessfactors/endpoints/onb2.ts new file mode 100644 index 000000000..5c4e37918 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/onb2.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Onboarding 2.0 Processes +// Retrieve Onboarding 2.0 process records for new hires. +export const getOnb2Process: SapsuccessfactorsEndpoints['getOnb2Process'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getOnb2Process'] + >('odata/v2/ONB2Process', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.onb2.getOnb2Process', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/onboardee.ts b/packages/sapsuccessfactors/endpoints/onboardee.ts new file mode 100644 index 000000000..a49f33821 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/onboardee.ts @@ -0,0 +1,26 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Create Onboardee +// Create a new onboardee in Onboarding 2.0 (new hire or rehire). +export const createOnboardee: SapsuccessfactorsEndpoints['createOnboardee'] = + async (ctx, input) => { + const { body, ...rest } = (input ?? {}) as { + body?: Record; + }; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['createOnboardee'] + >('odata/v2/Onboardee', ctx.key, { + method: 'POST', + body: (body ?? rest) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.onboardee.createOnboardee', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/pending.ts b/packages/sapsuccessfactors/endpoints/pending.ts new file mode 100644 index 000000000..729375ded --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/pending.ts @@ -0,0 +1,27 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Pending Feedback Requests +// Query pending feedback requests. +export const getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoints['getPendingFeedbackRequestsFeedback'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getPendingFeedbackRequestsFeedback'] + >('odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', ctx.key, { + method: 'GET', + query, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.pending.getPendingFeedbackRequestsFeedback', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/per.ts b/packages/sapsuccessfactors/endpoints/per.ts new file mode 100644 index 000000000..7ca180857 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/per.ts @@ -0,0 +1,69 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Person by ID +// Retrieve core person info for an employee by external person ID. +export const getPerPersonById: SapsuccessfactorsEndpoints['getPerPersonById'] = + async (ctx, input) => { + const { person_id_external, ...query } = (input ?? {}) as { + person_id_external?: string; + }; + const resourcePath = person_id_external + ? `odata/v2/PerPerson('${person_id_external}')` + : 'odata/v2/PerPerson'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getPerPersonById'] + >(resourcePath, ctx.key, { + method: 'GET', + query: query as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.per.getPerPersonById', + input ?? {}, + 'completed', + ); + return response; + }; + +// List Person Records +// Retrieve person records (latest active record per person). +export const listPerPerson: SapsuccessfactorsEndpoints['listPerPerson'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['listPerPerson'] + >('odata/v2/PerPerson', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.per.listPerPerson', + input ?? {}, + 'completed', + ); + return response; + }; + +// Get Personal Information Records +// Retrieve biographical info, emergency contacts, social/email data. +export const getPerPersonal: SapsuccessfactorsEndpoints['getPerPersonal'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getPerPersonal'] + >('odata/v2/PerPersonal', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.per.getPerPersonal', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/picklist.ts b/packages/sapsuccessfactors/endpoints/picklist.ts new file mode 100644 index 000000000..f9354b8be --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/picklist.ts @@ -0,0 +1,43 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Picklist +// Retrieve picklist definitions (selectable value lists). +export const getPicklist: SapsuccessfactorsEndpoints['getPicklist'] = async ( + ctx, + input, +) => { + const query = input as Record; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getPicklist'] + >('odata/v2/Picklist', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.picklist.getPicklist', + input ?? {}, + 'completed', + ); + return response; +}; + +// Get Picklist Option +// Retrieve picklist option values with localized labels. +export const getPicklistOption: SapsuccessfactorsEndpoints['getPicklistOption'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getPicklistOption'] + >('odata/v2/PicklistOption', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.picklist.getPicklistOption', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/position.ts b/packages/sapsuccessfactors/endpoints/position.ts new file mode 100644 index 000000000..3c73d3b25 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/position.ts @@ -0,0 +1,23 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Position +// Retrieve position management records (structure and hierarchy). +export const getPosition: SapsuccessfactorsEndpoints['getPosition'] = async ( + ctx, + input, +) => { + const query = input as Record; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getPosition'] + >('odata/v2/Position', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.position.getPosition', + input ?? {}, + 'completed', + ); + return response; +}; diff --git a/packages/sapsuccessfactors/endpoints/query.ts b/packages/sapsuccessfactors/endpoints/query.ts new file mode 100644 index 000000000..6c3fdff89 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/query.ts @@ -0,0 +1,47 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Query All Available Clock In/Clock Out Groups +// Retrieve all configured clock in/clock out groups. +export const queryAllAvailableClockClockOut: SapsuccessfactorsEndpoints['queryAllAvailableClockClockOut'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['queryAllAvailableClockClockOut'] + >('odata/v2/ClockInClockOutGroup', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.query.queryAllAvailableClockClockOut', + input ?? {}, + 'completed', + ); + return response; + }; + +// Query Clock In/Clock Out Group By Code +// Retrieve one clock in/out group by code, optionally with time event types. +export const queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoints['queryClockClockOutGroupCodeTime'] = + async (ctx, input) => { + const { code, ...query } = (input ?? {}) as { code?: string }; + const resourcePath = code + ? `odata/v2/ClockInClockOutGroup('${code}')` + : 'odata/v2/ClockInClockOutGroup'; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['queryClockClockOutGroupCodeTime'] + >(resourcePath, ctx.key, { + method: 'GET', + query: query as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.query.queryClockClockOutGroupCodeTime', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/successor.ts b/packages/sapsuccessfactors/endpoints/successor.ts new file mode 100644 index 000000000..ddd574ddd --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/successor.ts @@ -0,0 +1,26 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Create or Update Successor Nomination +// Create/update a successor nomination for a position or talent pool. +export const createUpdateSuccessorNomination: SapsuccessfactorsEndpoints['createUpdateSuccessorNomination'] = + async (ctx, input) => { + const { body, ...rest } = (input ?? {}) as { + body?: Record; + }; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['createUpdateSuccessorNomination'] + >('odata/v4/NominationService.svc/Nomination', ctx.key, { + method: 'POST', + body: (body ?? rest) as Record, + }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.successor.createUpdateSuccessorNomination', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/talent.ts b/packages/sapsuccessfactors/endpoints/talent.ts new file mode 100644 index 000000000..4c800c212 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/talent.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Talent Pool +// Retrieve talent pool records including members and nominations. +export const getTalentPool: SapsuccessfactorsEndpoints['getTalentPool'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getTalentPool'] + >('odata/v2/TalentPool', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.talent.getTalentPool', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/temporary.ts b/packages/sapsuccessfactors/endpoints/temporary.ts new file mode 100644 index 000000000..52e2af831 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/temporary.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Temporary Time Information +// Retrieve temporary work schedules assigned to employees. +export const getTemporaryTimeInformation: SapsuccessfactorsEndpoints['getTemporaryTimeInformation'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getTemporaryTimeInformation'] + >('odata/v2/TemporaryTimeInfo', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.temporary.getTemporaryTimeInformation', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/time.ts b/packages/sapsuccessfactors/endpoints/time.ts new file mode 100644 index 000000000..ece81928a --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/time.ts @@ -0,0 +1,24 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Time Account Snapshot +// Retrieve time account balances for leave liability / payroll as-of a date. +export const getTimeAccountSnapshot: SapsuccessfactorsEndpoints['getTimeAccountSnapshot'] = + async (ctx, input) => { + const query = input as Record< + string, + string | number | boolean | undefined + >; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getTimeAccountSnapshot'] + >('odata/v2/TimeAccountSnapshot', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.time.getTimeAccountSnapshot', + input ?? {}, + 'completed', + ); + return response; + }; diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts new file mode 100644 index 000000000..3d87885ff --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -0,0 +1,1783 @@ +import { z } from 'zod'; + +// Approve Calibration Session +const ApproveCalibrationSessionInputSchema = z.object({ + session_id: z.string(), +}); +export type ApproveCalibrationSessionInput = z.infer< + typeof ApproveCalibrationSessionInputSchema +>; + +const ApproveCalibrationSessionResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type ApproveCalibrationSessionResponse = z.infer< + typeof ApproveCalibrationSessionResponseSchema +>; + +// Get Calibration Session By ID +const GetCalibrationSessionByIdInputSchema = z.object({ + session_id: z.string(), + select: z.string().optional(), + expand: z.string().optional(), +}); +export type GetCalibrationSessionByIdInput = z.infer< + typeof GetCalibrationSessionByIdInputSchema +>; + +const GetCalibrationSessionByIdResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCalibrationSessionByIdResponse = z.infer< + typeof GetCalibrationSessionByIdResponseSchema +>; + +// Get Calibration Sessions +const GetCalibrationSessionsInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetCalibrationSessionsInput = z.infer< + typeof GetCalibrationSessionsInputSchema +>; + +const GetCalibrationSessionsResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCalibrationSessionsResponse = z.infer< + typeof GetCalibrationSessionsResponseSchema +>; + +// Get Calibration Session Metadata +const GetOdataMetadataCalibSessionServiceInputSchema = z.object({}).optional(); +export type GetOdataMetadataCalibSessionServiceInput = z.infer< + typeof GetOdataMetadataCalibSessionServiceInputSchema +>; + +const GetOdataMetadataCalibSessionServiceResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetOdataMetadataCalibSessionServiceResponse = z.infer< + typeof GetOdataMetadataCalibSessionServiceResponseSchema +>; + +// Get Calibration Subject By ID +const GetCalibrationSubjectByIdInputSchema = z.object({ + subject_id: z.string(), + select: z.string().optional(), + expand: z.string().optional(), +}); +export type GetCalibrationSubjectByIdInput = z.infer< + typeof GetCalibrationSubjectByIdInputSchema +>; + +const GetCalibrationSubjectByIdResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCalibrationSubjectByIdResponse = z.infer< + typeof GetCalibrationSubjectByIdResponseSchema +>; + +// Get Calibration Subject Ratings +const GetCalibrationSubjectRatingsInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), + session_id: z.string(), +}); +export type GetCalibrationSubjectRatingsInput = z.infer< + typeof GetCalibrationSubjectRatingsInputSchema +>; + +const GetCalibrationSubjectRatingsResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCalibrationSubjectRatingsResponse = z.infer< + typeof GetCalibrationSubjectRatingsResponseSchema +>; + +// Update Calibration Subject Ratings +const UpdateCalibrationSubjectRatingsInputSchema = z.object({ + subject_id: z.string(), + body: z.record(z.string(), z.unknown()), +}); +export type UpdateCalibrationSubjectRatingsInput = z.infer< + typeof UpdateCalibrationSubjectRatingsInputSchema +>; + +const UpdateCalibrationSubjectRatingsResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type UpdateCalibrationSubjectRatingsResponse = z.infer< + typeof UpdateCalibrationSubjectRatingsResponseSchema +>; + +// Create Onboardee +const CreateOnboardeeInputSchema = z.object({ + body: z.record(z.string(), z.unknown()), +}); +export type CreateOnboardeeInput = z.infer; + +const CreateOnboardeeResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type CreateOnboardeeResponse = z.infer< + typeof CreateOnboardeeResponseSchema +>; + +// Get Onboarding 2.0 Processes +const GetOnb2ProcessInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetOnb2ProcessInput = z.infer; + +const GetOnb2ProcessResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetOnb2ProcessResponse = z.infer< + typeof GetOnb2ProcessResponseSchema +>; + +// Get Onboarding Additional Services Metadata +const GetOdataMetadataOnboardingAddlInputSchema = z.object({}).optional(); +export type GetOdataMetadataOnboardingAddlInput = z.infer< + typeof GetOdataMetadataOnboardingAddlInputSchema +>; + +const GetOdataMetadataOnboardingAddlResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetOdataMetadataOnboardingAddlResponse = z.infer< + typeof GetOdataMetadataOnboardingAddlResponseSchema +>; + +// Update Username Post Hiring +const UpdateInternalUsernameNewHiresAfterInputSchema = z.object({ + user_id: z.string(), + new_username: z.string(), +}); +export type UpdateInternalUsernameNewHiresAfterInput = z.infer< + typeof UpdateInternalUsernameNewHiresAfterInputSchema +>; + +const UpdateInternalUsernameNewHiresAfterResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type UpdateInternalUsernameNewHiresAfterResponse = z.infer< + typeof UpdateInternalUsernameNewHiresAfterResponseSchema +>; + +// Create a Feedback Request +const CreateAFeedbackRequestInputSchema = z.object({ + body: z.record(z.string(), z.unknown()), +}); +export type CreateAFeedbackRequestInput = z.infer< + typeof CreateAFeedbackRequestInputSchema +>; + +const CreateAFeedbackRequestResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type CreateAFeedbackRequestResponse = z.infer< + typeof CreateAFeedbackRequestResponseSchema +>; + +// Get Feedback Records +const GetFeedbackRecordsServiceAvailableInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFeedbackRecordsServiceAvailableInput = z.infer< + typeof GetFeedbackRecordsServiceAvailableInputSchema +>; + +const GetFeedbackRecordsServiceAvailableResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFeedbackRecordsServiceAvailableResponse = z.infer< + typeof GetFeedbackRecordsServiceAvailableResponseSchema +>; + +// Get Pending Feedback Requests +const GetPendingFeedbackRequestsFeedbackInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetPendingFeedbackRequestsFeedbackInput = z.infer< + typeof GetPendingFeedbackRequestsFeedbackInputSchema +>; + +const GetPendingFeedbackRequestsFeedbackResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetPendingFeedbackRequestsFeedbackResponse = z.infer< + typeof GetPendingFeedbackRequestsFeedbackResponseSchema +>; + +// Give Feedback or Respond to Feedback Request +const GiveFeedbackOrRespondToAFeedbackRequestInputSchema = z.object({ + body: z.record(z.string(), z.unknown()), +}); +export type GiveFeedbackOrRespondToAFeedbackRequestInput = z.infer< + typeof GiveFeedbackOrRespondToAFeedbackRequestInputSchema +>; + +const GiveFeedbackOrRespondToAFeedbackRequestResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GiveFeedbackOrRespondToAFeedbackRequestResponse = z.infer< + typeof GiveFeedbackOrRespondToAFeedbackRequestResponseSchema +>; + +// Refresh Metadata for Continuous Feedback +const RefreshMetadataContFeedbackServiceInputSchema = z.object({}).optional(); +export type RefreshMetadataContFeedbackServiceInput = z.infer< + typeof RefreshMetadataContFeedbackServiceInputSchema +>; + +const RefreshMetadataContFeedbackServiceResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type RefreshMetadataContFeedbackServiceResponse = z.infer< + typeof RefreshMetadataContFeedbackServiceResponseSchema +>; + +// Create or Update Successor Nomination +const CreateUpdateSuccessorNominationInputSchema = z.object({ + body: z.record(z.string(), z.unknown()), +}); +export type CreateUpdateSuccessorNominationInput = z.infer< + typeof CreateUpdateSuccessorNominationInputSchema +>; + +const CreateUpdateSuccessorNominationResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type CreateUpdateSuccessorNominationResponse = z.infer< + typeof CreateUpdateSuccessorNominationResponseSchema +>; + +// Delete Nomination +const DeleteNominationPositionTalentPoolInputSchema = z.object({ + nomination_id: z.string(), +}); +export type DeleteNominationPositionTalentPoolInput = z.infer< + typeof DeleteNominationPositionTalentPoolInputSchema +>; + +const DeleteNominationPositionTalentPoolResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type DeleteNominationPositionTalentPoolResponse = z.infer< + typeof DeleteNominationPositionTalentPoolResponseSchema +>; + +// Get Nomination Service Metadata +const GetOdataMetadataForNominationServiceInputSchema = z.object({}).optional(); +export type GetOdataMetadataForNominationServiceInput = z.infer< + typeof GetOdataMetadataForNominationServiceInputSchema +>; + +const GetOdataMetadataForNominationServiceResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetOdataMetadataForNominationServiceResponse = z.infer< + typeof GetOdataMetadataForNominationServiceResponseSchema +>; + +// Get Talent Pool +const GetTalentPoolInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetTalentPoolInput = z.infer; + +const GetTalentPoolResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetTalentPoolResponse = z.infer; + +// Get Application Interview +const GetApplicationInterviewInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetApplicationInterviewInput = z.infer< + typeof GetApplicationInterviewInputSchema +>; + +const GetApplicationInterviewResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetApplicationInterviewResponse = z.infer< + typeof GetApplicationInterviewResponseSchema +>; + +// Get Interview Overall Assessment +const GetInterviewOverallAssessmentInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetInterviewOverallAssessmentInput = z.infer< + typeof GetInterviewOverallAssessmentInputSchema +>; + +const GetInterviewOverallAssessmentResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetInterviewOverallAssessmentResponse = z.infer< + typeof GetInterviewOverallAssessmentResponseSchema +>; + +// Get Job Application +const GetJobApplicationInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetJobApplicationInput = z.infer< + typeof GetJobApplicationInputSchema +>; + +const GetJobApplicationResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetJobApplicationResponse = z.infer< + typeof GetJobApplicationResponseSchema +>; + +// Get Job Requisition +const GetJobRequisitionInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetJobRequisitionInput = z.infer< + typeof GetJobRequisitionInputSchema +>; + +const GetJobRequisitionResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetJobRequisitionResponse = z.infer< + typeof GetJobRequisitionResponseSchema +>; + +// Get Job Requisition Screening Questions +const GetJobReqScreeningQuestionInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetJobReqScreeningQuestionInput = z.infer< + typeof GetJobReqScreeningQuestionInputSchema +>; + +const GetJobReqScreeningQuestionResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetJobReqScreeningQuestionResponse = z.infer< + typeof GetJobReqScreeningQuestionResponseSchema +>; + +// List Candidates +const ListCandidatesInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type ListCandidatesInput = z.infer; + +const ListCandidatesResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type ListCandidatesResponse = z.infer< + typeof ListCandidatesResponseSchema +>; + +// Get FOBusinessUnit +const GetFoBusinessUnitInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoBusinessUnitInput = z.infer< + typeof GetFoBusinessUnitInputSchema +>; + +const GetFoBusinessUnitResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoBusinessUnitResponse = z.infer< + typeof GetFoBusinessUnitResponseSchema +>; + +// Get FOCompany Records +const GetFoCompanyInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoCompanyInput = z.infer; + +const GetFoCompanyResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoCompanyResponse = z.infer; + +// Get Foundation Object Cost Centers +const GetFoCostCenterInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoCostCenterInput = z.infer; + +const GetFoCostCenterResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoCostCenterResponse = z.infer< + typeof GetFoCostCenterResponseSchema +>; + +// Get FODepartment Records +const GetFoDepartmentInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoDepartmentInput = z.infer; + +const GetFoDepartmentResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoDepartmentResponse = z.infer< + typeof GetFoDepartmentResponseSchema +>; + +// Get Foundation Object Job Codes +const GetFoJobCodeInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoJobCodeInput = z.infer; + +const GetFoJobCodeResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoJobCodeResponse = z.infer; + +// Get Job Functions +const GetFoJobFunctionInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoJobFunctionInput = z.infer; + +const GetFoJobFunctionResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoJobFunctionResponse = z.infer< + typeof GetFoJobFunctionResponseSchema +>; + +// Get Foundation Object Location +const GetFoLocationInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoLocationInput = z.infer; + +const GetFoLocationResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoLocationResponse = z.infer; + +// Get FOPayGroup +const GetFoPayGroupInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFoPayGroupInput = z.infer; + +const GetFoPayGroupResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFoPayGroupResponse = z.infer; + +// Get Position +const GetPositionInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetPositionInput = z.infer; + +const GetPositionResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetPositionResponse = z.infer; + +// Get Custom MDF Object +const GetCustomMdfObjectInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), + custom_object: z.string(), +}); +export type GetCustomMdfObjectInput = z.infer< + typeof GetCustomMdfObjectInputSchema +>; + +const GetCustomMdfObjectResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCustomMdfObjectResponse = z.infer< + typeof GetCustomMdfObjectResponseSchema +>; + +// Get Picklist +const GetPicklistInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetPicklistInput = z.infer; + +const GetPicklistResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetPicklistResponse = z.infer; + +// Get Picklist Option +const GetPicklistOptionInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetPicklistOptionInput = z.infer< + typeof GetPicklistOptionInputSchema +>; + +const GetPicklistOptionResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetPicklistOptionResponse = z.infer< + typeof GetPicklistOptionResponseSchema +>; + +// Get Current User +const GetCurrentUserInputSchema = z.object({ + select: z.string().optional(), + expand: z.string().optional(), +}); +export type GetCurrentUserInput = z.infer; + +const GetCurrentUserResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCurrentUserResponse = z.infer< + typeof GetCurrentUserResponseSchema +>; + +// Get User Entity Metadata +const GetOdataUserMetadataInputSchema = z.object({}).optional(); +export type GetOdataUserMetadataInput = z.infer< + typeof GetOdataUserMetadataInputSchema +>; + +const GetOdataUserMetadataResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetOdataUserMetadataResponse = z.infer< + typeof GetOdataUserMetadataResponseSchema +>; + +// List Users +const ListUsersInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type ListUsersInput = z.infer; + +const ListUsersResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type ListUsersResponse = z.infer; + +// Get Person by ID +const GetPerPersonByIdInputSchema = z.object({ + person_id_external: z.string(), + select: z.string().optional(), + expand: z.string().optional(), +}); +export type GetPerPersonByIdInput = z.infer; + +const GetPerPersonByIdResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetPerPersonByIdResponse = z.infer< + typeof GetPerPersonByIdResponseSchema +>; + +// List Person Records +const ListPerPersonInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type ListPerPersonInput = z.infer; + +const ListPerPersonResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type ListPerPersonResponse = z.infer; + +// Get Personal Information Records +const GetPerPersonalInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetPerPersonalInput = z.infer; + +const GetPerPersonalResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetPerPersonalResponse = z.infer< + typeof GetPerPersonalResponseSchema +>; + +// Get Background Education +const GetBackgroundEducationInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetBackgroundEducationInput = z.infer< + typeof GetBackgroundEducationInputSchema +>; + +const GetBackgroundEducationResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetBackgroundEducationResponse = z.infer< + typeof GetBackgroundEducationResponseSchema +>; + +// Get Background Mobility +const GetBackgroundMobilityInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetBackgroundMobilityInput = z.infer< + typeof GetBackgroundMobilityInputSchema +>; + +const GetBackgroundMobilityResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetBackgroundMobilityResponse = z.infer< + typeof GetBackgroundMobilityResponseSchema +>; + +// List Employee Employment Records +const ListEmpEmploymentInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type ListEmpEmploymentInput = z.infer< + typeof ListEmpEmploymentInputSchema +>; + +const ListEmpEmploymentResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type ListEmpEmploymentResponse = z.infer< + typeof ListEmpEmploymentResponseSchema +>; + +// Get Employee Employment Termination +const GetEmpEmploymentTerminationInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetEmpEmploymentTerminationInput = z.infer< + typeof GetEmpEmploymentTerminationInputSchema +>; + +const GetEmpEmploymentTerminationResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetEmpEmploymentTerminationResponse = z.infer< + typeof GetEmpEmploymentTerminationResponseSchema +>; + +// Get Work Order +const GetWorkOrderInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetWorkOrderInput = z.infer; + +const GetWorkOrderResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetWorkOrderResponse = z.infer; + +// Get Recurring Pay Components +const GetEmpPayCompRecurringInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetEmpPayCompRecurringInput = z.infer< + typeof GetEmpPayCompRecurringInputSchema +>; + +const GetEmpPayCompRecurringResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetEmpPayCompRecurringResponse = z.infer< + typeof GetEmpPayCompRecurringResponseSchema +>; + +// Get Non-Recurring Pay Components +const GetEmpPayCompNonRecurringInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetEmpPayCompNonRecurringInput = z.infer< + typeof GetEmpPayCompNonRecurringInputSchema +>; + +const GetEmpPayCompNonRecurringResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetEmpPayCompNonRecurringResponse = z.infer< + typeof GetEmpPayCompNonRecurringResponseSchema +>; + +// Get Goal Plan Template +const GetGoalPlanTemplateInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetGoalPlanTemplateInput = z.infer< + typeof GetGoalPlanTemplateInputSchema +>; + +const GetGoalPlanTemplateResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetGoalPlanTemplateResponse = z.infer< + typeof GetGoalPlanTemplateResponseSchema +>; + +// Get Goals By Plan +const GetGoalsByPlanInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), + goal_plan_id: z.string(), +}); +export type GetGoalsByPlanInput = z.infer; + +const GetGoalsByPlanResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetGoalsByPlanResponse = z.infer< + typeof GetGoalsByPlanResponseSchema +>; + +// Get Form Content +const GetFormContentInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetFormContentInput = z.infer; + +const GetFormContentResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetFormContentResponse = z.infer< + typeof GetFormContentResponseSchema +>; + +// Create Learning Activities Bulk +const CreateLearningActivitiesBulkInputSchema = z.object({ + body: z.record(z.string(), z.unknown()), +}); +export type CreateLearningActivitiesBulkInput = z.infer< + typeof CreateLearningActivitiesBulkInputSchema +>; + +const CreateLearningActivitiesBulkResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type CreateLearningActivitiesBulkResponse = z.infer< + typeof CreateLearningActivitiesBulkResponseSchema +>; + +// Get CDP Learning Metadata +const GetCdpLearningMetadataInputSchema = z.object({}).optional(); +export type GetCdpLearningMetadataInput = z.infer< + typeof GetCdpLearningMetadataInputSchema +>; + +const GetCdpLearningMetadataResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetCdpLearningMetadataResponse = z.infer< + typeof GetCdpLearningMetadataResponseSchema +>; + +// Refresh CDP Learning Metadata +const RefreshCdpLearningMetadataInputSchema = z.object({}).optional(); +export type RefreshCdpLearningMetadataInput = z.infer< + typeof RefreshCdpLearningMetadataInputSchema +>; + +const RefreshCdpLearningMetadataResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type RefreshCdpLearningMetadataResponse = z.infer< + typeof RefreshCdpLearningMetadataResponseSchema +>; + +// Get Employee Time +const GetEmployeeTimeInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetEmployeeTimeInput = z.infer; + +const GetEmployeeTimeResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetEmployeeTimeResponse = z.infer< + typeof GetEmployeeTimeResponseSchema +>; + +// Get Employee Timesheet +const GetEmployeeTimesheetInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetEmployeeTimesheetInput = z.infer< + typeof GetEmployeeTimesheetInputSchema +>; + +const GetEmployeeTimesheetResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetEmployeeTimesheetResponse = z.infer< + typeof GetEmployeeTimesheetResponseSchema +>; + +// Get Temporary Time Information +const GetTemporaryTimeInformationInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetTemporaryTimeInformationInput = z.infer< + typeof GetTemporaryTimeInformationInputSchema +>; + +const GetTemporaryTimeInformationResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetTemporaryTimeInformationResponse = z.infer< + typeof GetTemporaryTimeInformationResponseSchema +>; + +// Get Time Account Snapshot +const GetTimeAccountSnapshotInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type GetTimeAccountSnapshotInput = z.infer< + typeof GetTimeAccountSnapshotInputSchema +>; + +const GetTimeAccountSnapshotResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetTimeAccountSnapshotResponse = z.infer< + typeof GetTimeAccountSnapshotResponseSchema +>; + +// Get Clock In/Out Integration Metadata +const GetOdataMetadataClockInclockOutInputSchema = z.object({}).optional(); +export type GetOdataMetadataClockInclockOutInput = z.infer< + typeof GetOdataMetadataClockInclockOutInputSchema +>; + +const GetOdataMetadataClockInclockOutResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type GetOdataMetadataClockInclockOutResponse = z.infer< + typeof GetOdataMetadataClockInclockOutResponseSchema +>; + +// Query All Available Clock In/Clock Out Groups +const QueryAllAvailableClockClockOutInputSchema = z.object({ + filter: z.string().optional(), + select: z.string().optional(), + expand: z.string().optional(), + top: z.number().int().optional(), + skip: z.number().int().optional(), + orderby: z.string().optional(), +}); +export type QueryAllAvailableClockClockOutInput = z.infer< + typeof QueryAllAvailableClockClockOutInputSchema +>; + +const QueryAllAvailableClockClockOutResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type QueryAllAvailableClockClockOutResponse = z.infer< + typeof QueryAllAvailableClockClockOutResponseSchema +>; + +// Query Clock In/Clock Out Group By Code +const QueryClockClockOutGroupCodeTimeInputSchema = z.object({ + code: z.string(), + select: z.string().optional(), + expand: z.string().optional(), +}); +export type QueryClockClockOutGroupCodeTimeInput = z.infer< + typeof QueryClockClockOutGroupCodeTimeInputSchema +>; + +const QueryClockClockOutGroupCodeTimeResponseSchema = z + .object({ + d: z + .object({ + results: z.array(z.unknown()), + id: z.string(), + status: z.string(), + }) + .catchall(z.unknown()), + }) + .passthrough(); +export type QueryClockClockOutGroupCodeTimeResponse = z.infer< + typeof QueryClockClockOutGroupCodeTimeResponseSchema +>; + +export const SapsuccessfactorsEndpointInputSchemas = { + approveCalibrationSession: ApproveCalibrationSessionInputSchema, + getCalibrationSessionById: GetCalibrationSessionByIdInputSchema, + getCalibrationSessions: GetCalibrationSessionsInputSchema, + getOdataMetadataCalibSessionService: + GetOdataMetadataCalibSessionServiceInputSchema, + getCalibrationSubjectById: GetCalibrationSubjectByIdInputSchema, + getCalibrationSubjectRatings: GetCalibrationSubjectRatingsInputSchema, + updateCalibrationSubjectRatings: UpdateCalibrationSubjectRatingsInputSchema, + createOnboardee: CreateOnboardeeInputSchema, + getOnb2Process: GetOnb2ProcessInputSchema, + getOdataMetadataOnboardingAddl: GetOdataMetadataOnboardingAddlInputSchema, + updateInternalUsernameNewHiresAfter: + UpdateInternalUsernameNewHiresAfterInputSchema, + createAFeedbackRequest: CreateAFeedbackRequestInputSchema, + getFeedbackRecordsServiceAvailable: + GetFeedbackRecordsServiceAvailableInputSchema, + getPendingFeedbackRequestsFeedback: + GetPendingFeedbackRequestsFeedbackInputSchema, + giveFeedbackOrRespondToAFeedbackRequest: + GiveFeedbackOrRespondToAFeedbackRequestInputSchema, + refreshMetadataContFeedbackService: + RefreshMetadataContFeedbackServiceInputSchema, + createUpdateSuccessorNomination: CreateUpdateSuccessorNominationInputSchema, + deleteNominationPositionTalentPool: + DeleteNominationPositionTalentPoolInputSchema, + getOdataMetadataForNominationService: + GetOdataMetadataForNominationServiceInputSchema, + getTalentPool: GetTalentPoolInputSchema, + getApplicationInterview: GetApplicationInterviewInputSchema, + getInterviewOverallAssessment: GetInterviewOverallAssessmentInputSchema, + getJobApplication: GetJobApplicationInputSchema, + getJobRequisition: GetJobRequisitionInputSchema, + getJobReqScreeningQuestion: GetJobReqScreeningQuestionInputSchema, + listCandidates: ListCandidatesInputSchema, + getFoBusinessUnit: GetFoBusinessUnitInputSchema, + getFoCompany: GetFoCompanyInputSchema, + getFoCostCenter: GetFoCostCenterInputSchema, + getFoDepartment: GetFoDepartmentInputSchema, + getFoJobCode: GetFoJobCodeInputSchema, + getFoJobFunction: GetFoJobFunctionInputSchema, + getFoLocation: GetFoLocationInputSchema, + getFoPayGroup: GetFoPayGroupInputSchema, + getPosition: GetPositionInputSchema, + getCustomMdfObject: GetCustomMdfObjectInputSchema, + getPicklist: GetPicklistInputSchema, + getPicklistOption: GetPicklistOptionInputSchema, + getCurrentUser: GetCurrentUserInputSchema, + getOdataUserMetadata: GetOdataUserMetadataInputSchema, + listUsers: ListUsersInputSchema, + getPerPersonById: GetPerPersonByIdInputSchema, + listPerPerson: ListPerPersonInputSchema, + getPerPersonal: GetPerPersonalInputSchema, + getBackgroundEducation: GetBackgroundEducationInputSchema, + getBackgroundMobility: GetBackgroundMobilityInputSchema, + listEmpEmployment: ListEmpEmploymentInputSchema, + getEmpEmploymentTermination: GetEmpEmploymentTerminationInputSchema, + getWorkOrder: GetWorkOrderInputSchema, + getEmpPayCompRecurring: GetEmpPayCompRecurringInputSchema, + getEmpPayCompNonRecurring: GetEmpPayCompNonRecurringInputSchema, + getGoalPlanTemplate: GetGoalPlanTemplateInputSchema, + getGoalsByPlan: GetGoalsByPlanInputSchema, + getFormContent: GetFormContentInputSchema, + createLearningActivitiesBulk: CreateLearningActivitiesBulkInputSchema, + getCdpLearningMetadata: GetCdpLearningMetadataInputSchema, + refreshCdpLearningMetadata: RefreshCdpLearningMetadataInputSchema, + getEmployeeTime: GetEmployeeTimeInputSchema, + getEmployeeTimesheet: GetEmployeeTimesheetInputSchema, + getTemporaryTimeInformation: GetTemporaryTimeInformationInputSchema, + getTimeAccountSnapshot: GetTimeAccountSnapshotInputSchema, + getOdataMetadataClockInclockOut: GetOdataMetadataClockInclockOutInputSchema, + queryAllAvailableClockClockOut: QueryAllAvailableClockClockOutInputSchema, + queryClockClockOutGroupCodeTime: QueryClockClockOutGroupCodeTimeInputSchema, +} as const; + +export type SapsuccessfactorsEndpointInputs = { + [K in keyof typeof SapsuccessfactorsEndpointInputSchemas]: z.infer< + (typeof SapsuccessfactorsEndpointInputSchemas)[K] + >; +}; + +export const SapsuccessfactorsEndpointOutputSchemas = { + approveCalibrationSession: ApproveCalibrationSessionResponseSchema, + getCalibrationSessionById: GetCalibrationSessionByIdResponseSchema, + getCalibrationSessions: GetCalibrationSessionsResponseSchema, + getOdataMetadataCalibSessionService: + GetOdataMetadataCalibSessionServiceResponseSchema, + getCalibrationSubjectById: GetCalibrationSubjectByIdResponseSchema, + getCalibrationSubjectRatings: GetCalibrationSubjectRatingsResponseSchema, + updateCalibrationSubjectRatings: + UpdateCalibrationSubjectRatingsResponseSchema, + createOnboardee: CreateOnboardeeResponseSchema, + getOnb2Process: GetOnb2ProcessResponseSchema, + getOdataMetadataOnboardingAddl: GetOdataMetadataOnboardingAddlResponseSchema, + updateInternalUsernameNewHiresAfter: + UpdateInternalUsernameNewHiresAfterResponseSchema, + createAFeedbackRequest: CreateAFeedbackRequestResponseSchema, + getFeedbackRecordsServiceAvailable: + GetFeedbackRecordsServiceAvailableResponseSchema, + getPendingFeedbackRequestsFeedback: + GetPendingFeedbackRequestsFeedbackResponseSchema, + giveFeedbackOrRespondToAFeedbackRequest: + GiveFeedbackOrRespondToAFeedbackRequestResponseSchema, + refreshMetadataContFeedbackService: + RefreshMetadataContFeedbackServiceResponseSchema, + createUpdateSuccessorNomination: + CreateUpdateSuccessorNominationResponseSchema, + deleteNominationPositionTalentPool: + DeleteNominationPositionTalentPoolResponseSchema, + getOdataMetadataForNominationService: + GetOdataMetadataForNominationServiceResponseSchema, + getTalentPool: GetTalentPoolResponseSchema, + getApplicationInterview: GetApplicationInterviewResponseSchema, + getInterviewOverallAssessment: GetInterviewOverallAssessmentResponseSchema, + getJobApplication: GetJobApplicationResponseSchema, + getJobRequisition: GetJobRequisitionResponseSchema, + getJobReqScreeningQuestion: GetJobReqScreeningQuestionResponseSchema, + listCandidates: ListCandidatesResponseSchema, + getFoBusinessUnit: GetFoBusinessUnitResponseSchema, + getFoCompany: GetFoCompanyResponseSchema, + getFoCostCenter: GetFoCostCenterResponseSchema, + getFoDepartment: GetFoDepartmentResponseSchema, + getFoJobCode: GetFoJobCodeResponseSchema, + getFoJobFunction: GetFoJobFunctionResponseSchema, + getFoLocation: GetFoLocationResponseSchema, + getFoPayGroup: GetFoPayGroupResponseSchema, + getPosition: GetPositionResponseSchema, + getCustomMdfObject: GetCustomMdfObjectResponseSchema, + getPicklist: GetPicklistResponseSchema, + getPicklistOption: GetPicklistOptionResponseSchema, + getCurrentUser: GetCurrentUserResponseSchema, + getOdataUserMetadata: GetOdataUserMetadataResponseSchema, + listUsers: ListUsersResponseSchema, + getPerPersonById: GetPerPersonByIdResponseSchema, + listPerPerson: ListPerPersonResponseSchema, + getPerPersonal: GetPerPersonalResponseSchema, + getBackgroundEducation: GetBackgroundEducationResponseSchema, + getBackgroundMobility: GetBackgroundMobilityResponseSchema, + listEmpEmployment: ListEmpEmploymentResponseSchema, + getEmpEmploymentTermination: GetEmpEmploymentTerminationResponseSchema, + getWorkOrder: GetWorkOrderResponseSchema, + getEmpPayCompRecurring: GetEmpPayCompRecurringResponseSchema, + getEmpPayCompNonRecurring: GetEmpPayCompNonRecurringResponseSchema, + getGoalPlanTemplate: GetGoalPlanTemplateResponseSchema, + getGoalsByPlan: GetGoalsByPlanResponseSchema, + getFormContent: GetFormContentResponseSchema, + createLearningActivitiesBulk: CreateLearningActivitiesBulkResponseSchema, + getCdpLearningMetadata: GetCdpLearningMetadataResponseSchema, + refreshCdpLearningMetadata: RefreshCdpLearningMetadataResponseSchema, + getEmployeeTime: GetEmployeeTimeResponseSchema, + getEmployeeTimesheet: GetEmployeeTimesheetResponseSchema, + getTemporaryTimeInformation: GetTemporaryTimeInformationResponseSchema, + getTimeAccountSnapshot: GetTimeAccountSnapshotResponseSchema, + getOdataMetadataClockInclockOut: + GetOdataMetadataClockInclockOutResponseSchema, + queryAllAvailableClockClockOut: QueryAllAvailableClockClockOutResponseSchema, + queryClockClockOutGroupCodeTime: + QueryClockClockOutGroupCodeTimeResponseSchema, +} as const; + +export type SapsuccessfactorsEndpointOutputs = { + [K in keyof typeof SapsuccessfactorsEndpointOutputSchemas]: z.infer< + (typeof SapsuccessfactorsEndpointOutputSchemas)[K] + >; +}; diff --git a/packages/sapsuccessfactors/endpoints/users.ts b/packages/sapsuccessfactors/endpoints/users.ts new file mode 100644 index 000000000..82e465d94 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/users.ts @@ -0,0 +1,23 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// List Users +// Retrieve a list of all employee users. +export const listUsers: SapsuccessfactorsEndpoints['listUsers'] = async ( + ctx, + input, +) => { + const query = input as Record; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['listUsers'] + >('odata/v2/User', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.users.listUsers', + input ?? {}, + 'completed', + ); + return response; +}; diff --git a/packages/sapsuccessfactors/endpoints/work.ts b/packages/sapsuccessfactors/endpoints/work.ts new file mode 100644 index 000000000..1c5d708ff --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/work.ts @@ -0,0 +1,23 @@ +import { logEventFromContext } from 'corsair/core'; +import type { SapsuccessfactorsEndpoints } from '..'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { SapsuccessfactorsEndpointOutputs } from './types'; + +// Get Work Order +// Retrieve work order records for contingent worker management. +export const getWorkOrder: SapsuccessfactorsEndpoints['getWorkOrder'] = async ( + ctx, + input, +) => { + const query = input as Record; + const response = await makeSapsuccessfactorsRequest< + SapsuccessfactorsEndpointOutputs['getWorkOrder'] + >('odata/v2/WorkOrder', ctx.key, { method: 'GET', query }); + await logEventFromContext( + ctx, + 'sapsuccessfactors.work.getWorkOrder', + input ?? {}, + 'completed', + ); + return response; +}; diff --git a/packages/sapsuccessfactors/error-handlers.ts b/packages/sapsuccessfactors/error-handlers.ts new file mode 100644 index 000000000..5a4f4c19f --- /dev/null +++ b/packages/sapsuccessfactors/error-handlers.ts @@ -0,0 +1,31 @@ +import type { CorsairErrorHandler } from 'corsair/core'; +import { ApiError } from 'corsair/http'; + +export const errorHandlers = { + RATE_LIMIT_ERROR: { + match: (error: Error) => { + if (error instanceof ApiError && error.status === 429) return true; + const msg = error.message.toLowerCase(); + return msg.includes('rate_limited') || msg.includes('429'); + }, + handler: async (error: Error) => { + let retryAfterMs: number | undefined; + if (error instanceof ApiError && error.retryAfter !== undefined) { + retryAfterMs = error.retryAfter; + } + return { maxRetries: 5, headersRetryAfterMs: retryAfterMs }; + }, + }, + AUTH_ERROR: { + match: (error: Error) => { + if (error instanceof ApiError && error.status === 401) return true; + const msg = error.message.toLowerCase(); + return msg.includes('unauthorized') || msg.includes('invalid_auth'); + }, + handler: async () => ({ maxRetries: 0 }), + }, + DEFAULT: { + match: () => true, + handler: async () => ({ maxRetries: 0 }), + }, +} satisfies CorsairErrorHandler; diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts new file mode 100644 index 000000000..566e97879 --- /dev/null +++ b/packages/sapsuccessfactors/index.ts @@ -0,0 +1,951 @@ +import type { + AuthTypes, + BindEndpoints, + BindWebhooks, + CorsairEndpoint, + CorsairErrorHandler, + CorsairPlugin, + CorsairPluginContext, + KeyBuilderContext, + PickAuth, + PluginAuthConfig, + PluginPermissionsConfig, + RawWebhookRequest, + RequiredPluginEndpointMeta, +} from 'corsair/core'; +import { + A, + Application, + Approve, + Background, + Calibration, + Candidates, + Cdp, + Current, + Custom, + Emp, + Employee, + Feedback, + Fo, + Form, + Give, + Goal, + Goals, + Internal, + Interview, + Job, + Learning, + Metadata, + Nomination, + Odata, + Onb2, + Onboardee, + Pending, + Per, + Picklist, + Position, + Query, + Successor, + Talent, + Temporary, + Time, + Users, + Work, +} from './endpoints'; +import type { + SapsuccessfactorsEndpointInputs, + SapsuccessfactorsEndpointOutputs, +} from './endpoints/types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './endpoints/types'; +import { errorHandlers } from './error-handlers'; +import { SapsuccessfactorsSchema } from './schema'; +import { resolveSapsuccessfactorsOAuthWebhookTenantLink } from './webhooks/oauth-tenant-link'; +import { matchSapsuccessfactorsTenantWebhook } from './webhooks/tenant-matcher'; + +export type SapsuccessfactorsPluginOptions = { + /** Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. */ + authType?: PickAuth<'api_key'>; + key?: string; + webhookSecret?: string; + hooks?: InternalSapsuccessfactorsPlugin['hooks']; + webhookHooks?: InternalSapsuccessfactorsPlugin['webhookHooks']; + errorHandlers?: CorsairErrorHandler; + permissions?: PluginPermissionsConfig< + typeof sapsuccessfactorsEndpointsNested + >; +}; + +export type SapsuccessfactorsContext = CorsairPluginContext< + typeof SapsuccessfactorsSchema, + SapsuccessfactorsPluginOptions +>; +export type SapsuccessfactorsKeyBuilderContext = + KeyBuilderContext; +export type SapsuccessfactorsBoundEndpoints = BindEndpoints< + typeof sapsuccessfactorsEndpointsNested +>; + +type SapsuccessfactorsEndpoint< + K extends keyof SapsuccessfactorsEndpointOutputs, +> = CorsairEndpoint< + SapsuccessfactorsContext, + SapsuccessfactorsEndpointInputs[K], + SapsuccessfactorsEndpointOutputs[K] +>; + +export type SapsuccessfactorsEndpoints = { + approveCalibrationSession: SapsuccessfactorsEndpoint<'approveCalibrationSession'>; + getCalibrationSessionById: SapsuccessfactorsEndpoint<'getCalibrationSessionById'>; + getCalibrationSessions: SapsuccessfactorsEndpoint<'getCalibrationSessions'>; + getCalibrationSubjectById: SapsuccessfactorsEndpoint<'getCalibrationSubjectById'>; + getCalibrationSubjectRatings: SapsuccessfactorsEndpoint<'getCalibrationSubjectRatings'>; + updateCalibrationSubjectRatings: SapsuccessfactorsEndpoint<'updateCalibrationSubjectRatings'>; + getOdataMetadataCalibSessionService: SapsuccessfactorsEndpoint<'getOdataMetadataCalibSessionService'>; + getOdataMetadataOnboardingAddl: SapsuccessfactorsEndpoint<'getOdataMetadataOnboardingAddl'>; + getOdataMetadataForNominationService: SapsuccessfactorsEndpoint<'getOdataMetadataForNominationService'>; + getOdataUserMetadata: SapsuccessfactorsEndpoint<'getOdataUserMetadata'>; + getOdataMetadataClockInclockOut: SapsuccessfactorsEndpoint<'getOdataMetadataClockInclockOut'>; + createOnboardee: SapsuccessfactorsEndpoint<'createOnboardee'>; + getOnb2Process: SapsuccessfactorsEndpoint<'getOnb2Process'>; + updateInternalUsernameNewHiresAfter: SapsuccessfactorsEndpoint<'updateInternalUsernameNewHiresAfter'>; + createAFeedbackRequest: SapsuccessfactorsEndpoint<'createAFeedbackRequest'>; + getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoint<'getFeedbackRecordsServiceAvailable'>; + getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoint<'getPendingFeedbackRequestsFeedback'>; + giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoint<'giveFeedbackOrRespondToAFeedbackRequest'>; + refreshMetadataContFeedbackService: SapsuccessfactorsEndpoint<'refreshMetadataContFeedbackService'>; + createUpdateSuccessorNomination: SapsuccessfactorsEndpoint<'createUpdateSuccessorNomination'>; + deleteNominationPositionTalentPool: SapsuccessfactorsEndpoint<'deleteNominationPositionTalentPool'>; + getTalentPool: SapsuccessfactorsEndpoint<'getTalentPool'>; + getApplicationInterview: SapsuccessfactorsEndpoint<'getApplicationInterview'>; + getInterviewOverallAssessment: SapsuccessfactorsEndpoint<'getInterviewOverallAssessment'>; + getJobApplication: SapsuccessfactorsEndpoint<'getJobApplication'>; + getJobRequisition: SapsuccessfactorsEndpoint<'getJobRequisition'>; + getJobReqScreeningQuestion: SapsuccessfactorsEndpoint<'getJobReqScreeningQuestion'>; + listCandidates: SapsuccessfactorsEndpoint<'listCandidates'>; + getFoBusinessUnit: SapsuccessfactorsEndpoint<'getFoBusinessUnit'>; + getFoCompany: SapsuccessfactorsEndpoint<'getFoCompany'>; + getFoCostCenter: SapsuccessfactorsEndpoint<'getFoCostCenter'>; + getFoDepartment: SapsuccessfactorsEndpoint<'getFoDepartment'>; + getFoJobCode: SapsuccessfactorsEndpoint<'getFoJobCode'>; + getFoJobFunction: SapsuccessfactorsEndpoint<'getFoJobFunction'>; + getFoLocation: SapsuccessfactorsEndpoint<'getFoLocation'>; + getFoPayGroup: SapsuccessfactorsEndpoint<'getFoPayGroup'>; + getPosition: SapsuccessfactorsEndpoint<'getPosition'>; + getCustomMdfObject: SapsuccessfactorsEndpoint<'getCustomMdfObject'>; + getPicklist: SapsuccessfactorsEndpoint<'getPicklist'>; + getPicklistOption: SapsuccessfactorsEndpoint<'getPicklistOption'>; + getCurrentUser: SapsuccessfactorsEndpoint<'getCurrentUser'>; + listUsers: SapsuccessfactorsEndpoint<'listUsers'>; + getPerPersonById: SapsuccessfactorsEndpoint<'getPerPersonById'>; + listPerPerson: SapsuccessfactorsEndpoint<'listPerPerson'>; + getPerPersonal: SapsuccessfactorsEndpoint<'getPerPersonal'>; + getBackgroundEducation: SapsuccessfactorsEndpoint<'getBackgroundEducation'>; + getBackgroundMobility: SapsuccessfactorsEndpoint<'getBackgroundMobility'>; + listEmpEmployment: SapsuccessfactorsEndpoint<'listEmpEmployment'>; + getEmpEmploymentTermination: SapsuccessfactorsEndpoint<'getEmpEmploymentTermination'>; + getEmpPayCompRecurring: SapsuccessfactorsEndpoint<'getEmpPayCompRecurring'>; + getEmpPayCompNonRecurring: SapsuccessfactorsEndpoint<'getEmpPayCompNonRecurring'>; + getWorkOrder: SapsuccessfactorsEndpoint<'getWorkOrder'>; + getGoalPlanTemplate: SapsuccessfactorsEndpoint<'getGoalPlanTemplate'>; + getGoalsByPlan: SapsuccessfactorsEndpoint<'getGoalsByPlan'>; + getFormContent: SapsuccessfactorsEndpoint<'getFormContent'>; + createLearningActivitiesBulk: SapsuccessfactorsEndpoint<'createLearningActivitiesBulk'>; + getCdpLearningMetadata: SapsuccessfactorsEndpoint<'getCdpLearningMetadata'>; + refreshCdpLearningMetadata: SapsuccessfactorsEndpoint<'refreshCdpLearningMetadata'>; + getEmployeeTime: SapsuccessfactorsEndpoint<'getEmployeeTime'>; + getEmployeeTimesheet: SapsuccessfactorsEndpoint<'getEmployeeTimesheet'>; + getTemporaryTimeInformation: SapsuccessfactorsEndpoint<'getTemporaryTimeInformation'>; + getTimeAccountSnapshot: SapsuccessfactorsEndpoint<'getTimeAccountSnapshot'>; + queryAllAvailableClockClockOut: SapsuccessfactorsEndpoint<'queryAllAvailableClockClockOut'>; + queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoint<'queryClockClockOutGroupCodeTime'>; +}; + +export type SapsuccessfactorsBoundWebhooks = BindWebhooks< + Record +>; + +const sapsuccessfactorsEndpointsNested = { + approve: { + approveCalibrationSession: Approve.approveCalibrationSession, + }, + calibration: { + getCalibrationSessionById: Calibration.getCalibrationSessionById, + getCalibrationSessions: Calibration.getCalibrationSessions, + getCalibrationSubjectById: Calibration.getCalibrationSubjectById, + getCalibrationSubjectRatings: Calibration.getCalibrationSubjectRatings, + updateCalibrationSubjectRatings: + Calibration.updateCalibrationSubjectRatings, + }, + odata: { + getOdataMetadataCalibSessionService: + Odata.getOdataMetadataCalibSessionService, + getOdataMetadataOnboardingAddl: Odata.getOdataMetadataOnboardingAddl, + getOdataMetadataForNominationService: + Odata.getOdataMetadataForNominationService, + getOdataUserMetadata: Odata.getOdataUserMetadata, + getOdataMetadataClockInclockOut: Odata.getOdataMetadataClockInclockOut, + }, + onboardee: { + createOnboardee: Onboardee.createOnboardee, + }, + onb2: { + getOnb2Process: Onb2.getOnb2Process, + }, + internal: { + updateInternalUsernameNewHiresAfter: + Internal.updateInternalUsernameNewHiresAfter, + }, + a: { + createAFeedbackRequest: A.createAFeedbackRequest, + }, + feedback: { + getFeedbackRecordsServiceAvailable: + Feedback.getFeedbackRecordsServiceAvailable, + }, + pending: { + getPendingFeedbackRequestsFeedback: + Pending.getPendingFeedbackRequestsFeedback, + }, + give: { + giveFeedbackOrRespondToAFeedbackRequest: + Give.giveFeedbackOrRespondToAFeedbackRequest, + }, + metadata: { + refreshMetadataContFeedbackService: + Metadata.refreshMetadataContFeedbackService, + }, + successor: { + createUpdateSuccessorNomination: Successor.createUpdateSuccessorNomination, + }, + nomination: { + deleteNominationPositionTalentPool: + Nomination.deleteNominationPositionTalentPool, + }, + talent: { + getTalentPool: Talent.getTalentPool, + }, + application: { + getApplicationInterview: Application.getApplicationInterview, + }, + interview: { + getInterviewOverallAssessment: Interview.getInterviewOverallAssessment, + }, + job: { + getJobApplication: Job.getJobApplication, + getJobRequisition: Job.getJobRequisition, + getJobReqScreeningQuestion: Job.getJobReqScreeningQuestion, + }, + candidates: { + listCandidates: Candidates.listCandidates, + }, + fo: { + getFoBusinessUnit: Fo.getFoBusinessUnit, + getFoCompany: Fo.getFoCompany, + getFoCostCenter: Fo.getFoCostCenter, + getFoDepartment: Fo.getFoDepartment, + getFoJobCode: Fo.getFoJobCode, + getFoJobFunction: Fo.getFoJobFunction, + getFoLocation: Fo.getFoLocation, + getFoPayGroup: Fo.getFoPayGroup, + }, + position: { + getPosition: Position.getPosition, + }, + custom: { + getCustomMdfObject: Custom.getCustomMdfObject, + }, + picklist: { + getPicklist: Picklist.getPicklist, + getPicklistOption: Picklist.getPicklistOption, + }, + current: { + getCurrentUser: Current.getCurrentUser, + }, + users: { + listUsers: Users.listUsers, + }, + per: { + getPerPersonById: Per.getPerPersonById, + listPerPerson: Per.listPerPerson, + getPerPersonal: Per.getPerPersonal, + }, + background: { + getBackgroundEducation: Background.getBackgroundEducation, + getBackgroundMobility: Background.getBackgroundMobility, + }, + emp: { + listEmpEmployment: Emp.listEmpEmployment, + getEmpEmploymentTermination: Emp.getEmpEmploymentTermination, + getEmpPayCompRecurring: Emp.getEmpPayCompRecurring, + getEmpPayCompNonRecurring: Emp.getEmpPayCompNonRecurring, + }, + work: { + getWorkOrder: Work.getWorkOrder, + }, + goal: { + getGoalPlanTemplate: Goal.getGoalPlanTemplate, + }, + goals: { + getGoalsByPlan: Goals.getGoalsByPlan, + }, + form: { + getFormContent: Form.getFormContent, + }, + learning: { + createLearningActivitiesBulk: Learning.createLearningActivitiesBulk, + }, + cdp: { + getCdpLearningMetadata: Cdp.getCdpLearningMetadata, + refreshCdpLearningMetadata: Cdp.refreshCdpLearningMetadata, + }, + employee: { + getEmployeeTime: Employee.getEmployeeTime, + getEmployeeTimesheet: Employee.getEmployeeTimesheet, + }, + temporary: { + getTemporaryTimeInformation: Temporary.getTemporaryTimeInformation, + }, + time: { + getTimeAccountSnapshot: Time.getTimeAccountSnapshot, + }, + query: { + queryAllAvailableClockClockOut: Query.queryAllAvailableClockClockOut, + queryClockClockOutGroupCodeTime: Query.queryClockClockOutGroupCodeTime, + }, +} as const; + +const sapsuccessfactorsWebhooksNested = { + // TODO: Add webhook handlers here once implemented +} as const; + +export const sapsuccessfactorsEndpointSchemas = { + 'approve.approveCalibrationSession': { + input: SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession, + output: SapsuccessfactorsEndpointOutputSchemas.approveCalibrationSession, + }, + 'calibration.getCalibrationSessionById': { + input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSessionById, + output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessionById, + }, + 'calibration.getCalibrationSessions': { + input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSessions, + output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessions, + }, + 'calibration.getCalibrationSubjectById': { + input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectById, + output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectById, + }, + 'calibration.getCalibrationSubjectRatings': { + input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectRatings, + output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectRatings, + }, + 'calibration.updateCalibrationSubjectRatings': { + input: + SapsuccessfactorsEndpointInputSchemas.updateCalibrationSubjectRatings, + output: + SapsuccessfactorsEndpointOutputSchemas.updateCalibrationSubjectRatings, + }, + 'odata.getOdataMetadataCalibSessionService': { + input: + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataCalibSessionService, + output: + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataCalibSessionService, + }, + 'odata.getOdataMetadataOnboardingAddl': { + input: SapsuccessfactorsEndpointInputSchemas.getOdataMetadataOnboardingAddl, + output: + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataOnboardingAddl, + }, + 'odata.getOdataMetadataForNominationService': { + input: + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataForNominationService, + output: + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataForNominationService, + }, + 'odata.getOdataUserMetadata': { + input: SapsuccessfactorsEndpointInputSchemas.getOdataUserMetadata, + output: SapsuccessfactorsEndpointOutputSchemas.getOdataUserMetadata, + }, + 'odata.getOdataMetadataClockInclockOut': { + input: + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataClockInclockOut, + output: + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataClockInclockOut, + }, + 'onboardee.createOnboardee': { + input: SapsuccessfactorsEndpointInputSchemas.createOnboardee, + output: SapsuccessfactorsEndpointOutputSchemas.createOnboardee, + }, + 'onb2.getOnb2Process': { + input: SapsuccessfactorsEndpointInputSchemas.getOnb2Process, + output: SapsuccessfactorsEndpointOutputSchemas.getOnb2Process, + }, + 'internal.updateInternalUsernameNewHiresAfter': { + input: + SapsuccessfactorsEndpointInputSchemas.updateInternalUsernameNewHiresAfter, + output: + SapsuccessfactorsEndpointOutputSchemas.updateInternalUsernameNewHiresAfter, + }, + 'a.createAFeedbackRequest': { + input: SapsuccessfactorsEndpointInputSchemas.createAFeedbackRequest, + output: SapsuccessfactorsEndpointOutputSchemas.createAFeedbackRequest, + }, + 'feedback.getFeedbackRecordsServiceAvailable': { + input: + SapsuccessfactorsEndpointInputSchemas.getFeedbackRecordsServiceAvailable, + output: + SapsuccessfactorsEndpointOutputSchemas.getFeedbackRecordsServiceAvailable, + }, + 'pending.getPendingFeedbackRequestsFeedback': { + input: + SapsuccessfactorsEndpointInputSchemas.getPendingFeedbackRequestsFeedback, + output: + SapsuccessfactorsEndpointOutputSchemas.getPendingFeedbackRequestsFeedback, + }, + 'give.giveFeedbackOrRespondToAFeedbackRequest': { + input: + SapsuccessfactorsEndpointInputSchemas.giveFeedbackOrRespondToAFeedbackRequest, + output: + SapsuccessfactorsEndpointOutputSchemas.giveFeedbackOrRespondToAFeedbackRequest, + }, + 'metadata.refreshMetadataContFeedbackService': { + input: + SapsuccessfactorsEndpointInputSchemas.refreshMetadataContFeedbackService, + output: + SapsuccessfactorsEndpointOutputSchemas.refreshMetadataContFeedbackService, + }, + 'successor.createUpdateSuccessorNomination': { + input: + SapsuccessfactorsEndpointInputSchemas.createUpdateSuccessorNomination, + output: + SapsuccessfactorsEndpointOutputSchemas.createUpdateSuccessorNomination, + }, + 'nomination.deleteNominationPositionTalentPool': { + input: + SapsuccessfactorsEndpointInputSchemas.deleteNominationPositionTalentPool, + output: + SapsuccessfactorsEndpointOutputSchemas.deleteNominationPositionTalentPool, + }, + 'talent.getTalentPool': { + input: SapsuccessfactorsEndpointInputSchemas.getTalentPool, + output: SapsuccessfactorsEndpointOutputSchemas.getTalentPool, + }, + 'application.getApplicationInterview': { + input: SapsuccessfactorsEndpointInputSchemas.getApplicationInterview, + output: SapsuccessfactorsEndpointOutputSchemas.getApplicationInterview, + }, + 'interview.getInterviewOverallAssessment': { + input: SapsuccessfactorsEndpointInputSchemas.getInterviewOverallAssessment, + output: + SapsuccessfactorsEndpointOutputSchemas.getInterviewOverallAssessment, + }, + 'job.getJobApplication': { + input: SapsuccessfactorsEndpointInputSchemas.getJobApplication, + output: SapsuccessfactorsEndpointOutputSchemas.getJobApplication, + }, + 'job.getJobRequisition': { + input: SapsuccessfactorsEndpointInputSchemas.getJobRequisition, + output: SapsuccessfactorsEndpointOutputSchemas.getJobRequisition, + }, + 'job.getJobReqScreeningQuestion': { + input: SapsuccessfactorsEndpointInputSchemas.getJobReqScreeningQuestion, + output: SapsuccessfactorsEndpointOutputSchemas.getJobReqScreeningQuestion, + }, + 'candidates.listCandidates': { + input: SapsuccessfactorsEndpointInputSchemas.listCandidates, + output: SapsuccessfactorsEndpointOutputSchemas.listCandidates, + }, + 'fo.getFoBusinessUnit': { + input: SapsuccessfactorsEndpointInputSchemas.getFoBusinessUnit, + output: SapsuccessfactorsEndpointOutputSchemas.getFoBusinessUnit, + }, + 'fo.getFoCompany': { + input: SapsuccessfactorsEndpointInputSchemas.getFoCompany, + output: SapsuccessfactorsEndpointOutputSchemas.getFoCompany, + }, + 'fo.getFoCostCenter': { + input: SapsuccessfactorsEndpointInputSchemas.getFoCostCenter, + output: SapsuccessfactorsEndpointOutputSchemas.getFoCostCenter, + }, + 'fo.getFoDepartment': { + input: SapsuccessfactorsEndpointInputSchemas.getFoDepartment, + output: SapsuccessfactorsEndpointOutputSchemas.getFoDepartment, + }, + 'fo.getFoJobCode': { + input: SapsuccessfactorsEndpointInputSchemas.getFoJobCode, + output: SapsuccessfactorsEndpointOutputSchemas.getFoJobCode, + }, + 'fo.getFoJobFunction': { + input: SapsuccessfactorsEndpointInputSchemas.getFoJobFunction, + output: SapsuccessfactorsEndpointOutputSchemas.getFoJobFunction, + }, + 'fo.getFoLocation': { + input: SapsuccessfactorsEndpointInputSchemas.getFoLocation, + output: SapsuccessfactorsEndpointOutputSchemas.getFoLocation, + }, + 'fo.getFoPayGroup': { + input: SapsuccessfactorsEndpointInputSchemas.getFoPayGroup, + output: SapsuccessfactorsEndpointOutputSchemas.getFoPayGroup, + }, + 'position.getPosition': { + input: SapsuccessfactorsEndpointInputSchemas.getPosition, + output: SapsuccessfactorsEndpointOutputSchemas.getPosition, + }, + 'custom.getCustomMdfObject': { + input: SapsuccessfactorsEndpointInputSchemas.getCustomMdfObject, + output: SapsuccessfactorsEndpointOutputSchemas.getCustomMdfObject, + }, + 'picklist.getPicklist': { + input: SapsuccessfactorsEndpointInputSchemas.getPicklist, + output: SapsuccessfactorsEndpointOutputSchemas.getPicklist, + }, + 'picklist.getPicklistOption': { + input: SapsuccessfactorsEndpointInputSchemas.getPicklistOption, + output: SapsuccessfactorsEndpointOutputSchemas.getPicklistOption, + }, + 'current.getCurrentUser': { + input: SapsuccessfactorsEndpointInputSchemas.getCurrentUser, + output: SapsuccessfactorsEndpointOutputSchemas.getCurrentUser, + }, + 'users.listUsers': { + input: SapsuccessfactorsEndpointInputSchemas.listUsers, + output: SapsuccessfactorsEndpointOutputSchemas.listUsers, + }, + 'per.getPerPersonById': { + input: SapsuccessfactorsEndpointInputSchemas.getPerPersonById, + output: SapsuccessfactorsEndpointOutputSchemas.getPerPersonById, + }, + 'per.listPerPerson': { + input: SapsuccessfactorsEndpointInputSchemas.listPerPerson, + output: SapsuccessfactorsEndpointOutputSchemas.listPerPerson, + }, + 'per.getPerPersonal': { + input: SapsuccessfactorsEndpointInputSchemas.getPerPersonal, + output: SapsuccessfactorsEndpointOutputSchemas.getPerPersonal, + }, + 'background.getBackgroundEducation': { + input: SapsuccessfactorsEndpointInputSchemas.getBackgroundEducation, + output: SapsuccessfactorsEndpointOutputSchemas.getBackgroundEducation, + }, + 'background.getBackgroundMobility': { + input: SapsuccessfactorsEndpointInputSchemas.getBackgroundMobility, + output: SapsuccessfactorsEndpointOutputSchemas.getBackgroundMobility, + }, + 'emp.listEmpEmployment': { + input: SapsuccessfactorsEndpointInputSchemas.listEmpEmployment, + output: SapsuccessfactorsEndpointOutputSchemas.listEmpEmployment, + }, + 'emp.getEmpEmploymentTermination': { + input: SapsuccessfactorsEndpointInputSchemas.getEmpEmploymentTermination, + output: SapsuccessfactorsEndpointOutputSchemas.getEmpEmploymentTermination, + }, + 'emp.getEmpPayCompRecurring': { + input: SapsuccessfactorsEndpointInputSchemas.getEmpPayCompRecurring, + output: SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompRecurring, + }, + 'emp.getEmpPayCompNonRecurring': { + input: SapsuccessfactorsEndpointInputSchemas.getEmpPayCompNonRecurring, + output: SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompNonRecurring, + }, + 'work.getWorkOrder': { + input: SapsuccessfactorsEndpointInputSchemas.getWorkOrder, + output: SapsuccessfactorsEndpointOutputSchemas.getWorkOrder, + }, + 'goal.getGoalPlanTemplate': { + input: SapsuccessfactorsEndpointInputSchemas.getGoalPlanTemplate, + output: SapsuccessfactorsEndpointOutputSchemas.getGoalPlanTemplate, + }, + 'goals.getGoalsByPlan': { + input: SapsuccessfactorsEndpointInputSchemas.getGoalsByPlan, + output: SapsuccessfactorsEndpointOutputSchemas.getGoalsByPlan, + }, + 'form.getFormContent': { + input: SapsuccessfactorsEndpointInputSchemas.getFormContent, + output: SapsuccessfactorsEndpointOutputSchemas.getFormContent, + }, + 'learning.createLearningActivitiesBulk': { + input: SapsuccessfactorsEndpointInputSchemas.createLearningActivitiesBulk, + output: SapsuccessfactorsEndpointOutputSchemas.createLearningActivitiesBulk, + }, + 'cdp.getCdpLearningMetadata': { + input: SapsuccessfactorsEndpointInputSchemas.getCdpLearningMetadata, + output: SapsuccessfactorsEndpointOutputSchemas.getCdpLearningMetadata, + }, + 'cdp.refreshCdpLearningMetadata': { + input: SapsuccessfactorsEndpointInputSchemas.refreshCdpLearningMetadata, + output: SapsuccessfactorsEndpointOutputSchemas.refreshCdpLearningMetadata, + }, + 'employee.getEmployeeTime': { + input: SapsuccessfactorsEndpointInputSchemas.getEmployeeTime, + output: SapsuccessfactorsEndpointOutputSchemas.getEmployeeTime, + }, + 'employee.getEmployeeTimesheet': { + input: SapsuccessfactorsEndpointInputSchemas.getEmployeeTimesheet, + output: SapsuccessfactorsEndpointOutputSchemas.getEmployeeTimesheet, + }, + 'temporary.getTemporaryTimeInformation': { + input: SapsuccessfactorsEndpointInputSchemas.getTemporaryTimeInformation, + output: SapsuccessfactorsEndpointOutputSchemas.getTemporaryTimeInformation, + }, + 'time.getTimeAccountSnapshot': { + input: SapsuccessfactorsEndpointInputSchemas.getTimeAccountSnapshot, + output: SapsuccessfactorsEndpointOutputSchemas.getTimeAccountSnapshot, + }, + 'query.queryAllAvailableClockClockOut': { + input: SapsuccessfactorsEndpointInputSchemas.queryAllAvailableClockClockOut, + output: + SapsuccessfactorsEndpointOutputSchemas.queryAllAvailableClockClockOut, + }, + 'query.queryClockClockOutGroupCodeTime': { + input: + SapsuccessfactorsEndpointInputSchemas.queryClockClockOutGroupCodeTime, + output: + SapsuccessfactorsEndpointOutputSchemas.queryClockClockOutGroupCodeTime, + }, +} as const; + +const defaultAuthType: AuthTypes = 'api_key' as const; + +const sapsuccessfactorsEndpointMeta = { + 'approve.approveCalibrationSession': { + riskLevel: 'write', + description: 'Approve Calibration Session', + }, + 'calibration.getCalibrationSessionById': { + riskLevel: 'read', + description: 'Get Calibration Session By ID', + }, + 'calibration.getCalibrationSessions': { + riskLevel: 'read', + description: 'Get Calibration Sessions', + }, + 'calibration.getCalibrationSubjectById': { + riskLevel: 'read', + description: 'Get Calibration Subject By ID', + }, + 'calibration.getCalibrationSubjectRatings': { + riskLevel: 'read', + description: 'Get Calibration Subject Ratings', + }, + 'calibration.updateCalibrationSubjectRatings': { + riskLevel: 'write', + description: 'Update Calibration Subject Ratings', + }, + 'odata.getOdataMetadataCalibSessionService': { + riskLevel: 'read', + description: 'Get Calibration Session Metadata', + }, + 'odata.getOdataMetadataOnboardingAddl': { + riskLevel: 'read', + description: 'Get Onboarding Additional Services Metadata', + }, + 'odata.getOdataMetadataForNominationService': { + riskLevel: 'read', + description: 'Get Nomination Service Metadata', + }, + 'odata.getOdataUserMetadata': { + riskLevel: 'read', + description: 'Get User Entity Metadata', + }, + 'odata.getOdataMetadataClockInclockOut': { + riskLevel: 'read', + description: 'Get Clock In/Out Integration Metadata', + }, + 'onboardee.createOnboardee': { + riskLevel: 'write', + description: 'Create Onboardee', + }, + 'onb2.getOnb2Process': { + riskLevel: 'read', + description: 'Get Onboarding 2.0 Processes', + }, + 'internal.updateInternalUsernameNewHiresAfter': { + riskLevel: 'write', + description: 'Update Username Post Hiring', + }, + 'a.createAFeedbackRequest': { + riskLevel: 'write', + description: 'Create a Feedback Request', + }, + 'feedback.getFeedbackRecordsServiceAvailable': { + riskLevel: 'read', + description: 'Get Feedback Records', + }, + 'pending.getPendingFeedbackRequestsFeedback': { + riskLevel: 'read', + description: 'Get Pending Feedback Requests', + }, + 'give.giveFeedbackOrRespondToAFeedbackRequest': { + riskLevel: 'write', + description: 'Give Feedback or Respond to Feedback Request', + }, + 'metadata.refreshMetadataContFeedbackService': { + riskLevel: 'write', + description: 'Refresh Metadata for Continuous Feedback', + }, + 'successor.createUpdateSuccessorNomination': { + riskLevel: 'write', + description: 'Create or Update Successor Nomination', + }, + 'nomination.deleteNominationPositionTalentPool': { + riskLevel: 'destructive', + irreversible: true, + description: 'Delete Nomination', + }, + 'talent.getTalentPool': { + riskLevel: 'read', + description: 'Get Talent Pool', + }, + 'application.getApplicationInterview': { + riskLevel: 'read', + description: 'Get Application Interview', + }, + 'interview.getInterviewOverallAssessment': { + riskLevel: 'read', + description: 'Get Interview Overall Assessment', + }, + 'job.getJobApplication': { + riskLevel: 'read', + description: 'Get Job Application', + }, + 'job.getJobRequisition': { + riskLevel: 'read', + description: 'Get Job Requisition', + }, + 'job.getJobReqScreeningQuestion': { + riskLevel: 'read', + description: 'Get Job Requisition Screening Questions', + }, + 'candidates.listCandidates': { + riskLevel: 'read', + description: 'List Candidates', + }, + 'fo.getFoBusinessUnit': { + riskLevel: 'read', + description: 'Get FOBusinessUnit', + }, + 'fo.getFoCompany': { + riskLevel: 'read', + description: 'Get FOCompany Records', + }, + 'fo.getFoCostCenter': { + riskLevel: 'read', + description: 'Get Foundation Object Cost Centers', + }, + 'fo.getFoDepartment': { + riskLevel: 'read', + description: 'Get FODepartment Records', + }, + 'fo.getFoJobCode': { + riskLevel: 'read', + description: 'Get Foundation Object Job Codes', + }, + 'fo.getFoJobFunction': { + riskLevel: 'read', + description: 'Get Job Functions', + }, + 'fo.getFoLocation': { + riskLevel: 'read', + description: 'Get Foundation Object Location', + }, + 'fo.getFoPayGroup': { + riskLevel: 'read', + description: 'Get FOPayGroup', + }, + 'position.getPosition': { + riskLevel: 'read', + description: 'Get Position', + }, + 'custom.getCustomMdfObject': { + riskLevel: 'read', + description: 'Get Custom MDF Object', + }, + 'picklist.getPicklist': { + riskLevel: 'read', + description: 'Get Picklist', + }, + 'picklist.getPicklistOption': { + riskLevel: 'read', + description: 'Get Picklist Option', + }, + 'current.getCurrentUser': { + riskLevel: 'read', + description: 'Get Current User', + }, + 'users.listUsers': { + riskLevel: 'read', + description: 'List Users', + }, + 'per.getPerPersonById': { + riskLevel: 'read', + description: 'Get Person by ID', + }, + 'per.listPerPerson': { + riskLevel: 'read', + description: 'List Person Records', + }, + 'per.getPerPersonal': { + riskLevel: 'read', + description: 'Get Personal Information Records', + }, + 'background.getBackgroundEducation': { + riskLevel: 'read', + description: 'Get Background Education', + }, + 'background.getBackgroundMobility': { + riskLevel: 'read', + description: 'Get Background Mobility', + }, + 'emp.listEmpEmployment': { + riskLevel: 'read', + description: 'List Employee Employment Records', + }, + 'emp.getEmpEmploymentTermination': { + riskLevel: 'read', + description: 'Get Employee Employment Termination', + }, + 'emp.getEmpPayCompRecurring': { + riskLevel: 'read', + description: 'Get Recurring Pay Components', + }, + 'emp.getEmpPayCompNonRecurring': { + riskLevel: 'read', + description: 'Get Non-Recurring Pay Components', + }, + 'work.getWorkOrder': { + riskLevel: 'read', + description: 'Get Work Order', + }, + 'goal.getGoalPlanTemplate': { + riskLevel: 'read', + description: 'Get Goal Plan Template', + }, + 'goals.getGoalsByPlan': { + riskLevel: 'read', + description: 'Get Goals By Plan', + }, + 'form.getFormContent': { + riskLevel: 'read', + description: 'Get Form Content', + }, + 'learning.createLearningActivitiesBulk': { + riskLevel: 'write', + description: 'Create Learning Activities Bulk', + }, + 'cdp.getCdpLearningMetadata': { + riskLevel: 'read', + description: 'Get CDP Learning Metadata', + }, + 'cdp.refreshCdpLearningMetadata': { + riskLevel: 'write', + description: 'Refresh CDP Learning Metadata', + }, + 'employee.getEmployeeTime': { + riskLevel: 'read', + description: 'Get Employee Time', + }, + 'employee.getEmployeeTimesheet': { + riskLevel: 'read', + description: 'Get Employee Timesheet', + }, + 'temporary.getTemporaryTimeInformation': { + riskLevel: 'read', + description: 'Get Temporary Time Information', + }, + 'time.getTimeAccountSnapshot': { + riskLevel: 'read', + description: 'Get Time Account Snapshot', + }, + 'query.queryAllAvailableClockClockOut': { + riskLevel: 'read', + description: 'Query All Available Clock In/Clock Out Groups', + }, + 'query.queryClockClockOutGroupCodeTime': { + riskLevel: 'read', + description: 'Query Clock In/Clock Out Group By Code', + }, +} satisfies RequiredPluginEndpointMeta; + +export const sapsuccessfactorsAuthConfig = { + api_key: { + account: ['tenant_external_id'] as const, + }, +} as const satisfies PluginAuthConfig; + +export type BaseSapsuccessfactorsPlugin< + T extends SapsuccessfactorsPluginOptions, +> = CorsairPlugin< + 'sapsuccessfactors', + typeof SapsuccessfactorsSchema, + typeof sapsuccessfactorsEndpointsNested, + typeof sapsuccessfactorsWebhooksNested, + T, + typeof defaultAuthType +>; + +export type InternalSapsuccessfactorsPlugin = + BaseSapsuccessfactorsPlugin; +export type ExternalSapsuccessfactorsPlugin< + T extends SapsuccessfactorsPluginOptions, +> = BaseSapsuccessfactorsPlugin; + +export function sapsuccessfactors< + const T extends SapsuccessfactorsPluginOptions, +>( + incomingOptions: SapsuccessfactorsPluginOptions & + T = {} as SapsuccessfactorsPluginOptions & T, +): ExternalSapsuccessfactorsPlugin { + const options = { + ...incomingOptions, + authType: incomingOptions.authType ?? defaultAuthType, + }; + return { + id: 'sapsuccessfactors', + authConfig: sapsuccessfactorsAuthConfig, + schema: SapsuccessfactorsSchema, + options, + hooks: options.hooks, + webhookHooks: options.webhookHooks, + endpoints: sapsuccessfactorsEndpointsNested, + webhooks: sapsuccessfactorsWebhooksNested, + endpointMeta: sapsuccessfactorsEndpointMeta, + endpointSchemas: sapsuccessfactorsEndpointSchemas, + pluginWebhookMatcher: (request: RawWebhookRequest) => { + // TODO: Update to match Sapsuccessfactors webhook signature headers + return 'x-sapsuccessfactors-signature' in request.headers; + }, + pluginTenantWebhookMatcher: matchSapsuccessfactorsTenantWebhook, + oauthWebhookTenantLinkResolver: + resolveSapsuccessfactorsOAuthWebhookTenantLink, + errorHandlers: { + ...errorHandlers, + ...options.errorHandlers, + }, + keyBuilder: async ( + ctx: SapsuccessfactorsKeyBuilderContext, + source: 'endpoint' | 'webhook', + ) => { + if (source === 'webhook' && options.webhookSecret) + return options.webhookSecret; + if (source === 'webhook') { + const res = await ctx.keys.get_webhook_signature(); + return res ?? ''; + } + if (source === 'endpoint' && options.key) return options.key; + if (source === 'endpoint' && ctx.authType === 'api_key') { + const res = await ctx.keys.get_api_key(); + return res ?? ''; + } + return ''; + }, + } satisfies InternalSapsuccessfactorsPlugin; +} + +export type { + SapsuccessfactorsEndpointInputs, + SapsuccessfactorsEndpointOutputs, +} from './endpoints/types'; +export type { SapsuccessfactorsWebhookOutputs } from './webhooks/types'; diff --git a/packages/sapsuccessfactors/package.json b/packages/sapsuccessfactors/package.json new file mode 100644 index 000000000..458a24f4f --- /dev/null +++ b/packages/sapsuccessfactors/package.json @@ -0,0 +1,44 @@ +{ + "name": "@corsair-dev/sapsuccessfactors", + "version": "0.1.0", + "description": "Sapsuccessfactors plugin for Corsair", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "dev-source": "./index.ts", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "rm -rf dist && tsc --build --force && tsup", + "typecheck": "tsc --noEmit", + "test": "jest" + }, + "peerDependencies": { + "corsair": ">=0.1.0", + "zod": "^4.1.13" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "corsair": "workspace:*", + "jest": "^29.7.0", + "ts-jest": "^29.4.9", + "tsup": "^8.0.1", + "typescript": "catalog:", + "zod": "^4.1.13" + }, + "keywords": [ + "corsair", + "sapsuccessfactors", + "plugin" + ], + "author": "", + "license": "Apache-2.0", + "files": [ + "dist" + ] +} diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts new file mode 100644 index 000000000..dde157915 --- /dev/null +++ b/packages/sapsuccessfactors/schema.test.ts @@ -0,0 +1,57 @@ +declare const describe: (name: string, fn: () => void) => void; +declare const it: (name: string, fn: () => void) => void; +declare const expect: (val: any) => any; + +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './endpoints/types'; +import { SapsuccessfactorsSchema } from './schema'; + +describe('Sapsuccessfactors schema and validation', () => { + it('declares a semver version', () => { + expect(SapsuccessfactorsSchema.version).toBeDefined(); + expect(SapsuccessfactorsSchema.version).toMatch(/^\d+\.\d+\.\d+$/); + }); + + it('declares an entities map', () => { + expect(typeof SapsuccessfactorsSchema.entities).toBe('object'); + expect(SapsuccessfactorsSchema.entities).not.toBeNull(); + }); + + it('validates approveCalibrationSession input schema', () => { + const valid = { session_id: 'session-123' }; + expect( + SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.parse( + valid, + ), + ).toEqual(valid); + }); + + it('validates getPersonById input schema', () => { + const valid = { person_id_external: 'emp-456' }; + expect( + SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(valid), + ).toEqual(valid); + }); + + it('validates listUsers input schema with pagination', () => { + const valid = { top: 10, skip: 0, filter: "status eq 'ACTIVE'" }; + expect( + SapsuccessfactorsEndpointInputSchemas.listUsers.parse(valid), + ).toEqual(valid); + }); + + it('validates standard response output schema', () => { + const validResponse = { + d: { + results: [{ id: '1', name: 'Test' }], + id: '1', + status: 'OK', + }, + }; + expect( + SapsuccessfactorsEndpointOutputSchemas.listUsers.parse(validResponse), + ).toBeDefined(); + }); +}); diff --git a/packages/sapsuccessfactors/schema/database.ts b/packages/sapsuccessfactors/schema/database.ts new file mode 100644 index 000000000..a4651be14 --- /dev/null +++ b/packages/sapsuccessfactors/schema/database.ts @@ -0,0 +1,7 @@ +// TODO: Define database entity schemas here if you want Corsair to persist data. +// Example: +// export const SapsuccessfactorsItem = z.object({ +// id: z.string(), +// created_at: z.coerce.date().nullable().optional(), +// }); +// export type SapsuccessfactorsItem = z.infer; diff --git a/packages/sapsuccessfactors/schema/index.ts b/packages/sapsuccessfactors/schema/index.ts new file mode 100644 index 000000000..0a9d56b19 --- /dev/null +++ b/packages/sapsuccessfactors/schema/index.ts @@ -0,0 +1,4 @@ +export const SapsuccessfactorsSchema = { + version: '1.0.0', + entities: {}, +} as const; diff --git a/packages/sapsuccessfactors/tsconfig.json b/packages/sapsuccessfactors/tsconfig.json new file mode 100644 index 000000000..360eafeaf --- /dev/null +++ b/packages/sapsuccessfactors/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["esnext"], + "types": ["node"], + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "./dist", + "rootDir": "./", + "composite": true, + "incremental": true, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "skipLibCheck": true + }, + "include": ["./**/*"], + "exclude": ["dist", "node_modules"], + "references": [] +} diff --git a/packages/sapsuccessfactors/tsup.config.ts b/packages/sapsuccessfactors/tsup.config.ts new file mode 100644 index 000000000..3ec221e23 --- /dev/null +++ b/packages/sapsuccessfactors/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: false, + dts: false, + format: ['esm'], + target: 'esnext', + platform: 'node', + bundle: true, + splitting: true, + minify: true, + outDir: 'dist', + external: ['corsair', 'zod'], + entry: ['index.ts'], +}); diff --git a/packages/sapsuccessfactors/webhooks/index.ts b/packages/sapsuccessfactors/webhooks/index.ts new file mode 100644 index 000000000..07a99b8fe --- /dev/null +++ b/packages/sapsuccessfactors/webhooks/index.ts @@ -0,0 +1,3 @@ +export * from './oauth-tenant-link'; +export * from './tenant-matcher'; +export * from './types'; diff --git a/packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts b/packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts new file mode 100644 index 000000000..6d0d43d8a --- /dev/null +++ b/packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts @@ -0,0 +1,31 @@ +import type { TokenResponse, WebhookTenantMatch } from 'corsair/core'; +import { toExternalId } from 'corsair/core'; + +// TODO: Rename linkType 'tenant_external_id' to match pluginTenantWebhookMatcher. +// Called after OAuth to store the routing id on corsair_accounts.config. +export async function resolveSapsuccessfactorsOAuthWebhookTenantLink( + tokens: TokenResponse, +): Promise { + // TODO: Read from token response when the provider includes a stable id. + // const externalId = toExternalId(asRecord(tokens.team)?.id); + const externalId = toExternalId(tokens.tenant_external_id); + if (externalId) { + return { linkType: 'tenant_external_id', externalId }; + } + + const accessToken = tokens.access_token; + if (!accessToken) return null; + + // TODO: Fetch from provider API when the token response omits the id. + // const response = await fetch('https://api.example.com/me', { + // headers: { Authorization: `Bearer ${accessToken}` }, + // }); + // if (!response.ok) return null; + // const payload = (await response.json()) as { id?: string }; + // const fetchedId = toExternalId(payload.id); + // return fetchedId + // ? { linkType: 'tenant_external_id', externalId: fetchedId } + // : null; + + return null; +} diff --git a/packages/sapsuccessfactors/webhooks/tenant-matcher.ts b/packages/sapsuccessfactors/webhooks/tenant-matcher.ts new file mode 100644 index 000000000..38eaa46fb --- /dev/null +++ b/packages/sapsuccessfactors/webhooks/tenant-matcher.ts @@ -0,0 +1,25 @@ +import type { RawWebhookRequest, WebhookTenantMatch } from 'corsair/core'; +import { asRecord, firstString, readBodyRecord } from 'corsair/core'; + +// TODO: Rename linkType 'tenant_external_id' to match the provider field +// (e.g. team_id, installation_id, organization_id). Must match authConfig.account +// and oauthWebhookTenantLinkResolver. +// Return null for URL verification / handshake payloads that have no tenant id. +export function matchSapsuccessfactorsTenantWebhook( + request: RawWebhookRequest, +): WebhookTenantMatch | null { + const body = readBodyRecord(request); + if (!body) return null; + + // TODO: Extract the stable external id from the webhook payload. + // Example: + // const externalId = firstString([body.tenant_external_id, asRecord(body.data)?.id]); + const externalId = firstString([ + body.tenant_external_id, + asRecord(body.data)?.tenant_external_id, + ]); + + if (!externalId) return null; + + return { linkType: 'tenant_external_id', externalId }; +} diff --git a/packages/sapsuccessfactors/webhooks/types.ts b/packages/sapsuccessfactors/webhooks/types.ts new file mode 100644 index 000000000..10add7c08 --- /dev/null +++ b/packages/sapsuccessfactors/webhooks/types.ts @@ -0,0 +1,56 @@ +import type { + CorsairWebhookMatcher, + RawWebhookRequest, + WebhookRequest, +} from 'corsair/core'; +import { z } from 'zod'; + +// Base webhook payload — TODO: update to match actual Sapsuccessfactors webhook shape +export const SapsuccessfactorsWebhookPayloadSchema = z.object({ + type: z.string(), + created_at: z.string().optional(), + data: z.record(z.string(), z.unknown()), +}); +export type SapsuccessfactorsWebhookPayload = z.infer< + typeof SapsuccessfactorsWebhookPayloadSchema +>; + +// TODO: Add event-specific schemas here. +// Example: +// export const SomeEventSchema = z.object({ +// type: z.literal('some.event'), +// created_at: z.string(), +// data: z.object({ id: z.string() }).catchall(z.unknown()), +// }); +// export type SomeEvent = z.infer; + +export type SapsuccessfactorsWebhookOutputs = { + // TODO: Add webhook event output types here once you know the event types + // example: someEvent: SomeEvent; +}; + +function parseBody(body: unknown): unknown { + return typeof body === 'string' ? JSON.parse(body) : body; +} + +export function createSapsuccessfactorsEventMatch( + eventType: string, +): CorsairWebhookMatcher { + return (request: RawWebhookRequest) => { + const parsed = parseBody(request.body) as Record; + return typeof parsed.type === 'string' && parsed.type === eventType; + }; +} + +export function verifySapsuccessfactorsWebhookSignature( + request: WebhookRequest, + secret: string, +): { valid: boolean; error?: string } { + // TODO: Implement actual webhook signature verification. + // Check the Sapsuccessfactors docs for the signing algorithm and header name. + // Common patterns: + // HMAC-SHA256: verifyHmacSignature(rawBody, secret, signature) + // Svix: verifyHmacSignatureWithPrefix(rawBody, secret, signature, 'sha256=') + if (!secret) return { valid: false, error: 'No webhook secret configured' }; + return { valid: true }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6118de71..7fbd60467 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4317,6 +4317,30 @@ importers: specifier: 4.4.3 version: 4.4.3 + packages/sapsuccessfactors: + devDependencies: + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + corsair: + specifier: workspace:* + version: link:../corsair + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + ts-jest: + specifier: ^29.4.9 + version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@30.4.1)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.27.0)(jest-util@30.4.1)(jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(typescript@5.9.3) + tsup: + specifier: ^8.0.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.4)(typescript@5.9.3)(yaml@2.9.0) + typescript: + specifier: 'catalog:' + version: 5.9.3 + zod: + specifier: 4.4.3 + version: 4.4.3 + packages/securitytrails: devDependencies: '@types/jest': From b0300418821755fe9903b234b580e38ab036893e Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 00:56:30 +0530 Subject: [PATCH 02/18] fix(sapsuccessfactors): enforce runtime schemas and sanitize paths --- packages/sapsuccessfactors/AGENT.md | 701 ------------------ packages/sapsuccessfactors/api.test.ts | 52 +- packages/sapsuccessfactors/endpoints/a.ts | 16 +- .../endpoints/application.ts | 16 +- .../sapsuccessfactors/endpoints/approve.ts | 16 +- .../sapsuccessfactors/endpoints/background.ts | 28 +- .../endpoints/calibration.ts | 68 +- .../sapsuccessfactors/endpoints/candidates.ts | 12 +- packages/sapsuccessfactors/endpoints/cdp.ts | 26 +- .../sapsuccessfactors/endpoints/current.ts | 12 +- .../sapsuccessfactors/endpoints/custom.ts | 25 +- packages/sapsuccessfactors/endpoints/emp.ts | 50 +- .../sapsuccessfactors/endpoints/employee.ts | 24 +- .../sapsuccessfactors/endpoints/feedback.ts | 16 +- packages/sapsuccessfactors/endpoints/fo.ts | 76 +- packages/sapsuccessfactors/endpoints/form.ts | 12 +- packages/sapsuccessfactors/endpoints/give.ts | 16 +- packages/sapsuccessfactors/endpoints/goal.ts | 16 +- packages/sapsuccessfactors/endpoints/goals.ts | 15 +- .../sapsuccessfactors/endpoints/internal.ts | 16 +- .../sapsuccessfactors/endpoints/interview.ts | 16 +- packages/sapsuccessfactors/endpoints/job.ts | 36 +- .../sapsuccessfactors/endpoints/learning.ts | 16 +- .../sapsuccessfactors/endpoints/metadata.ts | 16 +- .../sapsuccessfactors/endpoints/nomination.ts | 18 +- packages/sapsuccessfactors/endpoints/odata.ts | 54 +- packages/sapsuccessfactors/endpoints/onb2.ts | 12 +- .../sapsuccessfactors/endpoints/onboardee.ts | 12 +- .../sapsuccessfactors/endpoints/pending.ts | 16 +- packages/sapsuccessfactors/endpoints/per.ts | 28 +- .../sapsuccessfactors/endpoints/picklist.ts | 25 +- .../sapsuccessfactors/endpoints/position.ts | 15 +- packages/sapsuccessfactors/endpoints/query.ts | 28 +- .../sapsuccessfactors/endpoints/successor.ts | 16 +- .../sapsuccessfactors/endpoints/talent.ts | 12 +- .../sapsuccessfactors/endpoints/temporary.ts | 16 +- packages/sapsuccessfactors/endpoints/time.ts | 16 +- packages/sapsuccessfactors/endpoints/users.ts | 16 +- packages/sapsuccessfactors/endpoints/work.ts | 15 +- packages/sapsuccessfactors/index.ts | 35 +- packages/sapsuccessfactors/jest.config.cjs | 55 ++ packages/sapsuccessfactors/schema.test.ts | 4 - packages/sapsuccessfactors/schema/database.ts | 26 +- packages/sapsuccessfactors/tsconfig.json | 2 +- packages/sapsuccessfactors/webhooks/index.ts | 3 - .../webhooks/oauth-tenant-link.ts | 31 - .../webhooks/tenant-matcher.ts | 23 +- packages/sapsuccessfactors/webhooks/types.ts | 56 -- 48 files changed, 827 insertions(+), 1004 deletions(-) delete mode 100644 packages/sapsuccessfactors/AGENT.md create mode 100644 packages/sapsuccessfactors/jest.config.cjs delete mode 100644 packages/sapsuccessfactors/webhooks/index.ts delete mode 100644 packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts delete mode 100644 packages/sapsuccessfactors/webhooks/types.ts diff --git a/packages/sapsuccessfactors/AGENT.md b/packages/sapsuccessfactors/AGENT.md deleted file mode 100644 index cd011a6ff..000000000 --- a/packages/sapsuccessfactors/AGENT.md +++ /dev/null @@ -1,701 +0,0 @@ -# SapSuccessfactors Plugin — Agent Completion Guide - -> **Auto-generated from scraped API spec.** The Zod schemas, types, and plugin wiring -> are complete. Your job is to fill in the actual HTTP details and write tests. - -## About this integration - -Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. - -- **Auth mode:** `API_KEY` → mapped to Corsair `api_key` -- **Key field:** `api_key` (API Key / Bearer Token) -- **Total operations:** 64 - ---- - -## Step 1 — Find the docs - -Search for: **"SapSuccessfactors API documentation"** or **"SapSuccessfactors developer docs"** - -You're looking for: -1. The **base API URL** (e.g., `https://api.sapsuccessfactors.com/v1`) -2. The **authentication format** — how the key is passed (header name, query param, Bearer prefix) -3. The **endpoint paths** for each operation below - ---- - -## Step 2 — Fill in `client.ts` - -Open `client.ts` and: - -- [ ] Replace `https://api.TODO_sapsuccessfactors.com` with the real base URL -- [ ] Update the `HEADERS` block to use the correct auth format - -Common patterns to look for in the docs: -``` -Authorization: Bearer {api_key} ← most common -X-Api-Key: {api_key} ← also common -?api_key={api_key} ← query param (add to query object instead) -Authorization: Basic base64(key:) ← for BASIC auth -``` - ---- - -## Step 3 — Fill in each endpoint - -The functions are in `endpoints/{group}.ts`. Each has a `TODO_PATH` and `TODO_METHOD` placeholder. -Replace them with the real path and method from the docs. - -### All operations (64 total) - -| Endpoint | Name | Risk | Description | -|---|---|---|---| -| `approve.approveCalibrationSession` | Approve Calibration Session | `write` | Finalize a calibration session that is In Progress or Approving | -| `calibration.getCalibrationSessionById` | Get Calibration Session By ID | `read` | Get a specific calibration session by session ID | -| `calibration.getCalibrationSessions` | Get Calibration Sessions | `read` | Query all calibration sessions the current user can access | -| `calibration.getCalibrationSubjectById` | Get Calibration Subject By ID | `read` | Query a subject's competency ratings within a calibration session | -| `calibration.getCalibrationSubjectRatings` | Get Calibration Subject Ratings | `read` | Query a subject's ratings/competency ratings/comments by session ID | -| `calibration.updateCalibrationSubjectRatings` | Update Calibration Subject Ratings | `write` | Update a subject's competency ratings in a calibration session | -| `odata.getOdataMetadataCalibSessionService` | Get Calibration Session Metadata | `read` | Get OData metadata / available entity sets for CalSession | -| `odata.getOdataMetadataOnboardingAddl` | Get Onboarding Additional Services Metadata | `read` | Get metadata for Onboarding Additional Services (incl | -| `odata.getOdataMetadataForNominationService` | Get Nomination Service Metadata | `read` | Get OData metadata for the Nomination service | -| `odata.getOdataUserMetadata` | Get User Entity Metadata | `read` | Retrieve OData metadata for the User entity | -| `odata.getOdataMetadataClockInclockOut` | Get Clock In/Out Integration Metadata | `read` | Get OData metadata for the Clock In/Clock Out Integration service | -| `onboardee.createOnboardee` | Create Onboardee | `write` | Create a new onboardee in Onboarding 2 | -| `onb2.getOnb2Process` | Get Onboarding 2.0 Processes | `read` | Retrieve Onboarding 2 | -| `internal.updateInternalUsernameNewHiresAfter` | Update Username Post Hiring | `write` | Update a new hire's internal username after MPH submit, pre day-1 | -| `a.createAFeedbackRequest` | Create a Feedback Request | `write` | Request performance feedback from one employee about another | -| `feedback.getFeedbackRecordsServiceAvailable` | Get Feedback Records | `read` | Query continuous feedback records (OData v4) | -| `pending.getPendingFeedbackRequestsFeedback` | Get Pending Feedback Requests | `read` | Query pending feedback requests | -| `give.giveFeedbackOrRespondToAFeedbackRequest` | Give Feedback or Respond to Feedback Request | `write` | Give feedback or respond to a feedback request (up to 3 Q&A pairs) | -| `metadata.refreshMetadataContFeedbackService` | Refresh Metadata for Continuous Feedback | `write` | Refresh the metadata cache for the Continuous Feedback service | -| `successor.createUpdateSuccessorNomination` | Create or Update Successor Nomination | `write` | Create/update a successor nomination for a position or talent pool | -| `nomination.deleteNominationPositionTalentPool` | Delete Nomination | `destructive` | Remove a nominee from a position or talent pool nomination | -| `talent.getTalentPool` | Get Talent Pool | `read` | Retrieve talent pool records including members and nominations | -| `application.getApplicationInterview` | Get Application Interview | `read` | Retrieve interview info from Interview Central (first 1000 records; filter by applicationId) | -| `interview.getInterviewOverallAssessment` | Get Interview Overall Assessment | `read` | Retrieve overall interview ratings, recommendations, and comments | -| `job.getJobApplication` | Get Job Application | `read` | Retrieve job application records linking candidates to requisitions | -| `job.getJobRequisition` | Get Job Requisition | `read` | Retrieve job requisition records from Recruiting Management | -| `job.getJobReqScreeningQuestion` | Get Job Requisition Screening Questions | `read` | Retrieve screening questions for a job requisition | -| `candidates.listCandidates` | List Candidates | `read` | Retrieve a list of candidates | -| `fo.getFoBusinessUnit` | Get FOBusinessUnit | `read` | Retrieve business unit records for org structure hierarchy | -| `fo.getFoCompany` | Get FOCompany Records | `read` | Retrieve company records (display_name, legal_name, entityOID) | -| `fo.getFoCostCenter` | Get Foundation Object Cost Centers | `read` | Retrieve cost center records for org structure | -| `fo.getFoDepartment` | Get FODepartment Records | `read` | Retrieve department records (team/group org structure) | -| `fo.getFoJobCode` | Get Foundation Object Job Codes | `read` | Retrieve job code records with associated position metadata | -| `fo.getFoJobFunction` | Get Job Functions | `read` | Retrieve job function records for categorizing job roles | -| `fo.getFoLocation` | Get Foundation Object Location | `read` | Retrieve work location records (names, status, timezones, address) | -| `fo.getFoPayGroup` | Get FOPayGroup | `read` | Retrieve pay group records for compensation/payroll groupings | -| `position.getPosition` | Get Position | `read` | Retrieve position management records (structure and hierarchy) | -| `custom.getCustomMdfObject` | Get Custom MDF Object | `read` | Retrieve custom MDF objects (names begin with cust_) | -| `picklist.getPicklist` | Get Picklist | `read` | Retrieve picklist definitions (selectable value lists) | -| `picklist.getPicklistOption` | Get Picklist Option | `read` | Retrieve picklist option values with localized labels | -| `current.getCurrentUser` | Get Current User | `read` | Retrieve the currently authenticated user's information | -| `users.listUsers` | List Users | `read` | Retrieve a list of all employee users | -| `per.getPerPersonById` | Get Person by ID | `read` | Retrieve core person info for an employee by external person ID | -| `per.listPerPerson` | List Person Records | `read` | Retrieve person records (latest active record per person) | -| `per.getPerPersonal` | Get Personal Information Records | `read` | Retrieve biographical info, emergency contacts, social/email data | -| `background.getBackgroundEducation` | Get Background Education | `read` | Retrieve background education records (key: backgroundElementId) | -| `background.getBackgroundMobility` | Get Background Mobility | `read` | Retrieve relocation willingness / geographic mobility preferences | -| `emp.listEmpEmployment` | List Employee Employment Records | `read` | Retrieve employment records (start dates, types, assignment classes) | -| `emp.getEmpEmploymentTermination` | Get Employee Employment Termination | `read` | Retrieve termination records (date, reason) | -| `emp.getEmpPayCompRecurring` | Get Recurring Pay Components | `read` | Retrieve recurring pay components (salary, allowances, benefits) | -| `emp.getEmpPayCompNonRecurring` | Get Non-Recurring Pay Components | `read` | Retrieve non-recurring pay components (bonuses, one-time payments) | -| `work.getWorkOrder` | Get Work Order | `read` | Retrieve work order records for contingent worker management | -| `goal.getGoalPlanTemplate` | Get Goal Plan Template | `read` | Retrieve goal plan template configuration (structure via DTD file) | -| `goals.getGoalsByPlan` | Get Goals By Plan | `read` | Retrieve goals for a specific plan (e | -| `form.getFormContent` | Get Form Content | `read` | Retrieve performance form content (filter by template ID, modified date) | -| `learning.createLearningActivitiesBulk` | Create Learning Activities Bulk | `write` | Create learning activities linked to dev goals in bulk (3rd-party LMS) | -| `cdp.getCdpLearningMetadata` | Get CDP Learning Metadata | `read` | Get metadata for the Career Development Planning Learning service | -| `cdp.refreshCdpLearningMetadata` | Refresh CDP Learning Metadata | `write` | Refresh metadata for the CDP Learning service | -| `employee.getEmployeeTime` | Get Employee Time | `read` | Retrieve employee time entries incl | -| `employee.getEmployeeTimesheet` | Get Employee Timesheet | `read` | Retrieve timesheet records: attendance, overtime, on-call, allowances | -| `temporary.getTemporaryTimeInformation` | Get Temporary Time Information | `read` | Retrieve temporary work schedules assigned to employees | -| `time.getTimeAccountSnapshot` | Get Time Account Snapshot | `read` | Retrieve time account balances for leave liability / payroll as-of a date | -| `query.queryAllAvailableClockClockOut` | Query All Available Clock In/Clock Out Groups | `read` | Retrieve all configured clock in/clock out groups | -| `query.queryClockClockOutGroupCodeTime` | Query Clock In/Clock Out Group By Code | `read` | Retrieve one clock in/out group by code, optionally with time event types | - ---- - -### `approve.approveCalibrationSession` — Approve Calibration Session -- **Description:** Finalize a calibration session that is In Progress or Approving. -- **File:** `endpoints/approve.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/approves` or `/approve/approveCalibrationSession`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `calibration.getCalibrationSessionById` — Get Calibration Session By ID -- **Description:** Get a specific calibration session by session ID. -- **File:** `endpoints/calibration.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSessionById`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `calibration.getCalibrationSessions` — Get Calibration Sessions -- **Description:** Query all calibration sessions the current user can access. -- **File:** `endpoints/calibration.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSessions`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `calibration.getCalibrationSubjectById` — Get Calibration Subject By ID -- **Description:** Query a subject's competency ratings within a calibration session. -- **File:** `endpoints/calibration.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSubjectById`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `calibration.getCalibrationSubjectRatings` — Get Calibration Subject Ratings -- **Description:** Query a subject's ratings/competency ratings/comments by session ID. -- **File:** `endpoints/calibration.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/getCalibrationSubjectRatings`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `calibration.updateCalibrationSubjectRatings` — Update Calibration Subject Ratings -- **Description:** Update a subject's competency ratings in a calibration session. -- **File:** `endpoints/calibration.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/calibrations` or `/calibration/updateCalibrationSubjectRatings`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `odata.getOdataMetadataCalibSessionService` — Get Calibration Session Metadata -- **Description:** Get OData metadata / available entity sets for CalSession.svc. -- **File:** `endpoints/odata.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataCalibSessionService`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `odata.getOdataMetadataOnboardingAddl` — Get Onboarding Additional Services Metadata -- **Description:** Get metadata for Onboarding Additional Services (incl. username update ops). -- **File:** `endpoints/odata.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataOnboardingAddl`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `odata.getOdataMetadataForNominationService` — Get Nomination Service Metadata -- **Description:** Get OData metadata for the Nomination service. -- **File:** `endpoints/odata.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataForNominationService`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `odata.getOdataUserMetadata` — Get User Entity Metadata -- **Description:** Retrieve OData metadata for the User entity. -- **File:** `endpoints/odata.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataUserMetadata`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `odata.getOdataMetadataClockInclockOut` — Get Clock In/Out Integration Metadata -- **Description:** Get OData metadata for the Clock In/Clock Out Integration service. -- **File:** `endpoints/odata.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/odatas` or `/odata/getOdataMetadataClockInclockOut`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `onboardee.createOnboardee` — Create Onboardee -- **Description:** Create a new onboardee in Onboarding 2.0 (new hire or rehire). -- **File:** `endpoints/onboardee.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/onboardees` or `/onboardee/createOnboardee`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `onb2.getOnb2Process` — Get Onboarding 2.0 Processes -- **Description:** Retrieve Onboarding 2.0 process records for new hires. -- **File:** `endpoints/onb2.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/onb2s` or `/onb2/getOnb2Process`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `internal.updateInternalUsernameNewHiresAfter` — Update Username Post Hiring -- **Description:** Update a new hire's internal username after MPH submit, pre day-1. -- **File:** `endpoints/internal.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/internals` or `/internal/updateInternalUsernameNewHiresAfter`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `a.createAFeedbackRequest` — Create a Feedback Request -- **Description:** Request performance feedback from one employee about another. -- **File:** `endpoints/a.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/as` or `/a/createAFeedbackRequest`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `feedback.getFeedbackRecordsServiceAvailable` — Get Feedback Records -- **Description:** Query continuous feedback records (OData v4). -- **File:** `endpoints/feedback.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/feedbacks` or `/feedback/getFeedbackRecordsServiceAvailable`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `pending.getPendingFeedbackRequestsFeedback` — Get Pending Feedback Requests -- **Description:** Query pending feedback requests. -- **File:** `endpoints/pending.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/pendings` or `/pending/getPendingFeedbackRequestsFeedback`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `give.giveFeedbackOrRespondToAFeedbackRequest` — Give Feedback or Respond to Feedback Request -- **Description:** Give feedback or respond to a feedback request (up to 3 Q&A pairs). -- **File:** `endpoints/give.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/gives` or `/give/giveFeedbackOrRespondToAFeedbackRequest`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `metadata.refreshMetadataContFeedbackService` — Refresh Metadata for Continuous Feedback -- **Description:** Refresh the metadata cache for the Continuous Feedback service. -- **File:** `endpoints/metadata.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/metadatas` or `/metadata/refreshMetadataContFeedbackService`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `successor.createUpdateSuccessorNomination` — Create or Update Successor Nomination -- **Description:** Create/update a successor nomination for a position or talent pool. -- **File:** `endpoints/successor.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/successors` or `/successor/createUpdateSuccessorNomination`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `nomination.deleteNominationPositionTalentPool` — Delete Nomination -- **Description:** Remove a nominee from a position or talent pool nomination. -- **File:** `endpoints/nomination.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/nominations` or `/nomination/deleteNominationPositionTalentPool`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `talent.getTalentPool` — Get Talent Pool -- **Description:** Retrieve talent pool records including members and nominations. -- **File:** `endpoints/talent.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/talents` or `/talent/getTalentPool`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `application.getApplicationInterview` — Get Application Interview -- **Description:** Retrieve interview info from Interview Central (first 1000 records; filter by applicationId). -- **File:** `endpoints/application.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/applications` or `/application/getApplicationInterview`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `interview.getInterviewOverallAssessment` — Get Interview Overall Assessment -- **Description:** Retrieve overall interview ratings, recommendations, and comments. -- **File:** `endpoints/interview.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/interviews` or `/interview/getInterviewOverallAssessment`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `job.getJobApplication` — Get Job Application -- **Description:** Retrieve job application records linking candidates to requisitions. -- **File:** `endpoints/job.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/jobs` or `/job/getJobApplication`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `job.getJobRequisition` — Get Job Requisition -- **Description:** Retrieve job requisition records from Recruiting Management. -- **File:** `endpoints/job.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/jobs` or `/job/getJobRequisition`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `job.getJobReqScreeningQuestion` — Get Job Requisition Screening Questions -- **Description:** Retrieve screening questions for a job requisition. -- **File:** `endpoints/job.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/jobs` or `/job/getJobReqScreeningQuestion`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `candidates.listCandidates` — List Candidates -- **Description:** Retrieve a list of candidates. -- **File:** `endpoints/candidates.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/candidatess` or `/candidates/listCandidates`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoBusinessUnit` — Get FOBusinessUnit -- **Description:** Retrieve business unit records for org structure hierarchy. -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoBusinessUnit`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoCompany` — Get FOCompany Records -- **Description:** Retrieve company records (display_name, legal_name, entityOID). -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoCompany`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoCostCenter` — Get Foundation Object Cost Centers -- **Description:** Retrieve cost center records for org structure. -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoCostCenter`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoDepartment` — Get FODepartment Records -- **Description:** Retrieve department records (team/group org structure). -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoDepartment`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoJobCode` — Get Foundation Object Job Codes -- **Description:** Retrieve job code records with associated position metadata. -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoJobCode`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoJobFunction` — Get Job Functions -- **Description:** Retrieve job function records for categorizing job roles. -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoJobFunction`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoLocation` — Get Foundation Object Location -- **Description:** Retrieve work location records (names, status, timezones, address). -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoLocation`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `fo.getFoPayGroup` — Get FOPayGroup -- **Description:** Retrieve pay group records for compensation/payroll groupings. -- **File:** `endpoints/fo.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/fos` or `/fo/getFoPayGroup`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `position.getPosition` — Get Position -- **Description:** Retrieve position management records (structure and hierarchy). -- **File:** `endpoints/position.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/positions` or `/position/getPosition`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `custom.getCustomMdfObject` — Get Custom MDF Object -- **Description:** Retrieve custom MDF objects (names begin with cust_). -- **File:** `endpoints/custom.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/customs` or `/custom/getCustomMdfObject`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `picklist.getPicklist` — Get Picklist -- **Description:** Retrieve picklist definitions (selectable value lists). -- **File:** `endpoints/picklist.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/picklists` or `/picklist/getPicklist`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `picklist.getPicklistOption` — Get Picklist Option -- **Description:** Retrieve picklist option values with localized labels. -- **File:** `endpoints/picklist.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/picklists` or `/picklist/getPicklistOption`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `current.getCurrentUser` — Get Current User -- **Description:** Retrieve the currently authenticated user's information. -- **File:** `endpoints/current.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/currents` or `/current/getCurrentUser`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `users.listUsers` — List Users -- **Description:** Retrieve a list of all employee users. -- **File:** `endpoints/users.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/userss` or `/users/listUsers`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `per.getPerPersonById` — Get Person by ID -- **Description:** Retrieve core person info for an employee by external person ID. -- **File:** `endpoints/per.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/pers` or `/per/getPerPersonById`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `per.listPerPerson` — List Person Records -- **Description:** Retrieve person records (latest active record per person). -- **File:** `endpoints/per.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/pers` or `/per/listPerPerson`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `per.getPerPersonal` — Get Personal Information Records -- **Description:** Retrieve biographical info, emergency contacts, social/email data. -- **File:** `endpoints/per.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/pers` or `/per/getPerPersonal`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `background.getBackgroundEducation` — Get Background Education -- **Description:** Retrieve background education records (key: backgroundElementId). -- **File:** `endpoints/background.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/backgrounds` or `/background/getBackgroundEducation`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `background.getBackgroundMobility` — Get Background Mobility -- **Description:** Retrieve relocation willingness / geographic mobility preferences. -- **File:** `endpoints/background.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/backgrounds` or `/background/getBackgroundMobility`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `emp.listEmpEmployment` — List Employee Employment Records -- **Description:** Retrieve employment records (start dates, types, assignment classes). -- **File:** `endpoints/emp.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/listEmpEmployment`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `emp.getEmpEmploymentTermination` — Get Employee Employment Termination -- **Description:** Retrieve termination records (date, reason). -- **File:** `endpoints/emp.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/getEmpEmploymentTermination`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `emp.getEmpPayCompRecurring` — Get Recurring Pay Components -- **Description:** Retrieve recurring pay components (salary, allowances, benefits). -- **File:** `endpoints/emp.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/getEmpPayCompRecurring`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `emp.getEmpPayCompNonRecurring` — Get Non-Recurring Pay Components -- **Description:** Retrieve non-recurring pay components (bonuses, one-time payments). -- **File:** `endpoints/emp.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/emps` or `/emp/getEmpPayCompNonRecurring`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `work.getWorkOrder` — Get Work Order -- **Description:** Retrieve work order records for contingent worker management. -- **File:** `endpoints/work.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/works` or `/work/getWorkOrder`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `goal.getGoalPlanTemplate` — Get Goal Plan Template -- **Description:** Retrieve goal plan template configuration (structure via DTD file). -- **File:** `endpoints/goal.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/goals` or `/goal/getGoalPlanTemplate`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `goals.getGoalsByPlan` — Get Goals By Plan -- **Description:** Retrieve goals for a specific plan (e.g. Goal_11), optionally by userId. -- **File:** `endpoints/goals.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/goalss` or `/goals/getGoalsByPlan`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `form.getFormContent` — Get Form Content -- **Description:** Retrieve performance form content (filter by template ID, modified date). -- **File:** `endpoints/form.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/forms` or `/form/getFormContent`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `learning.createLearningActivitiesBulk` — Create Learning Activities Bulk -- **Description:** Create learning activities linked to dev goals in bulk (3rd-party LMS). -- **File:** `endpoints/learning.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/learnings` or `/learning/createLearningActivitiesBulk`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `cdp.getCdpLearningMetadata` — Get CDP Learning Metadata -- **Description:** Get metadata for the Career Development Planning Learning service. -- **File:** `endpoints/cdp.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/cdps` or `/cdp/getCdpLearningMetadata`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `cdp.refreshCdpLearningMetadata` — Refresh CDP Learning Metadata -- **Description:** Refresh metadata for the CDP Learning service. -- **File:** `endpoints/cdp.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/cdps` or `/cdp/refreshCdpLearningMetadata`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **body** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `employee.getEmployeeTime` — Get Employee Time -- **Description:** Retrieve employee time entries incl. time off (filter by userId/status/type/date). -- **File:** `endpoints/employee.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/employees` or `/employee/getEmployeeTime`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `employee.getEmployeeTimesheet` — Get Employee Timesheet -- **Description:** Retrieve timesheet records: attendance, overtime, on-call, allowances. -- **File:** `endpoints/employee.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/employees` or `/employee/getEmployeeTimesheet`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `temporary.getTemporaryTimeInformation` — Get Temporary Time Information -- **Description:** Retrieve temporary work schedules assigned to employees. -- **File:** `endpoints/temporary.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/temporarys` or `/temporary/getTemporaryTimeInformation`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `time.getTimeAccountSnapshot` — Get Time Account Snapshot -- **Description:** Retrieve time account balances for leave liability / payroll as-of a date. -- **File:** `endpoints/time.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/times` or `/time/getTimeAccountSnapshot`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `query.queryAllAvailableClockClockOut` — Query All Available Clock In/Clock Out Groups -- **Description:** Retrieve all configured clock in/clock out groups. -- **File:** `endpoints/query.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/querys` or `/query/queryAllAvailableClockClockOut`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - -### `query.queryClockClockOutGroupCodeTime` — Query Clock In/Clock Out Group By Code -- **Description:** Retrieve one clock in/out group by code, optionally with time event types. -- **File:** `endpoints/query.ts` -- [ ] Set the correct HTTP method (`GET`/`POST`/`PUT`/`DELETE`/`PATCH`) -- [ ] Set the correct endpoint path (e.g., `/v1/querys` or `/query/queryClockClockOutGroupCodeTime`) -- [ ] Confirm params go in `query` (GET) or `body` (POST/PUT) — currently defaulted to **query** -- [ ] Verify the input schema in `endpoints/types.ts` matches actual API docs - ---- - -## Step 4 — Webhooks - - -This integration does not have documented webhook triggers in the scraped spec. - -Check the docs to confirm: -- [ ] Does SapSuccessfactors support webhooks? If yes, add them following the Resend plugin as a reference (`packages/resend/webhooks/`). -- [ ] If no webhooks, the empty `webhooksNested` in `index.ts` is correct. - -## Webhook tenant routing - -Corsair routes multi-tenant webhooks using three linked pieces. **linkType must match across all three:** - -| Piece | File | Purpose | -|---|---|---| -| `pluginTenantWebhookMatcher` | `webhooks/tenant-matcher.ts` | Extract id from incoming webhook | -| `authConfig.{authType}.account` | `index.ts` | Field name stored on `corsair_accounts.config` | -| `oauthWebhookTenantLinkResolver` | `webhooks/oauth-tenant-link.ts` | Populate field after OAuth (if applicable) | - -- [ ] Rename `tenant_external_id` to the provider's real field (e.g. `team_id`, `installation_id`) -- [ ] Update `match{Plugin}TenantWebhook` to parse the webhook payload (return `null` for handshakes) -- [ ] Update `{plugin}AuthConfig` account fields to use the same linkType -- [ ] If OAuth: implement `resolve{Plugin}OAuthWebhookTenantLink` (token response and/or post-OAuth API call) -- [ ] Wire `pluginTenantWebhookMatcher` and `oauthWebhookTenantLinkResolver` on the plugin return object -- [ ] Reference: `packages/slack/webhooks/tenant-matcher.ts` and `packages/slack/webhooks/oauth-tenant-link.ts` - - - ---- - -## Step 5 — Typecheck - -```bash -cd packages/sapsuccessfactors && pnpm typecheck -# or from the root: -pnpm typecheck -``` - -Fix any TypeScript errors before moving on. - ---- - -## Step 6 — Write tests - -Create a `tests/` directory in this package. Write at minimum: - -1. **Schema validation tests** — confirm the Zod schemas accept valid payloads and reject invalid ones -2. **Endpoint stub tests** — mock `makeSapsuccessfactorsRequest` and verify the correct path/method/params are passed -3. **At least one happy-path integration test** if you have access to a SapSuccessfactors sandbox/test account - -Reference: look at existing test files in `packages/resend/` or `packages/slack/` for patterns. - ---- - -## Step 7 — Register in your corsair instance - -After the plugin is complete, add it to your app's `corsair.ts`: - -```ts -import { sapsuccessfactors } from '@corsair-dev/sapsuccessfactors'; - -export const corsair = createCorsair({ - plugins: [ - sapsuccessfactors({ key: process.env.SAPSUCCESSFACTORS_API_KEY }), - // ... other plugins - ], -}); -``` diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 98391f8f2..d4e8ee696 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -1,16 +1,10 @@ -declare const describe: (name: string, fn: () => void) => void; -declare const it: (name: string, fn: () => Promise | void) => void; -declare const expect: (val: any) => any; -declare const beforeEach: (fn: () => void) => void; -declare const jest: any; - import { request } from 'corsair/http'; import { sapsuccessfactors } from './index'; jest.mock('corsair/http', () => ({ - request: jest - .fn() - .mockResolvedValue({ d: { results: [{ id: 'test-123' }] } }), + request: jest.fn().mockResolvedValue({ + d: { results: [{ id: 'test-123' }], id: 'test-123', status: 'OK' }, + }), ApiError: class ApiError extends Error { constructor( public status: number, @@ -46,7 +40,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.approve ?.approveCalibrationSession; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { session_id: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -55,7 +49,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.calibration ?.getCalibrationSessionById; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { session_id: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -73,7 +67,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.calibration ?.getCalibrationSubjectById; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { subject_id: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -82,7 +76,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.calibration ?.getCalibrationSubjectRatings; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { session_id: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -91,7 +85,10 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.calibration ?.updateCalibrationSubjectRatings; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { + subject_id: 'test_value', + body: { test: 'data' }, + } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -143,7 +140,7 @@ describe('SapSuccessfactors Plugin', () => { it('calls onboardee.createOnboardee endpoint correctly', async () => { const endpoint = (plugin.endpoints as any)?.onboardee?.createOnboardee; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -160,7 +157,10 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.internal ?.updateInternalUsernameNewHiresAfter; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { + user_id: 'test_value', + new_username: 'test_value', + } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -168,7 +168,7 @@ describe('SapSuccessfactors Plugin', () => { it('calls a.createAFeedbackRequest endpoint correctly', async () => { const endpoint = (plugin.endpoints as any)?.a?.createAFeedbackRequest; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -195,7 +195,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.give ?.giveFeedbackOrRespondToAFeedbackRequest; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -213,7 +213,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.successor ?.createUpdateSuccessorNomination; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -222,7 +222,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.nomination ?.deleteNominationPositionTalentPool; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { nomination_id: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -360,7 +360,7 @@ describe('SapSuccessfactors Plugin', () => { it('calls custom.getCustomMdfObject endpoint correctly', async () => { const endpoint = (plugin.endpoints as any)?.custom?.getCustomMdfObject; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { custom_object: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -400,7 +400,9 @@ describe('SapSuccessfactors Plugin', () => { it('calls per.getPerPersonById endpoint correctly', async () => { const endpoint = (plugin.endpoints as any)?.per?.getPerPersonById; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { + person_id_external: 'test_value', + } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -491,7 +493,7 @@ describe('SapSuccessfactors Plugin', () => { it('calls goals.getGoalsByPlan endpoint correctly', async () => { const endpoint = (plugin.endpoints as any)?.goals?.getGoalsByPlan; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { goal_plan_id: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -508,7 +510,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.learning ?.createLearningActivitiesBulk; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); @@ -575,7 +577,7 @@ describe('SapSuccessfactors Plugin', () => { const endpoint = (plugin.endpoints as any)?.query ?.queryClockClockOutGroupCodeTime; expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); + const res = await endpoint(mockCtx, { code: 'test_value' } as any); expect(res).toBeDefined(); expect(mockedRequest).toHaveBeenCalled(); }); diff --git a/packages/sapsuccessfactors/endpoints/a.ts b/packages/sapsuccessfactors/endpoints/a.ts index f7220e6cf..8fc163ffb 100644 --- a/packages/sapsuccessfactors/endpoints/a.ts +++ b/packages/sapsuccessfactors/endpoints/a.ts @@ -2,12 +2,20 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Create a Feedback Request // Request performance feedback from one employee about another. export const createAFeedbackRequest: SapsuccessfactorsEndpoints['createAFeedbackRequest'] = async (ctx, input) => { - const { body, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.createAFeedbackRequest.parse( + input ?? {}, + ); + const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; const response = await makeSapsuccessfactorsRequest< @@ -16,11 +24,15 @@ export const createAFeedbackRequest: SapsuccessfactorsEndpoints['createAFeedback method: 'POST', body: (body ?? rest) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.createAFeedbackRequest.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.a.createAFeedbackRequest', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/application.ts b/packages/sapsuccessfactors/endpoints/application.ts index 80b095aca..83a521403 100644 --- a/packages/sapsuccessfactors/endpoints/application.ts +++ b/packages/sapsuccessfactors/endpoints/application.ts @@ -2,23 +2,35 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Application Interview // Retrieve interview info from Interview Central (first 1000 records; filter by applicationId). export const getApplicationInterview: SapsuccessfactorsEndpoints['getApplicationInterview'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getApplicationInterview.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getApplicationInterview'] >('odata/v2/ApplicationInterview', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getApplicationInterview.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.application.getApplicationInterview', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/approve.ts b/packages/sapsuccessfactors/endpoints/approve.ts index 3487e4d66..c4465b9ef 100644 --- a/packages/sapsuccessfactors/endpoints/approve.ts +++ b/packages/sapsuccessfactors/endpoints/approve.ts @@ -2,22 +2,34 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Approve Calibration Session // Finalize a calibration session that is In Progress or Approving. export const approveCalibrationSession: SapsuccessfactorsEndpoints['approveCalibrationSession'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['approveCalibrationSession'] >('odata/v4/CalSession.svc/Approve', ctx.key, { method: 'POST', - body: (input ?? {}) as Record, + body: (validatedInput ?? {}) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.approveCalibrationSession.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.approve.approveCalibrationSession', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/background.ts b/packages/sapsuccessfactors/endpoints/background.ts index 00460352d..08f85cb80 100644 --- a/packages/sapsuccessfactors/endpoints/background.ts +++ b/packages/sapsuccessfactors/endpoints/background.ts @@ -2,43 +2,63 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Background Education // Retrieve background education records (key: backgroundElementId). export const getBackgroundEducation: SapsuccessfactorsEndpoints['getBackgroundEducation'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getBackgroundEducation.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getBackgroundEducation'] >('odata/v2/BackgroundEducation', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getBackgroundEducation.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.background.getBackgroundEducation', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Background Mobility // Retrieve relocation willingness / geographic mobility preferences. export const getBackgroundMobility: SapsuccessfactorsEndpoints['getBackgroundMobility'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getBackgroundMobility.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getBackgroundMobility'] >('odata/v2/BackgroundMobility', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getBackgroundMobility.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.background.getBackgroundMobility', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/calibration.ts b/packages/sapsuccessfactors/endpoints/calibration.ts index 079c082c0..5123b0924 100644 --- a/packages/sapsuccessfactors/endpoints/calibration.ts +++ b/packages/sapsuccessfactors/endpoints/calibration.ts @@ -2,12 +2,22 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Calibration Session By ID // Get a specific calibration session by session ID. export const getCalibrationSessionById: SapsuccessfactorsEndpoints['getCalibrationSessionById'] = async (ctx, input) => { - const { session_id, ...query } = (input ?? {}) as { session_id?: string }; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCalibrationSessionById.parse( + input ?? {}, + ); + const { session_id, ...query } = (validatedInput ?? {}) as { + session_id?: string; + }; const resourcePath = session_id ? `odata/v4/CalSession.svc/CalibrationSession('${session_id}')` : 'odata/v4/CalSession.svc/CalibrationSession'; @@ -17,20 +27,28 @@ export const getCalibrationSessionById: SapsuccessfactorsEndpoints['getCalibrati method: 'GET', query: query as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessionById.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.calibration.getCalibrationSessionById', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Calibration Sessions // Query all calibration sessions the current user can access. export const getCalibrationSessions: SapsuccessfactorsEndpoints['getCalibrationSessions'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCalibrationSessions.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; @@ -40,20 +58,30 @@ export const getCalibrationSessions: SapsuccessfactorsEndpoints['getCalibrationS method: 'GET', query, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessions.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.calibration.getCalibrationSessions', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Calibration Subject By ID // Query a subject's competency ratings within a calibration session. export const getCalibrationSubjectById: SapsuccessfactorsEndpoints['getCalibrationSubjectById'] = async (ctx, input) => { - const { subject_id, ...query } = (input ?? {}) as { subject_id?: string }; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectById.parse( + input ?? {}, + ); + const { subject_id, ...query } = (validatedInput ?? {}) as { + subject_id?: string; + }; const resourcePath = subject_id ? `odata/v4/CalSession.svc/CalibrationSubject('${subject_id}')` : 'odata/v4/CalSession.svc/CalibrationSubject'; @@ -63,20 +91,28 @@ export const getCalibrationSubjectById: SapsuccessfactorsEndpoints['getCalibrati method: 'GET', query: query as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectById.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.calibration.getCalibrationSubjectById', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Calibration Subject Ratings // Query a subject's ratings/competency ratings/comments by session ID. export const getCalibrationSubjectRatings: SapsuccessfactorsEndpoints['getCalibrationSubjectRatings'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectRatings.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; @@ -86,20 +122,28 @@ export const getCalibrationSubjectRatings: SapsuccessfactorsEndpoints['getCalibr method: 'GET', query, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectRatings.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.calibration.getCalibrationSubjectRatings', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Update Calibration Subject Ratings // Update a subject's competency ratings in a calibration session. export const updateCalibrationSubjectRatings: SapsuccessfactorsEndpoints['updateCalibrationSubjectRatings'] = async (ctx, input) => { - const { subject_id, body, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.updateCalibrationSubjectRatings.parse( + input ?? {}, + ); + const { subject_id, body, ...rest } = (validatedInput ?? {}) as { subject_id?: string; body?: Record; }; @@ -112,11 +156,15 @@ export const updateCalibrationSubjectRatings: SapsuccessfactorsEndpoints['update method: 'PATCH', body: (body ?? rest) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.updateCalibrationSubjectRatings.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.calibration.updateCalibrationSubjectRatings', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/candidates.ts b/packages/sapsuccessfactors/endpoints/candidates.ts index 370999b6a..98a8a1c54 100644 --- a/packages/sapsuccessfactors/endpoints/candidates.ts +++ b/packages/sapsuccessfactors/endpoints/candidates.ts @@ -2,23 +2,31 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // List Candidates // Retrieve a list of candidates. export const listCandidates: SapsuccessfactorsEndpoints['listCandidates'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.listCandidates.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listCandidates'] >('odata/v2/Candidate', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.listCandidates.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.candidates.listCandidates', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/cdp.ts b/packages/sapsuccessfactors/endpoints/cdp.ts index 981ade6e4..aabd074b3 100644 --- a/packages/sapsuccessfactors/endpoints/cdp.ts +++ b/packages/sapsuccessfactors/endpoints/cdp.ts @@ -2,38 +2,58 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get CDP Learning Metadata // Get metadata for the Career Development Planning Learning service. export const getCdpLearningMetadata: SapsuccessfactorsEndpoints['getCdpLearningMetadata'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCdpLearningMetadata.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getCdpLearningMetadata'] >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCdpLearningMetadata.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.cdp.getCdpLearningMetadata', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Refresh CDP Learning Metadata // Refresh metadata for the CDP Learning service. export const refreshCdpLearningMetadata: SapsuccessfactorsEndpoints['refreshCdpLearningMetadata'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.refreshCdpLearningMetadata.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['refreshCdpLearningMetadata'] >('odata/v2/RefreshCDPLearningMetadata', ctx.key, { method: 'POST', - body: (input ?? {}) as Record, + body: (validatedInput ?? {}) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.refreshCdpLearningMetadata.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.cdp.refreshCdpLearningMetadata', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/current.ts b/packages/sapsuccessfactors/endpoints/current.ts index 293ad3fc4..b42911b13 100644 --- a/packages/sapsuccessfactors/endpoints/current.ts +++ b/packages/sapsuccessfactors/endpoints/current.ts @@ -2,23 +2,31 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Current User // Retrieve the currently authenticated user's information. export const getCurrentUser: SapsuccessfactorsEndpoints['getCurrentUser'] = async (ctx, input) => { - const query = (input ?? {}) as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCurrentUser.parse(input ?? {}); + const query = (validatedInput ?? {}) as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getCurrentUser'] >('odata/v2/User', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCurrentUser.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.current.getCurrentUser', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/custom.ts b/packages/sapsuccessfactors/endpoints/custom.ts index a0a3a50d3..fb18cab26 100644 --- a/packages/sapsuccessfactors/endpoints/custom.ts +++ b/packages/sapsuccessfactors/endpoints/custom.ts @@ -2,28 +2,43 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Custom MDF Object // Retrieve custom MDF objects (names begin with cust_). export const getCustomMdfObject: SapsuccessfactorsEndpoints['getCustomMdfObject'] = async (ctx, input) => { - const { custom_object, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getCustomMdfObject.parse( + input ?? {}, + ); + const { custom_object, ...rest } = (validatedInput ?? {}) as { custom_object?: string; }; - const resourcePath = custom_object - ? `odata/v2/${custom_object}` - : 'odata/v2/custom_objects'; + const rawName = (custom_object || 'cust_object').replace( + /[^A-Za-z0-9_]/g, + '', + ); + const sanitizedObj = rawName.startsWith('cust_') + ? rawName + : `cust_${rawName}`; + const resourcePath = `odata/v2/${sanitizedObj}`; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getCustomMdfObject'] >(resourcePath, ctx.key, { method: 'GET', query: rest as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getCustomMdfObject.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.custom.getCustomMdfObject', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/emp.ts b/packages/sapsuccessfactors/endpoints/emp.ts index 8f41f5231..e0c903ff3 100644 --- a/packages/sapsuccessfactors/endpoints/emp.ts +++ b/packages/sapsuccessfactors/endpoints/emp.ts @@ -2,83 +2,117 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // List Employee Employment Records // Retrieve employment records (start dates, types, assignment classes). export const listEmpEmployment: SapsuccessfactorsEndpoints['listEmpEmployment'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.listEmpEmployment.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listEmpEmployment'] >('odata/v2/EmpEmployment', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.listEmpEmployment.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.emp.listEmpEmployment', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Employee Employment Termination // Retrieve termination records (date, reason). export const getEmpEmploymentTermination: SapsuccessfactorsEndpoints['getEmpEmploymentTermination'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getEmpEmploymentTermination.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmpEmploymentTermination'] >('odata/v2/EmpEmploymentTermination', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getEmpEmploymentTermination.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.emp.getEmpEmploymentTermination', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Recurring Pay Components // Retrieve recurring pay components (salary, allowances, benefits). export const getEmpPayCompRecurring: SapsuccessfactorsEndpoints['getEmpPayCompRecurring'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getEmpPayCompRecurring.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmpPayCompRecurring'] >('odata/v2/EmpPayCompRecurring', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompRecurring.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.emp.getEmpPayCompRecurring', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Non-Recurring Pay Components // Retrieve non-recurring pay components (bonuses, one-time payments). export const getEmpPayCompNonRecurring: SapsuccessfactorsEndpoints['getEmpPayCompNonRecurring'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getEmpPayCompNonRecurring.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmpPayCompNonRecurring'] >('odata/v2/EmpPayCompNonRecurring', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompNonRecurring.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.emp.getEmpPayCompNonRecurring', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/employee.ts b/packages/sapsuccessfactors/endpoints/employee.ts index 9db60f2f4..1af4c1a18 100644 --- a/packages/sapsuccessfactors/endpoints/employee.ts +++ b/packages/sapsuccessfactors/endpoints/employee.ts @@ -2,43 +2,59 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Employee Time // Retrieve employee time entries incl. time off (filter by userId/status/type/date). export const getEmployeeTime: SapsuccessfactorsEndpoints['getEmployeeTime'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getEmployeeTime.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmployeeTime'] >('odata/v2/EmployeeTime', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getEmployeeTime.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.employee.getEmployeeTime', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Employee Timesheet // Retrieve timesheet records: attendance, overtime, on-call, allowances. export const getEmployeeTimesheet: SapsuccessfactorsEndpoints['getEmployeeTimesheet'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getEmployeeTimesheet.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmployeeTimesheet'] >('odata/v2/EmployeeTimeSheet', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getEmployeeTimesheet.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.employee.getEmployeeTimesheet', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/feedback.ts b/packages/sapsuccessfactors/endpoints/feedback.ts index e71850017..0fbc584a3 100644 --- a/packages/sapsuccessfactors/endpoints/feedback.ts +++ b/packages/sapsuccessfactors/endpoints/feedback.ts @@ -2,12 +2,20 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Feedback Records // Query continuous feedback records (OData v4). export const getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoints['getFeedbackRecordsServiceAvailable'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFeedbackRecordsServiceAvailable.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; @@ -17,11 +25,15 @@ export const getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoints['get method: 'GET', query, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFeedbackRecordsServiceAvailable.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.feedback.getFeedbackRecordsServiceAvailable', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/fo.ts b/packages/sapsuccessfactors/endpoints/fo.ts index 2e68326f6..e6428f4d9 100644 --- a/packages/sapsuccessfactors/endpoints/fo.ts +++ b/packages/sapsuccessfactors/endpoints/fo.ts @@ -2,25 +2,35 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get FOBusinessUnit // Retrieve business unit records for org structure hierarchy. export const getFoBusinessUnit: SapsuccessfactorsEndpoints['getFoBusinessUnit'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoBusinessUnit.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoBusinessUnit'] >('odata/v2/FOBusinessUnit', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoBusinessUnit.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoBusinessUnit', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get FOCompany Records @@ -29,57 +39,72 @@ export const getFoCompany: SapsuccessfactorsEndpoints['getFoCompany'] = async ( ctx, input, ) => { - const query = input as Record; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoCompany.parse(input ?? {}); + const query = validatedInput as Record< + string, + string | number | boolean | undefined + >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoCompany'] >('odata/v2/FOCompany', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoCompany.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoCompany', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Foundation Object Cost Centers // Retrieve cost center records for org structure. export const getFoCostCenter: SapsuccessfactorsEndpoints['getFoCostCenter'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoCostCenter.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoCostCenter'] >('odata/v2/FOCostCenter', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoCostCenter.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoCostCenter', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get FODepartment Records // Retrieve department records (team/group org structure). export const getFoDepartment: SapsuccessfactorsEndpoints['getFoDepartment'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoDepartment.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoDepartment'] >('odata/v2/FODepartment', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoDepartment.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoDepartment', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Foundation Object Job Codes @@ -88,75 +113,94 @@ export const getFoJobCode: SapsuccessfactorsEndpoints['getFoJobCode'] = async ( ctx, input, ) => { - const query = input as Record; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoJobCode.parse(input ?? {}); + const query = validatedInput as Record< + string, + string | number | boolean | undefined + >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoJobCode'] >('odata/v2/FOJobCode', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoJobCode.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoJobCode', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Job Functions // Retrieve job function records for categorizing job roles. export const getFoJobFunction: SapsuccessfactorsEndpoints['getFoJobFunction'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoJobFunction.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoJobFunction'] >('odata/v2/FOJobFunction', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoJobFunction.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoJobFunction', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Foundation Object Location // Retrieve work location records (names, status, timezones, address). export const getFoLocation: SapsuccessfactorsEndpoints['getFoLocation'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoLocation.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoLocation'] >('odata/v2/FOLocation', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoLocation.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoLocation', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get FOPayGroup // Retrieve pay group records for compensation/payroll groupings. export const getFoPayGroup: SapsuccessfactorsEndpoints['getFoPayGroup'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFoPayGroup.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoPayGroup'] >('odata/v2/FOPayGroup', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFoPayGroup.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.fo.getFoPayGroup', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/form.ts b/packages/sapsuccessfactors/endpoints/form.ts index 459badff7..470bb7393 100644 --- a/packages/sapsuccessfactors/endpoints/form.ts +++ b/packages/sapsuccessfactors/endpoints/form.ts @@ -2,23 +2,31 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Form Content // Retrieve performance form content (filter by template ID, modified date). export const getFormContent: SapsuccessfactorsEndpoints['getFormContent'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getFormContent.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFormContent'] >('odata/v2/FormContent', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getFormContent.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.form.getFormContent', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/give.ts b/packages/sapsuccessfactors/endpoints/give.ts index cb6240cac..1cd0d4116 100644 --- a/packages/sapsuccessfactors/endpoints/give.ts +++ b/packages/sapsuccessfactors/endpoints/give.ts @@ -2,12 +2,20 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Give Feedback or Respond to Feedback Request // Give feedback or respond to a feedback request (up to 3 Q&A pairs). export const giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoints['giveFeedbackOrRespondToAFeedbackRequest'] = async (ctx, input) => { - const { body, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.giveFeedbackOrRespondToAFeedbackRequest.parse( + input ?? {}, + ); + const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; const response = await makeSapsuccessfactorsRequest< @@ -16,11 +24,15 @@ export const giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoints method: 'POST', body: (body ?? rest) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.giveFeedbackOrRespondToAFeedbackRequest.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.give.giveFeedbackOrRespondToAFeedbackRequest', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/goal.ts b/packages/sapsuccessfactors/endpoints/goal.ts index 2afdb5b2a..bf1fab4ad 100644 --- a/packages/sapsuccessfactors/endpoints/goal.ts +++ b/packages/sapsuccessfactors/endpoints/goal.ts @@ -2,23 +2,35 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Goal Plan Template // Retrieve goal plan template configuration (structure via DTD file). export const getGoalPlanTemplate: SapsuccessfactorsEndpoints['getGoalPlanTemplate'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getGoalPlanTemplate.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getGoalPlanTemplate'] >('odata/v2/GoalPlanTemplate', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getGoalPlanTemplate.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.goal.getGoalPlanTemplate', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/goals.ts b/packages/sapsuccessfactors/endpoints/goals.ts index 0ef557b9e..db8ffa2e4 100644 --- a/packages/sapsuccessfactors/endpoints/goals.ts +++ b/packages/sapsuccessfactors/endpoints/goals.ts @@ -2,16 +2,23 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Goals By Plan // Retrieve goals for a specific plan (e.g. Goal_11), optionally by userId. export const getGoalsByPlan: SapsuccessfactorsEndpoints['getGoalsByPlan'] = async (ctx, input) => { - const { goal_plan_id, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getGoalsByPlan.parse(input ?? {}); + const { goal_plan_id, ...rest } = (validatedInput ?? {}) as { goal_plan_id?: string; }; + const safeId = (goal_plan_id || 'Goal').replace(/[^A-Za-z0-9_]/g, ''); const resourcePath = goal_plan_id - ? `odata/v2/Goal_${goal_plan_id}` + ? `odata/v2/Goal_${safeId}` : 'odata/v2/Goal'; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getGoalsByPlan'] @@ -19,11 +26,13 @@ export const getGoalsByPlan: SapsuccessfactorsEndpoints['getGoalsByPlan'] = method: 'GET', query: rest as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getGoalsByPlan.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.goals.getGoalsByPlan', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/internal.ts b/packages/sapsuccessfactors/endpoints/internal.ts index d98dc9990..b0406c809 100644 --- a/packages/sapsuccessfactors/endpoints/internal.ts +++ b/packages/sapsuccessfactors/endpoints/internal.ts @@ -2,22 +2,34 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Update Username Post Hiring // Update a new hire's internal username after MPH submit, pre day-1. export const updateInternalUsernameNewHiresAfter: SapsuccessfactorsEndpoints['updateInternalUsernameNewHiresAfter'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.updateInternalUsernameNewHiresAfter.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['updateInternalUsernameNewHiresAfter'] >('odata/v2/updateUserNamePostHiring', ctx.key, { method: 'POST', - body: (input ?? {}) as Record, + body: (validatedInput ?? {}) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.updateInternalUsernameNewHiresAfter.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.internal.updateInternalUsernameNewHiresAfter', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/interview.ts b/packages/sapsuccessfactors/endpoints/interview.ts index 650e48e77..7593af760 100644 --- a/packages/sapsuccessfactors/endpoints/interview.ts +++ b/packages/sapsuccessfactors/endpoints/interview.ts @@ -2,23 +2,35 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Interview Overall Assessment // Retrieve overall interview ratings, recommendations, and comments. export const getInterviewOverallAssessment: SapsuccessfactorsEndpoints['getInterviewOverallAssessment'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getInterviewOverallAssessment.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getInterviewOverallAssessment'] >('odata/v2/OverallInterviewAssessment', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getInterviewOverallAssessment.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.interview.getInterviewOverallAssessment', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/job.ts b/packages/sapsuccessfactors/endpoints/job.ts index 1a7f0c7fb..90ddba3ad 100644 --- a/packages/sapsuccessfactors/endpoints/job.ts +++ b/packages/sapsuccessfactors/endpoints/job.ts @@ -2,63 +2,87 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Job Application // Retrieve job application records linking candidates to requisitions. export const getJobApplication: SapsuccessfactorsEndpoints['getJobApplication'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getJobApplication.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getJobApplication'] >('odata/v2/JobApplication', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getJobApplication.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.job.getJobApplication', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Job Requisition // Retrieve job requisition records from Recruiting Management. export const getJobRequisition: SapsuccessfactorsEndpoints['getJobRequisition'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getJobRequisition.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getJobRequisition'] >('odata/v2/JobRequisition', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getJobRequisition.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.job.getJobRequisition', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Job Requisition Screening Questions // Retrieve screening questions for a job requisition. export const getJobReqScreeningQuestion: SapsuccessfactorsEndpoints['getJobReqScreeningQuestion'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getJobReqScreeningQuestion.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getJobReqScreeningQuestion'] >('odata/v2/JobReqScreeningQuestion', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getJobReqScreeningQuestion.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.job.getJobReqScreeningQuestion', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/learning.ts b/packages/sapsuccessfactors/endpoints/learning.ts index a9bbd7fe4..0fff58410 100644 --- a/packages/sapsuccessfactors/endpoints/learning.ts +++ b/packages/sapsuccessfactors/endpoints/learning.ts @@ -2,12 +2,20 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Create Learning Activities Bulk // Create learning activities linked to dev goals in bulk (3rd-party LMS). export const createLearningActivitiesBulk: SapsuccessfactorsEndpoints['createLearningActivitiesBulk'] = async (ctx, input) => { - const { body, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.createLearningActivitiesBulk.parse( + input ?? {}, + ); + const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; const response = await makeSapsuccessfactorsRequest< @@ -16,11 +24,15 @@ export const createLearningActivitiesBulk: SapsuccessfactorsEndpoints['createLea method: 'POST', body: (body ?? rest) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.createLearningActivitiesBulk.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.learning.createLearningActivitiesBulk', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/metadata.ts b/packages/sapsuccessfactors/endpoints/metadata.ts index 5ff8704d4..b94035797 100644 --- a/packages/sapsuccessfactors/endpoints/metadata.ts +++ b/packages/sapsuccessfactors/endpoints/metadata.ts @@ -2,22 +2,34 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Refresh Metadata for Continuous Feedback // Refresh the metadata cache for the Continuous Feedback service. export const refreshMetadataContFeedbackService: SapsuccessfactorsEndpoints['refreshMetadataContFeedbackService'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.refreshMetadataContFeedbackService.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['refreshMetadataContFeedbackService'] >('odata/v4/ContinuousPerformanceManagement.svc/RefreshMetadata', ctx.key, { method: 'POST', - body: (input ?? {}) as Record, + body: (validatedInput ?? {}) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.refreshMetadataContFeedbackService.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.metadata.refreshMetadataContFeedbackService', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/nomination.ts b/packages/sapsuccessfactors/endpoints/nomination.ts index a946a6452..bac9bb00d 100644 --- a/packages/sapsuccessfactors/endpoints/nomination.ts +++ b/packages/sapsuccessfactors/endpoints/nomination.ts @@ -2,23 +2,37 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Delete Nomination // Remove a nominee from a position or talent pool nomination. export const deleteNominationPositionTalentPool: SapsuccessfactorsEndpoints['deleteNominationPositionTalentPool'] = async (ctx, input) => { - const { nomination_id } = (input ?? {}) as { nomination_id?: string }; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.deleteNominationPositionTalentPool.parse( + input ?? {}, + ); + const { nomination_id } = (validatedInput ?? {}) as { + nomination_id?: string; + }; const resourcePath = nomination_id ? `odata/v4/NominationService.svc/Nomination(${nomination_id})` : 'odata/v4/NominationService.svc/Nomination'; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['deleteNominationPositionTalentPool'] >(resourcePath, ctx.key, { method: 'DELETE' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.deleteNominationPositionTalentPool.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.nomination.deleteNominationPositionTalentPool', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/odata.ts b/packages/sapsuccessfactors/endpoints/odata.ts index 4587a69c9..070d67b22 100644 --- a/packages/sapsuccessfactors/endpoints/odata.ts +++ b/packages/sapsuccessfactors/endpoints/odata.ts @@ -2,83 +2,127 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Calibration Session Metadata // Get OData metadata / available entity sets for CalSession.svc. export const getOdataMetadataCalibSessionService: SapsuccessfactorsEndpoints['getOdataMetadataCalibSessionService'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataCalibSessionService.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataCalibSessionService'] >('odata/v4/CalSession.svc/$metadata', ctx.key, { method: 'GET' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataCalibSessionService.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.odata.getOdataMetadataCalibSessionService', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Onboarding Additional Services Metadata // Get metadata for Onboarding Additional Services (incl. username update ops). export const getOdataMetadataOnboardingAddl: SapsuccessfactorsEndpoints['getOdataMetadataOnboardingAddl'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataOnboardingAddl.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataOnboardingAddl'] >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataOnboardingAddl.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.odata.getOdataMetadataOnboardingAddl', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Nomination Service Metadata // Get OData metadata for the Nomination service. export const getOdataMetadataForNominationService: SapsuccessfactorsEndpoints['getOdataMetadataForNominationService'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataForNominationService.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataForNominationService'] >('odata/v4/NominationService.svc/$metadata', ctx.key, { method: 'GET' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataForNominationService.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.odata.getOdataMetadataForNominationService', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get User Entity Metadata // Retrieve OData metadata for the User entity. export const getOdataUserMetadata: SapsuccessfactorsEndpoints['getOdataUserMetadata'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getOdataUserMetadata.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataUserMetadata'] >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getOdataUserMetadata.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.odata.getOdataUserMetadata', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Clock In/Out Integration Metadata // Get OData metadata for the Clock In/Clock Out Integration service. export const getOdataMetadataClockInclockOut: SapsuccessfactorsEndpoints['getOdataMetadataClockInclockOut'] = async (ctx, input) => { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getOdataMetadataClockInclockOut.parse( + input ?? {}, + ); const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataClockInclockOut'] >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataClockInclockOut.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.odata.getOdataMetadataClockInclockOut', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/onb2.ts b/packages/sapsuccessfactors/endpoints/onb2.ts index 5c4e37918..0b7fc889e 100644 --- a/packages/sapsuccessfactors/endpoints/onb2.ts +++ b/packages/sapsuccessfactors/endpoints/onb2.ts @@ -2,23 +2,31 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Onboarding 2.0 Processes // Retrieve Onboarding 2.0 process records for new hires. export const getOnb2Process: SapsuccessfactorsEndpoints['getOnb2Process'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getOnb2Process.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOnb2Process'] >('odata/v2/ONB2Process', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getOnb2Process.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.onb2.getOnb2Process', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/onboardee.ts b/packages/sapsuccessfactors/endpoints/onboardee.ts index a49f33821..07f03d93d 100644 --- a/packages/sapsuccessfactors/endpoints/onboardee.ts +++ b/packages/sapsuccessfactors/endpoints/onboardee.ts @@ -2,12 +2,18 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Create Onboardee // Create a new onboardee in Onboarding 2.0 (new hire or rehire). export const createOnboardee: SapsuccessfactorsEndpoints['createOnboardee'] = async (ctx, input) => { - const { body, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.createOnboardee.parse(input ?? {}); + const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; const response = await makeSapsuccessfactorsRequest< @@ -16,11 +22,13 @@ export const createOnboardee: SapsuccessfactorsEndpoints['createOnboardee'] = method: 'POST', body: (body ?? rest) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.createOnboardee.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.onboardee.createOnboardee', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/pending.ts b/packages/sapsuccessfactors/endpoints/pending.ts index 729375ded..1596c11b4 100644 --- a/packages/sapsuccessfactors/endpoints/pending.ts +++ b/packages/sapsuccessfactors/endpoints/pending.ts @@ -2,12 +2,20 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Pending Feedback Requests // Query pending feedback requests. export const getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoints['getPendingFeedbackRequestsFeedback'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getPendingFeedbackRequestsFeedback.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; @@ -17,11 +25,15 @@ export const getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoints['get method: 'GET', query, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getPendingFeedbackRequestsFeedback.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.pending.getPendingFeedbackRequestsFeedback', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/per.ts b/packages/sapsuccessfactors/endpoints/per.ts index 7ca180857..c57e3402d 100644 --- a/packages/sapsuccessfactors/endpoints/per.ts +++ b/packages/sapsuccessfactors/endpoints/per.ts @@ -2,12 +2,18 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Person by ID // Retrieve core person info for an employee by external person ID. export const getPerPersonById: SapsuccessfactorsEndpoints['getPerPersonById'] = async (ctx, input) => { - const { person_id_external, ...query } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(input ?? {}); + const { person_id_external, ...query } = (validatedInput ?? {}) as { person_id_external?: string; }; const resourcePath = person_id_external @@ -19,51 +25,61 @@ export const getPerPersonById: SapsuccessfactorsEndpoints['getPerPersonById'] = method: 'GET', query: query as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getPerPersonById.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.per.getPerPersonById', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // List Person Records // Retrieve person records (latest active record per person). export const listPerPerson: SapsuccessfactorsEndpoints['listPerPerson'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.listPerPerson.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listPerPerson'] >('odata/v2/PerPerson', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.listPerPerson.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.per.listPerPerson', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Personal Information Records // Retrieve biographical info, emergency contacts, social/email data. export const getPerPersonal: SapsuccessfactorsEndpoints['getPerPersonal'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getPerPersonal.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPerPersonal'] >('odata/v2/PerPersonal', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getPerPersonal.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.per.getPerPersonal', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/picklist.ts b/packages/sapsuccessfactors/endpoints/picklist.ts index f9354b8be..31b97e76e 100644 --- a/packages/sapsuccessfactors/endpoints/picklist.ts +++ b/packages/sapsuccessfactors/endpoints/picklist.ts @@ -2,6 +2,10 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Picklist // Retrieve picklist definitions (selectable value lists). @@ -9,35 +13,48 @@ export const getPicklist: SapsuccessfactorsEndpoints['getPicklist'] = async ( ctx, input, ) => { - const query = input as Record; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getPicklist.parse(input ?? {}); + const query = validatedInput as Record< + string, + string | number | boolean | undefined + >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPicklist'] >('odata/v2/Picklist', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getPicklist.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.picklist.getPicklist', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Get Picklist Option // Retrieve picklist option values with localized labels. export const getPicklistOption: SapsuccessfactorsEndpoints['getPicklistOption'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getPicklistOption.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPicklistOption'] >('odata/v2/PicklistOption', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getPicklistOption.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.picklist.getPicklistOption', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/position.ts b/packages/sapsuccessfactors/endpoints/position.ts index 3c73d3b25..35abcc239 100644 --- a/packages/sapsuccessfactors/endpoints/position.ts +++ b/packages/sapsuccessfactors/endpoints/position.ts @@ -2,6 +2,10 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Position // Retrieve position management records (structure and hierarchy). @@ -9,15 +13,22 @@ export const getPosition: SapsuccessfactorsEndpoints['getPosition'] = async ( ctx, input, ) => { - const query = input as Record; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getPosition.parse(input ?? {}); + const query = validatedInput as Record< + string, + string | number | boolean | undefined + >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPosition'] >('odata/v2/Position', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getPosition.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.position.getPosition', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/query.ts b/packages/sapsuccessfactors/endpoints/query.ts index 6c3fdff89..c566e73e8 100644 --- a/packages/sapsuccessfactors/endpoints/query.ts +++ b/packages/sapsuccessfactors/endpoints/query.ts @@ -2,32 +2,48 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Query All Available Clock In/Clock Out Groups // Retrieve all configured clock in/clock out groups. export const queryAllAvailableClockClockOut: SapsuccessfactorsEndpoints['queryAllAvailableClockClockOut'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.queryAllAvailableClockClockOut.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['queryAllAvailableClockClockOut'] >('odata/v2/ClockInClockOutGroup', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.queryAllAvailableClockClockOut.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.query.queryAllAvailableClockClockOut', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; // Query Clock In/Clock Out Group By Code // Retrieve one clock in/out group by code, optionally with time event types. export const queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoints['queryClockClockOutGroupCodeTime'] = async (ctx, input) => { - const { code, ...query } = (input ?? {}) as { code?: string }; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.queryClockClockOutGroupCodeTime.parse( + input ?? {}, + ); + const { code, ...query } = (validatedInput ?? {}) as { code?: string }; const resourcePath = code ? `odata/v2/ClockInClockOutGroup('${code}')` : 'odata/v2/ClockInClockOutGroup'; @@ -37,11 +53,15 @@ export const queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoints['queryC method: 'GET', query: query as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.queryClockClockOutGroupCodeTime.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.query.queryClockClockOutGroupCodeTime', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/successor.ts b/packages/sapsuccessfactors/endpoints/successor.ts index ddd574ddd..0af4e4c43 100644 --- a/packages/sapsuccessfactors/endpoints/successor.ts +++ b/packages/sapsuccessfactors/endpoints/successor.ts @@ -2,12 +2,20 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Create or Update Successor Nomination // Create/update a successor nomination for a position or talent pool. export const createUpdateSuccessorNomination: SapsuccessfactorsEndpoints['createUpdateSuccessorNomination'] = async (ctx, input) => { - const { body, ...rest } = (input ?? {}) as { + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.createUpdateSuccessorNomination.parse( + input ?? {}, + ); + const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; const response = await makeSapsuccessfactorsRequest< @@ -16,11 +24,15 @@ export const createUpdateSuccessorNomination: SapsuccessfactorsEndpoints['create method: 'POST', body: (body ?? rest) as Record, }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.createUpdateSuccessorNomination.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.successor.createUpdateSuccessorNomination', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/talent.ts b/packages/sapsuccessfactors/endpoints/talent.ts index 4c800c212..5b5fbdaac 100644 --- a/packages/sapsuccessfactors/endpoints/talent.ts +++ b/packages/sapsuccessfactors/endpoints/talent.ts @@ -2,23 +2,31 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Talent Pool // Retrieve talent pool records including members and nominations. export const getTalentPool: SapsuccessfactorsEndpoints['getTalentPool'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getTalentPool.parse(input ?? {}); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getTalentPool'] >('odata/v2/TalentPool', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getTalentPool.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.talent.getTalentPool', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/temporary.ts b/packages/sapsuccessfactors/endpoints/temporary.ts index 52e2af831..08c494480 100644 --- a/packages/sapsuccessfactors/endpoints/temporary.ts +++ b/packages/sapsuccessfactors/endpoints/temporary.ts @@ -2,23 +2,35 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Temporary Time Information // Retrieve temporary work schedules assigned to employees. export const getTemporaryTimeInformation: SapsuccessfactorsEndpoints['getTemporaryTimeInformation'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getTemporaryTimeInformation.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getTemporaryTimeInformation'] >('odata/v2/TemporaryTimeInfo', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getTemporaryTimeInformation.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.temporary.getTemporaryTimeInformation', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/time.ts b/packages/sapsuccessfactors/endpoints/time.ts index ece81928a..9faa922aa 100644 --- a/packages/sapsuccessfactors/endpoints/time.ts +++ b/packages/sapsuccessfactors/endpoints/time.ts @@ -2,23 +2,35 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Time Account Snapshot // Retrieve time account balances for leave liability / payroll as-of a date. export const getTimeAccountSnapshot: SapsuccessfactorsEndpoints['getTimeAccountSnapshot'] = async (ctx, input) => { - const query = input as Record< + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getTimeAccountSnapshot.parse( + input ?? {}, + ); + const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getTimeAccountSnapshot'] >('odata/v2/TimeAccountSnapshot', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getTimeAccountSnapshot.parse( + response, + ); await logEventFromContext( ctx, 'sapsuccessfactors.time.getTimeAccountSnapshot', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/users.ts b/packages/sapsuccessfactors/endpoints/users.ts index 82e465d94..46f28b5b2 100644 --- a/packages/sapsuccessfactors/endpoints/users.ts +++ b/packages/sapsuccessfactors/endpoints/users.ts @@ -2,6 +2,10 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // List Users // Retrieve a list of all employee users. @@ -9,15 +13,23 @@ export const listUsers: SapsuccessfactorsEndpoints['listUsers'] = async ( ctx, input, ) => { - const query = input as Record; + const validatedInput = SapsuccessfactorsEndpointInputSchemas.listUsers.parse( + input ?? {}, + ); + const query = validatedInput as Record< + string, + string | number | boolean | undefined + >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listUsers'] >('odata/v2/User', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.listUsers.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.users.listUsers', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/endpoints/work.ts b/packages/sapsuccessfactors/endpoints/work.ts index 1c5d708ff..60229ff34 100644 --- a/packages/sapsuccessfactors/endpoints/work.ts +++ b/packages/sapsuccessfactors/endpoints/work.ts @@ -2,6 +2,10 @@ import { logEventFromContext } from 'corsair/core'; import type { SapsuccessfactorsEndpoints } from '..'; import { makeSapsuccessfactorsRequest } from '../client'; import type { SapsuccessfactorsEndpointOutputs } from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; // Get Work Order // Retrieve work order records for contingent worker management. @@ -9,15 +13,22 @@ export const getWorkOrder: SapsuccessfactorsEndpoints['getWorkOrder'] = async ( ctx, input, ) => { - const query = input as Record; + const validatedInput = + SapsuccessfactorsEndpointInputSchemas.getWorkOrder.parse(input ?? {}); + const query = validatedInput as Record< + string, + string | number | boolean | undefined + >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getWorkOrder'] >('odata/v2/WorkOrder', ctx.key, { method: 'GET', query }); + const validatedResponse = + SapsuccessfactorsEndpointOutputSchemas.getWorkOrder.parse(response); await logEventFromContext( ctx, 'sapsuccessfactors.work.getWorkOrder', input ?? {}, 'completed', ); - return response; + return validatedResponse; }; diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts index 566e97879..59a70aff7 100644 --- a/packages/sapsuccessfactors/index.ts +++ b/packages/sapsuccessfactors/index.ts @@ -10,9 +10,9 @@ import type { PickAuth, PluginAuthConfig, PluginPermissionsConfig, - RawWebhookRequest, RequiredPluginEndpointMeta, } from 'corsair/core'; +import { AuthMissingError } from 'corsair/core'; import { A, Application, @@ -60,10 +60,7 @@ import { SapsuccessfactorsEndpointInputSchemas, SapsuccessfactorsEndpointOutputSchemas, } from './endpoints/types'; -import { errorHandlers } from './error-handlers'; import { SapsuccessfactorsSchema } from './schema'; -import { resolveSapsuccessfactorsOAuthWebhookTenantLink } from './webhooks/oauth-tenant-link'; -import { matchSapsuccessfactorsTenantWebhook } from './webhooks/tenant-matcher'; export type SapsuccessfactorsPluginOptions = { /** Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. */ @@ -317,9 +314,7 @@ const sapsuccessfactorsEndpointsNested = { }, } as const; -const sapsuccessfactorsWebhooksNested = { - // TODO: Add webhook handlers here once implemented -} as const; +const sapsuccessfactorsWebhooksNested = {} as const; export const sapsuccessfactorsEndpointSchemas = { 'approve.approveCalibrationSession': { @@ -913,33 +908,14 @@ export function sapsuccessfactors< webhooks: sapsuccessfactorsWebhooksNested, endpointMeta: sapsuccessfactorsEndpointMeta, endpointSchemas: sapsuccessfactorsEndpointSchemas, - pluginWebhookMatcher: (request: RawWebhookRequest) => { - // TODO: Update to match Sapsuccessfactors webhook signature headers - return 'x-sapsuccessfactors-signature' in request.headers; - }, - pluginTenantWebhookMatcher: matchSapsuccessfactorsTenantWebhook, - oauthWebhookTenantLinkResolver: - resolveSapsuccessfactorsOAuthWebhookTenantLink, - errorHandlers: { - ...errorHandlers, - ...options.errorHandlers, - }, - keyBuilder: async ( - ctx: SapsuccessfactorsKeyBuilderContext, - source: 'endpoint' | 'webhook', - ) => { - if (source === 'webhook' && options.webhookSecret) - return options.webhookSecret; - if (source === 'webhook') { - const res = await ctx.keys.get_webhook_signature(); - return res ?? ''; - } + pluginWebhookMatcher: () => false, + keyBuilder: async (ctx: SapsuccessfactorsKeyBuilderContext, source) => { if (source === 'endpoint' && options.key) return options.key; if (source === 'endpoint' && ctx.authType === 'api_key') { const res = await ctx.keys.get_api_key(); return res ?? ''; } - return ''; + throw new AuthMissingError('sapsuccessfactors', 'api_key'); }, } satisfies InternalSapsuccessfactorsPlugin; } @@ -948,4 +924,3 @@ export type { SapsuccessfactorsEndpointInputs, SapsuccessfactorsEndpointOutputs, } from './endpoints/types'; -export type { SapsuccessfactorsWebhookOutputs } from './webhooks/types'; diff --git a/packages/sapsuccessfactors/jest.config.cjs b/packages/sapsuccessfactors/jest.config.cjs new file mode 100644 index 000000000..8c6218f64 --- /dev/null +++ b/packages/sapsuccessfactors/jest.config.cjs @@ -0,0 +1,55 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: [''], + testMatch: [ + '**/*.test.ts', + '**/tests/**/*.test.ts', + '**/plugins/**/*.test.ts', + '**/setup/**/*.test.ts', + ], + collectCoverageFrom: [ + '**/*.ts', + '!**/*.d.ts', + '!**/node_modules/**', + '!**/dist/**', + '!jest.config.ts', + '!tests/**', + ], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + transform: { + '^.+\\.yaml$': '/../corsair/jest-yaml-transform.cjs', + '^.+\\.ts$': [ + 'ts-jest', + { + useESM: true, + tsconfig: { + esModuleInterop: true, + allowSyntheticDefaultImports: true, + verbatimModuleSyntax: false, + module: 'ESNext', + moduleResolution: 'Bundler', + }, + }, + ], + '.*\\.js$': [ + 'ts-jest', + { + useESM: true, + tsconfig: { + esModuleInterop: true, + allowSyntheticDefaultImports: true, + }, + }, + ], + }, + moduleNameMapper: { + '^corsair/core$': '/../corsair/core.ts', + '^corsair/http$': '/../corsair/http.ts', + '^(\\.\\.?/.*)\\.js$': '$1', + }, + transformIgnorePatterns: ['node_modules/(?!.*uuid.*)'], + extensionsToTreatAsEsm: ['.ts'], + testTimeout: 30000, + verbose: true, +}; diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index dde157915..3af341a12 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -1,7 +1,3 @@ -declare const describe: (name: string, fn: () => void) => void; -declare const it: (name: string, fn: () => void) => void; -declare const expect: (val: any) => any; - import { SapsuccessfactorsEndpointInputSchemas, SapsuccessfactorsEndpointOutputSchemas, diff --git a/packages/sapsuccessfactors/schema/database.ts b/packages/sapsuccessfactors/schema/database.ts index a4651be14..f96177686 100644 --- a/packages/sapsuccessfactors/schema/database.ts +++ b/packages/sapsuccessfactors/schema/database.ts @@ -1,7 +1,19 @@ -// TODO: Define database entity schemas here if you want Corsair to persist data. -// Example: -// export const SapsuccessfactorsItem = z.object({ -// id: z.string(), -// created_at: z.coerce.date().nullable().optional(), -// }); -// export type SapsuccessfactorsItem = z.infer; +import { z } from 'zod'; + +export const SapsuccessfactorsUser = z.object({ + userId: z.string(), + username: z.string().optional(), + status: z.string().optional(), + email: z.string().optional(), +}); + +export const SapsuccessfactorsCalibrationSession = z.object({ + sessionId: z.string(), + sessionName: z.string().optional(), + status: z.string().optional(), +}); + +export type SapsuccessfactorsUser = z.infer; +export type SapsuccessfactorsCalibrationSession = z.infer< + typeof SapsuccessfactorsCalibrationSession +>; diff --git a/packages/sapsuccessfactors/tsconfig.json b/packages/sapsuccessfactors/tsconfig.json index 360eafeaf..15e507a13 100644 --- a/packages/sapsuccessfactors/tsconfig.json +++ b/packages/sapsuccessfactors/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "lib": ["esnext"], - "types": ["node"], + "types": ["node", "jest"], "module": "ESNext", "moduleResolution": "Bundler", "outDir": "./dist", diff --git a/packages/sapsuccessfactors/webhooks/index.ts b/packages/sapsuccessfactors/webhooks/index.ts deleted file mode 100644 index 07a99b8fe..000000000 --- a/packages/sapsuccessfactors/webhooks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './oauth-tenant-link'; -export * from './tenant-matcher'; -export * from './types'; diff --git a/packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts b/packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts deleted file mode 100644 index 6d0d43d8a..000000000 --- a/packages/sapsuccessfactors/webhooks/oauth-tenant-link.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { TokenResponse, WebhookTenantMatch } from 'corsair/core'; -import { toExternalId } from 'corsair/core'; - -// TODO: Rename linkType 'tenant_external_id' to match pluginTenantWebhookMatcher. -// Called after OAuth to store the routing id on corsair_accounts.config. -export async function resolveSapsuccessfactorsOAuthWebhookTenantLink( - tokens: TokenResponse, -): Promise { - // TODO: Read from token response when the provider includes a stable id. - // const externalId = toExternalId(asRecord(tokens.team)?.id); - const externalId = toExternalId(tokens.tenant_external_id); - if (externalId) { - return { linkType: 'tenant_external_id', externalId }; - } - - const accessToken = tokens.access_token; - if (!accessToken) return null; - - // TODO: Fetch from provider API when the token response omits the id. - // const response = await fetch('https://api.example.com/me', { - // headers: { Authorization: `Bearer ${accessToken}` }, - // }); - // if (!response.ok) return null; - // const payload = (await response.json()) as { id?: string }; - // const fetchedId = toExternalId(payload.id); - // return fetchedId - // ? { linkType: 'tenant_external_id', externalId: fetchedId } - // : null; - - return null; -} diff --git a/packages/sapsuccessfactors/webhooks/tenant-matcher.ts b/packages/sapsuccessfactors/webhooks/tenant-matcher.ts index 38eaa46fb..5e848edab 100644 --- a/packages/sapsuccessfactors/webhooks/tenant-matcher.ts +++ b/packages/sapsuccessfactors/webhooks/tenant-matcher.ts @@ -1,25 +1,8 @@ import type { RawWebhookRequest, WebhookTenantMatch } from 'corsair/core'; -import { asRecord, firstString, readBodyRecord } from 'corsair/core'; -// TODO: Rename linkType 'tenant_external_id' to match the provider field -// (e.g. team_id, installation_id, organization_id). Must match authConfig.account -// and oauthWebhookTenantLinkResolver. -// Return null for URL verification / handshake payloads that have no tenant id. +// SAP SuccessFactors REST/OData plugin does not expose inbound webhooks in this integration. export function matchSapsuccessfactorsTenantWebhook( - request: RawWebhookRequest, + _request: RawWebhookRequest, ): WebhookTenantMatch | null { - const body = readBodyRecord(request); - if (!body) return null; - - // TODO: Extract the stable external id from the webhook payload. - // Example: - // const externalId = firstString([body.tenant_external_id, asRecord(body.data)?.id]); - const externalId = firstString([ - body.tenant_external_id, - asRecord(body.data)?.tenant_external_id, - ]); - - if (!externalId) return null; - - return { linkType: 'tenant_external_id', externalId }; + return null; } diff --git a/packages/sapsuccessfactors/webhooks/types.ts b/packages/sapsuccessfactors/webhooks/types.ts deleted file mode 100644 index 10add7c08..000000000 --- a/packages/sapsuccessfactors/webhooks/types.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { - CorsairWebhookMatcher, - RawWebhookRequest, - WebhookRequest, -} from 'corsair/core'; -import { z } from 'zod'; - -// Base webhook payload — TODO: update to match actual Sapsuccessfactors webhook shape -export const SapsuccessfactorsWebhookPayloadSchema = z.object({ - type: z.string(), - created_at: z.string().optional(), - data: z.record(z.string(), z.unknown()), -}); -export type SapsuccessfactorsWebhookPayload = z.infer< - typeof SapsuccessfactorsWebhookPayloadSchema ->; - -// TODO: Add event-specific schemas here. -// Example: -// export const SomeEventSchema = z.object({ -// type: z.literal('some.event'), -// created_at: z.string(), -// data: z.object({ id: z.string() }).catchall(z.unknown()), -// }); -// export type SomeEvent = z.infer; - -export type SapsuccessfactorsWebhookOutputs = { - // TODO: Add webhook event output types here once you know the event types - // example: someEvent: SomeEvent; -}; - -function parseBody(body: unknown): unknown { - return typeof body === 'string' ? JSON.parse(body) : body; -} - -export function createSapsuccessfactorsEventMatch( - eventType: string, -): CorsairWebhookMatcher { - return (request: RawWebhookRequest) => { - const parsed = parseBody(request.body) as Record; - return typeof parsed.type === 'string' && parsed.type === eventType; - }; -} - -export function verifySapsuccessfactorsWebhookSignature( - request: WebhookRequest, - secret: string, -): { valid: boolean; error?: string } { - // TODO: Implement actual webhook signature verification. - // Check the Sapsuccessfactors docs for the signing algorithm and header name. - // Common patterns: - // HMAC-SHA256: verifyHmacSignature(rawBody, secret, signature) - // Svix: verifyHmacSignatureWithPrefix(rawBody, secret, signature, 'sha256=') - if (!secret) return { valid: false, error: 'No webhook secret configured' }; - return { valid: true }; -} From a6414cebbbf75250513559c5901713894bc62f42 Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 01:00:50 +0530 Subject: [PATCH 03/18] test(sapsuccessfactors): add negative schema validation tests --- packages/sapsuccessfactors/schema.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index 3af341a12..b1b431de9 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -22,6 +22,11 @@ describe('Sapsuccessfactors schema and validation', () => { valid, ), ).toEqual(valid); + expect( + SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.safeParse( + {}, + ).success, + ).toBe(false); }); it('validates getPersonById input schema', () => { @@ -29,6 +34,10 @@ describe('Sapsuccessfactors schema and validation', () => { expect( SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(valid), ).toEqual(valid); + expect( + SapsuccessfactorsEndpointInputSchemas.getPerPersonById.safeParse({}) + .success, + ).toBe(false); }); it('validates listUsers input schema with pagination', () => { @@ -36,6 +45,11 @@ describe('Sapsuccessfactors schema and validation', () => { expect( SapsuccessfactorsEndpointInputSchemas.listUsers.parse(valid), ).toEqual(valid); + expect( + SapsuccessfactorsEndpointInputSchemas.listUsers.safeParse({ + top: 'invalid_number', + }).success, + ).toBe(false); }); it('validates standard response output schema', () => { From 2f3a76526bc93ecce62800c7c4fc4ab5f6b092e6 Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 01:24:17 +0530 Subject: [PATCH 04/18] feat(sapsuccessfactors): add database entities and odata query mappings --- packages/sapsuccessfactors/api.test.ts | 36 ++- packages/sapsuccessfactors/client.ts | 40 +++- packages/sapsuccessfactors/endpoints/a.ts | 3 + .../endpoints/application.ts | 8 +- .../sapsuccessfactors/endpoints/approve.ts | 3 + .../sapsuccessfactors/endpoints/background.ts | 16 +- .../endpoints/calibration.ts | 15 ++ .../sapsuccessfactors/endpoints/candidates.ts | 4 +- packages/sapsuccessfactors/endpoints/cdp.ts | 7 +- .../sapsuccessfactors/endpoints/current.ts | 4 +- .../sapsuccessfactors/endpoints/custom.ts | 3 + packages/sapsuccessfactors/endpoints/emp.ts | 28 ++- .../sapsuccessfactors/endpoints/employee.ts | 12 +- .../sapsuccessfactors/endpoints/feedback.ts | 3 + packages/sapsuccessfactors/endpoints/fo.ts | 32 ++- packages/sapsuccessfactors/endpoints/form.ts | 4 +- packages/sapsuccessfactors/endpoints/give.ts | 3 + packages/sapsuccessfactors/endpoints/goal.ts | 8 +- packages/sapsuccessfactors/endpoints/goals.ts | 3 + .../sapsuccessfactors/endpoints/internal.ts | 3 + .../sapsuccessfactors/endpoints/interview.ts | 8 +- packages/sapsuccessfactors/endpoints/job.ts | 16 +- .../sapsuccessfactors/endpoints/learning.ts | 3 + .../sapsuccessfactors/endpoints/metadata.ts | 3 + .../sapsuccessfactors/endpoints/nomination.ts | 4 +- packages/sapsuccessfactors/endpoints/odata.ts | 26 ++- packages/sapsuccessfactors/endpoints/onb2.ts | 4 +- .../sapsuccessfactors/endpoints/onboardee.ts | 3 + .../sapsuccessfactors/endpoints/pending.ts | 3 + packages/sapsuccessfactors/endpoints/per.ts | 11 +- .../sapsuccessfactors/endpoints/picklist.ts | 8 +- .../sapsuccessfactors/endpoints/position.ts | 4 +- packages/sapsuccessfactors/endpoints/query.ts | 11 +- .../sapsuccessfactors/endpoints/successor.ts | 3 + .../sapsuccessfactors/endpoints/talent.ts | 4 +- .../sapsuccessfactors/endpoints/temporary.ts | 8 +- packages/sapsuccessfactors/endpoints/time.ts | 8 +- packages/sapsuccessfactors/endpoints/users.ts | 4 +- packages/sapsuccessfactors/endpoints/work.ts | 4 +- packages/sapsuccessfactors/error-handlers.ts | 27 ++- packages/sapsuccessfactors/schema.test.ts | 16 +- packages/sapsuccessfactors/schema/database.ts | 212 ++++++++++++++++-- packages/sapsuccessfactors/schema/index.ts | 30 ++- 43 files changed, 580 insertions(+), 75 deletions(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index d4e8ee696..24725d375 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -1,4 +1,6 @@ import { request } from 'corsair/http'; +import { makeSapsuccessfactorsRequest } from './client'; +import { errorHandlers } from './error-handlers'; import { sapsuccessfactors } from './index'; jest.mock('corsair/http', () => ({ @@ -9,8 +11,10 @@ jest.mock('corsair/http', () => ({ constructor( public status: number, message: string, + public retryAfter?: number, ) { super(message); + this.name = 'ApiError'; } }, })); @@ -18,10 +22,14 @@ jest.mock('corsair/http', () => ({ const mockedRequest = request as any; describe('SapSuccessfactors Plugin', () => { - const plugin = sapsuccessfactors({ key: 'test-api-token' }); + const plugin = sapsuccessfactors({ + key: 'test-api-token', + apiBaseUrl: 'https://api10.successfactors.com', + }); const mockCtx = { key: 'test-api-token', authType: 'api_key' as const, + options: { apiBaseUrl: 'https://api10.successfactors.com' }, db: {}, log: jest.fn(), } as any; @@ -34,6 +42,32 @@ describe('SapSuccessfactors Plugin', () => { expect(plugin.id).toBe('sapsuccessfactors'); expect(plugin.authConfig).toBeDefined(); expect(plugin.endpoints).toBeDefined(); + expect(plugin.schema).toBeDefined(); + }); + + it('handles rate limit error matching in errorHandlers', async () => { + const handler = errorHandlers.RATE_LIMIT_ERROR; + expect(handler.match(new Error('Rate limit 429'))).toBe(true); + const res = await handler.handler(new Error('429')); + expect(res.maxRetries).toBe(3); + }); + + it('translates query parameters into OData v2 format ($top, $filter)', async () => { + await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { + method: 'GET', + query: { top: 10, filter: "status eq 'ACTIVE'" }, + }); + expect(mockedRequest).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + query: expect.objectContaining({ + $format: 'json', + $top: 10, + $filter: "status eq 'ACTIVE'", + }), + }), + expect.anything(), + ); }); it('calls approve.approveCalibrationSession endpoint correctly', async () => { diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts index e349eb67a..445f24444 100644 --- a/packages/sapsuccessfactors/client.ts +++ b/packages/sapsuccessfactors/client.ts @@ -15,7 +15,8 @@ export class SapsuccessfactorsAPIError extends Error { } } -const SAP_SUCCESSFACTORS_API_BASE = 'https://api10.successfactors.com'; +export const SAP_SUCCESSFACTORS_DEFAULT_API_BASE = + 'https://api10.successfactors.com'; const SAP_SUCCESSFACTORS_RATE_LIMIT_CONFIG: RateLimitConfig = { enabled: true, @@ -32,20 +33,27 @@ export async function makeSapsuccessfactorsRequest( apiKey: string, options: { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + apiBaseUrl?: string; body?: Record; query?: Record; } = {}, ): Promise { - const { method = 'GET', body, query } = options; + const { + method = 'GET', + apiBaseUrl = SAP_SUCCESSFACTORS_DEFAULT_API_BASE, + body, + query, + } = options; const config: OpenAPIConfig = { - BASE: SAP_SUCCESSFACTORS_API_BASE, + BASE: apiBaseUrl.replace(/\/+$/, ''), VERSION: '1.0.0', WITH_CREDENTIALS: false, CREDENTIALS: 'omit', TOKEN: apiKey, HEADERS: { 'Content-Type': 'application/json', + Accept: 'application/json', Authorization: apiKey.startsWith('Basic ') || apiKey.startsWith('Bearer ') ? apiKey @@ -53,6 +61,28 @@ export async function makeSapsuccessfactorsRequest( }, }; + // Map query keys to standard OData v2 parameters + const formattedQuery: Record = + { + $format: 'json', + }; + if (query) { + const odataKeys = new Set([ + 'filter', + 'select', + 'expand', + 'top', + 'skip', + 'orderby', + ]); + for (const [k, v] of Object.entries(query)) { + if (v !== undefined) { + const targetKey = odataKeys.has(k) ? `$${k}` : k; + formattedQuery[targetKey] = v; + } + } + } + const requestOptions: ApiRequestOptions = { method, url: endpoint.startsWith('/') ? endpoint : `/${endpoint}`, @@ -61,7 +91,7 @@ export async function makeSapsuccessfactorsRequest( ? body : undefined, mediaType: 'application/json; charset=utf-8', - query: method === 'GET' ? query : undefined, + query: method === 'GET' ? formattedQuery : undefined, }; try { @@ -72,6 +102,6 @@ export async function makeSapsuccessfactorsRequest( if (error instanceof ApiError) throw error; if (error instanceof Error) throw new SapsuccessfactorsAPIError(error.message); - throw new SapsuccessfactorsAPIError('Unknown error'); + throw new SapsuccessfactorsAPIError('Unknown error occurred'); } } diff --git a/packages/sapsuccessfactors/endpoints/a.ts b/packages/sapsuccessfactors/endpoints/a.ts index 8fc163ffb..f8da5247c 100644 --- a/packages/sapsuccessfactors/endpoints/a.ts +++ b/packages/sapsuccessfactors/endpoints/a.ts @@ -15,6 +15,8 @@ export const createAFeedbackRequest: SapsuccessfactorsEndpoints['createAFeedback SapsuccessfactorsEndpointInputSchemas.createAFeedbackRequest.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; @@ -23,6 +25,7 @@ export const createAFeedbackRequest: SapsuccessfactorsEndpoints['createAFeedback >('odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', ctx.key, { method: 'POST', body: (body ?? rest) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.createAFeedbackRequest.parse( diff --git a/packages/sapsuccessfactors/endpoints/application.ts b/packages/sapsuccessfactors/endpoints/application.ts index 83a521403..99eaf8a9c 100644 --- a/packages/sapsuccessfactors/endpoints/application.ts +++ b/packages/sapsuccessfactors/endpoints/application.ts @@ -15,13 +15,19 @@ export const getApplicationInterview: SapsuccessfactorsEndpoints['getApplication SapsuccessfactorsEndpointInputSchemas.getApplicationInterview.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getApplicationInterview'] - >('odata/v2/ApplicationInterview', ctx.key, { method: 'GET', query }); + >('odata/v2/ApplicationInterview', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getApplicationInterview.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/approve.ts b/packages/sapsuccessfactors/endpoints/approve.ts index c4465b9ef..abe38d0ea 100644 --- a/packages/sapsuccessfactors/endpoints/approve.ts +++ b/packages/sapsuccessfactors/endpoints/approve.ts @@ -15,11 +15,14 @@ export const approveCalibrationSession: SapsuccessfactorsEndpoints['approveCalib SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['approveCalibrationSession'] >('odata/v4/CalSession.svc/Approve', ctx.key, { method: 'POST', body: (validatedInput ?? {}) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.approveCalibrationSession.parse( diff --git a/packages/sapsuccessfactors/endpoints/background.ts b/packages/sapsuccessfactors/endpoints/background.ts index 08f85cb80..f42431989 100644 --- a/packages/sapsuccessfactors/endpoints/background.ts +++ b/packages/sapsuccessfactors/endpoints/background.ts @@ -15,13 +15,19 @@ export const getBackgroundEducation: SapsuccessfactorsEndpoints['getBackgroundEd SapsuccessfactorsEndpointInputSchemas.getBackgroundEducation.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getBackgroundEducation'] - >('odata/v2/BackgroundEducation', ctx.key, { method: 'GET', query }); + >('odata/v2/BackgroundEducation', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getBackgroundEducation.parse( response, @@ -43,13 +49,19 @@ export const getBackgroundMobility: SapsuccessfactorsEndpoints['getBackgroundMob SapsuccessfactorsEndpointInputSchemas.getBackgroundMobility.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getBackgroundMobility'] - >('odata/v2/BackgroundMobility', ctx.key, { method: 'GET', query }); + >('odata/v2/BackgroundMobility', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getBackgroundMobility.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/calibration.ts b/packages/sapsuccessfactors/endpoints/calibration.ts index 5123b0924..4c53ad3b9 100644 --- a/packages/sapsuccessfactors/endpoints/calibration.ts +++ b/packages/sapsuccessfactors/endpoints/calibration.ts @@ -15,6 +15,8 @@ export const getCalibrationSessionById: SapsuccessfactorsEndpoints['getCalibrati SapsuccessfactorsEndpointInputSchemas.getCalibrationSessionById.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { session_id, ...query } = (validatedInput ?? {}) as { session_id?: string; }; @@ -26,6 +28,7 @@ export const getCalibrationSessionById: SapsuccessfactorsEndpoints['getCalibrati >(resourcePath, ctx.key, { method: 'GET', query: query as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessionById.parse( @@ -48,6 +51,8 @@ export const getCalibrationSessions: SapsuccessfactorsEndpoints['getCalibrationS SapsuccessfactorsEndpointInputSchemas.getCalibrationSessions.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined @@ -57,6 +62,7 @@ export const getCalibrationSessions: SapsuccessfactorsEndpoints['getCalibrationS >('odata/v4/CalSession.svc/CalibrationSession', ctx.key, { method: 'GET', query, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessions.parse( @@ -79,6 +85,8 @@ export const getCalibrationSubjectById: SapsuccessfactorsEndpoints['getCalibrati SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectById.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { subject_id, ...query } = (validatedInput ?? {}) as { subject_id?: string; }; @@ -90,6 +98,7 @@ export const getCalibrationSubjectById: SapsuccessfactorsEndpoints['getCalibrati >(resourcePath, ctx.key, { method: 'GET', query: query as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectById.parse( @@ -112,6 +121,8 @@ export const getCalibrationSubjectRatings: SapsuccessfactorsEndpoints['getCalibr SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectRatings.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined @@ -121,6 +132,7 @@ export const getCalibrationSubjectRatings: SapsuccessfactorsEndpoints['getCalibr >('odata/v4/CalSession.svc/CalibrationSubject', ctx.key, { method: 'GET', query, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectRatings.parse( @@ -143,6 +155,8 @@ export const updateCalibrationSubjectRatings: SapsuccessfactorsEndpoints['update SapsuccessfactorsEndpointInputSchemas.updateCalibrationSubjectRatings.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { subject_id, body, ...rest } = (validatedInput ?? {}) as { subject_id?: string; body?: Record; @@ -155,6 +169,7 @@ export const updateCalibrationSubjectRatings: SapsuccessfactorsEndpoints['update >(resourcePath, ctx.key, { method: 'PATCH', body: (body ?? rest) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.updateCalibrationSubjectRatings.parse( diff --git a/packages/sapsuccessfactors/endpoints/candidates.ts b/packages/sapsuccessfactors/endpoints/candidates.ts index 98a8a1c54..11893e239 100644 --- a/packages/sapsuccessfactors/endpoints/candidates.ts +++ b/packages/sapsuccessfactors/endpoints/candidates.ts @@ -13,13 +13,15 @@ export const listCandidates: SapsuccessfactorsEndpoints['listCandidates'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.listCandidates.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listCandidates'] - >('odata/v2/Candidate', ctx.key, { method: 'GET', query }); + >('odata/v2/Candidate', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.listCandidates.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/cdp.ts b/packages/sapsuccessfactors/endpoints/cdp.ts index aabd074b3..f75f90a8c 100644 --- a/packages/sapsuccessfactors/endpoints/cdp.ts +++ b/packages/sapsuccessfactors/endpoints/cdp.ts @@ -15,9 +15,11 @@ export const getCdpLearningMetadata: SapsuccessfactorsEndpoints['getCdpLearningM SapsuccessfactorsEndpointInputSchemas.getCdpLearningMetadata.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getCdpLearningMetadata'] - >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCdpLearningMetadata.parse( response, @@ -39,11 +41,14 @@ export const refreshCdpLearningMetadata: SapsuccessfactorsEndpoints['refreshCdpL SapsuccessfactorsEndpointInputSchemas.refreshCdpLearningMetadata.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['refreshCdpLearningMetadata'] >('odata/v2/RefreshCDPLearningMetadata', ctx.key, { method: 'POST', body: (validatedInput ?? {}) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.refreshCdpLearningMetadata.parse( diff --git a/packages/sapsuccessfactors/endpoints/current.ts b/packages/sapsuccessfactors/endpoints/current.ts index b42911b13..f86ef32f4 100644 --- a/packages/sapsuccessfactors/endpoints/current.ts +++ b/packages/sapsuccessfactors/endpoints/current.ts @@ -13,13 +13,15 @@ export const getCurrentUser: SapsuccessfactorsEndpoints['getCurrentUser'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getCurrentUser.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = (validatedInput ?? {}) as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getCurrentUser'] - >('odata/v2/User', ctx.key, { method: 'GET', query }); + >('odata/v2/User', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCurrentUser.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/custom.ts b/packages/sapsuccessfactors/endpoints/custom.ts index fb18cab26..0006399a4 100644 --- a/packages/sapsuccessfactors/endpoints/custom.ts +++ b/packages/sapsuccessfactors/endpoints/custom.ts @@ -15,6 +15,8 @@ export const getCustomMdfObject: SapsuccessfactorsEndpoints['getCustomMdfObject' SapsuccessfactorsEndpointInputSchemas.getCustomMdfObject.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { custom_object, ...rest } = (validatedInput ?? {}) as { custom_object?: string; }; @@ -31,6 +33,7 @@ export const getCustomMdfObject: SapsuccessfactorsEndpoints['getCustomMdfObject' >(resourcePath, ctx.key, { method: 'GET', query: rest as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getCustomMdfObject.parse(response); diff --git a/packages/sapsuccessfactors/endpoints/emp.ts b/packages/sapsuccessfactors/endpoints/emp.ts index e0c903ff3..f7951f7b5 100644 --- a/packages/sapsuccessfactors/endpoints/emp.ts +++ b/packages/sapsuccessfactors/endpoints/emp.ts @@ -15,13 +15,15 @@ export const listEmpEmployment: SapsuccessfactorsEndpoints['listEmpEmployment'] SapsuccessfactorsEndpointInputSchemas.listEmpEmployment.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listEmpEmployment'] - >('odata/v2/EmpEmployment', ctx.key, { method: 'GET', query }); + >('odata/v2/EmpEmployment', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.listEmpEmployment.parse(response); await logEventFromContext( @@ -41,13 +43,19 @@ export const getEmpEmploymentTermination: SapsuccessfactorsEndpoints['getEmpEmpl SapsuccessfactorsEndpointInputSchemas.getEmpEmploymentTermination.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmpEmploymentTermination'] - >('odata/v2/EmpEmploymentTermination', ctx.key, { method: 'GET', query }); + >('odata/v2/EmpEmploymentTermination', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getEmpEmploymentTermination.parse( response, @@ -69,13 +77,19 @@ export const getEmpPayCompRecurring: SapsuccessfactorsEndpoints['getEmpPayCompRe SapsuccessfactorsEndpointInputSchemas.getEmpPayCompRecurring.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmpPayCompRecurring'] - >('odata/v2/EmpPayCompRecurring', ctx.key, { method: 'GET', query }); + >('odata/v2/EmpPayCompRecurring', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompRecurring.parse( response, @@ -97,13 +111,19 @@ export const getEmpPayCompNonRecurring: SapsuccessfactorsEndpoints['getEmpPayCom SapsuccessfactorsEndpointInputSchemas.getEmpPayCompNonRecurring.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmpPayCompNonRecurring'] - >('odata/v2/EmpPayCompNonRecurring', ctx.key, { method: 'GET', query }); + >('odata/v2/EmpPayCompNonRecurring', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompNonRecurring.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/employee.ts b/packages/sapsuccessfactors/endpoints/employee.ts index 1af4c1a18..0c3389d1f 100644 --- a/packages/sapsuccessfactors/endpoints/employee.ts +++ b/packages/sapsuccessfactors/endpoints/employee.ts @@ -13,13 +13,15 @@ export const getEmployeeTime: SapsuccessfactorsEndpoints['getEmployeeTime'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getEmployeeTime.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmployeeTime'] - >('odata/v2/EmployeeTime', ctx.key, { method: 'GET', query }); + >('odata/v2/EmployeeTime', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getEmployeeTime.parse(response); await logEventFromContext( @@ -39,13 +41,19 @@ export const getEmployeeTimesheet: SapsuccessfactorsEndpoints['getEmployeeTimesh SapsuccessfactorsEndpointInputSchemas.getEmployeeTimesheet.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getEmployeeTimesheet'] - >('odata/v2/EmployeeTimeSheet', ctx.key, { method: 'GET', query }); + >('odata/v2/EmployeeTimeSheet', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getEmployeeTimesheet.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/feedback.ts b/packages/sapsuccessfactors/endpoints/feedback.ts index 0fbc584a3..74ee84e7a 100644 --- a/packages/sapsuccessfactors/endpoints/feedback.ts +++ b/packages/sapsuccessfactors/endpoints/feedback.ts @@ -15,6 +15,8 @@ export const getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoints['get SapsuccessfactorsEndpointInputSchemas.getFeedbackRecordsServiceAvailable.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined @@ -24,6 +26,7 @@ export const getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoints['get >('odata/v4/ContinuousPerformanceManagement.svc/Feedback', ctx.key, { method: 'GET', query, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFeedbackRecordsServiceAvailable.parse( diff --git a/packages/sapsuccessfactors/endpoints/fo.ts b/packages/sapsuccessfactors/endpoints/fo.ts index e6428f4d9..713413969 100644 --- a/packages/sapsuccessfactors/endpoints/fo.ts +++ b/packages/sapsuccessfactors/endpoints/fo.ts @@ -15,13 +15,15 @@ export const getFoBusinessUnit: SapsuccessfactorsEndpoints['getFoBusinessUnit'] SapsuccessfactorsEndpointInputSchemas.getFoBusinessUnit.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoBusinessUnit'] - >('odata/v2/FOBusinessUnit', ctx.key, { method: 'GET', query }); + >('odata/v2/FOBusinessUnit', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoBusinessUnit.parse(response); await logEventFromContext( @@ -41,13 +43,15 @@ export const getFoCompany: SapsuccessfactorsEndpoints['getFoCompany'] = async ( ) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoCompany.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoCompany'] - >('odata/v2/FOCompany', ctx.key, { method: 'GET', query }); + >('odata/v2/FOCompany', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoCompany.parse(response); await logEventFromContext( @@ -65,13 +69,15 @@ export const getFoCostCenter: SapsuccessfactorsEndpoints['getFoCostCenter'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoCostCenter.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoCostCenter'] - >('odata/v2/FOCostCenter', ctx.key, { method: 'GET', query }); + >('odata/v2/FOCostCenter', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoCostCenter.parse(response); await logEventFromContext( @@ -89,13 +95,15 @@ export const getFoDepartment: SapsuccessfactorsEndpoints['getFoDepartment'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoDepartment.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoDepartment'] - >('odata/v2/FODepartment', ctx.key, { method: 'GET', query }); + >('odata/v2/FODepartment', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoDepartment.parse(response); await logEventFromContext( @@ -115,13 +123,15 @@ export const getFoJobCode: SapsuccessfactorsEndpoints['getFoJobCode'] = async ( ) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoJobCode.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoJobCode'] - >('odata/v2/FOJobCode', ctx.key, { method: 'GET', query }); + >('odata/v2/FOJobCode', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoJobCode.parse(response); await logEventFromContext( @@ -139,13 +149,15 @@ export const getFoJobFunction: SapsuccessfactorsEndpoints['getFoJobFunction'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoJobFunction.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoJobFunction'] - >('odata/v2/FOJobFunction', ctx.key, { method: 'GET', query }); + >('odata/v2/FOJobFunction', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoJobFunction.parse(response); await logEventFromContext( @@ -163,13 +175,15 @@ export const getFoLocation: SapsuccessfactorsEndpoints['getFoLocation'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoLocation.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoLocation'] - >('odata/v2/FOLocation', ctx.key, { method: 'GET', query }); + >('odata/v2/FOLocation', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoLocation.parse(response); await logEventFromContext( @@ -187,13 +201,15 @@ export const getFoPayGroup: SapsuccessfactorsEndpoints['getFoPayGroup'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFoPayGroup.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFoPayGroup'] - >('odata/v2/FOPayGroup', ctx.key, { method: 'GET', query }); + >('odata/v2/FOPayGroup', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFoPayGroup.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/form.ts b/packages/sapsuccessfactors/endpoints/form.ts index 470bb7393..c0fb99464 100644 --- a/packages/sapsuccessfactors/endpoints/form.ts +++ b/packages/sapsuccessfactors/endpoints/form.ts @@ -13,13 +13,15 @@ export const getFormContent: SapsuccessfactorsEndpoints['getFormContent'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getFormContent.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getFormContent'] - >('odata/v2/FormContent', ctx.key, { method: 'GET', query }); + >('odata/v2/FormContent', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getFormContent.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/give.ts b/packages/sapsuccessfactors/endpoints/give.ts index 1cd0d4116..653de3548 100644 --- a/packages/sapsuccessfactors/endpoints/give.ts +++ b/packages/sapsuccessfactors/endpoints/give.ts @@ -15,6 +15,8 @@ export const giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoints SapsuccessfactorsEndpointInputSchemas.giveFeedbackOrRespondToAFeedbackRequest.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; @@ -23,6 +25,7 @@ export const giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoints >('odata/v4/ContinuousPerformanceManagement.svc/Feedback', ctx.key, { method: 'POST', body: (body ?? rest) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.giveFeedbackOrRespondToAFeedbackRequest.parse( diff --git a/packages/sapsuccessfactors/endpoints/goal.ts b/packages/sapsuccessfactors/endpoints/goal.ts index bf1fab4ad..9b3ce76ce 100644 --- a/packages/sapsuccessfactors/endpoints/goal.ts +++ b/packages/sapsuccessfactors/endpoints/goal.ts @@ -15,13 +15,19 @@ export const getGoalPlanTemplate: SapsuccessfactorsEndpoints['getGoalPlanTemplat SapsuccessfactorsEndpointInputSchemas.getGoalPlanTemplate.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getGoalPlanTemplate'] - >('odata/v2/GoalPlanTemplate', ctx.key, { method: 'GET', query }); + >('odata/v2/GoalPlanTemplate', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getGoalPlanTemplate.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/goals.ts b/packages/sapsuccessfactors/endpoints/goals.ts index db8ffa2e4..10dc6c148 100644 --- a/packages/sapsuccessfactors/endpoints/goals.ts +++ b/packages/sapsuccessfactors/endpoints/goals.ts @@ -13,6 +13,8 @@ export const getGoalsByPlan: SapsuccessfactorsEndpoints['getGoalsByPlan'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getGoalsByPlan.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { goal_plan_id, ...rest } = (validatedInput ?? {}) as { goal_plan_id?: string; }; @@ -25,6 +27,7 @@ export const getGoalsByPlan: SapsuccessfactorsEndpoints['getGoalsByPlan'] = >(resourcePath, ctx.key, { method: 'GET', query: rest as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getGoalsByPlan.parse(response); diff --git a/packages/sapsuccessfactors/endpoints/internal.ts b/packages/sapsuccessfactors/endpoints/internal.ts index b0406c809..8245fdf39 100644 --- a/packages/sapsuccessfactors/endpoints/internal.ts +++ b/packages/sapsuccessfactors/endpoints/internal.ts @@ -15,11 +15,14 @@ export const updateInternalUsernameNewHiresAfter: SapsuccessfactorsEndpoints['up SapsuccessfactorsEndpointInputSchemas.updateInternalUsernameNewHiresAfter.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['updateInternalUsernameNewHiresAfter'] >('odata/v2/updateUserNamePostHiring', ctx.key, { method: 'POST', body: (validatedInput ?? {}) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.updateInternalUsernameNewHiresAfter.parse( diff --git a/packages/sapsuccessfactors/endpoints/interview.ts b/packages/sapsuccessfactors/endpoints/interview.ts index 7593af760..8fc2fe95c 100644 --- a/packages/sapsuccessfactors/endpoints/interview.ts +++ b/packages/sapsuccessfactors/endpoints/interview.ts @@ -15,13 +15,19 @@ export const getInterviewOverallAssessment: SapsuccessfactorsEndpoints['getInter SapsuccessfactorsEndpointInputSchemas.getInterviewOverallAssessment.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getInterviewOverallAssessment'] - >('odata/v2/OverallInterviewAssessment', ctx.key, { method: 'GET', query }); + >('odata/v2/OverallInterviewAssessment', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getInterviewOverallAssessment.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/job.ts b/packages/sapsuccessfactors/endpoints/job.ts index 90ddba3ad..c5d518275 100644 --- a/packages/sapsuccessfactors/endpoints/job.ts +++ b/packages/sapsuccessfactors/endpoints/job.ts @@ -15,13 +15,15 @@ export const getJobApplication: SapsuccessfactorsEndpoints['getJobApplication'] SapsuccessfactorsEndpointInputSchemas.getJobApplication.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getJobApplication'] - >('odata/v2/JobApplication', ctx.key, { method: 'GET', query }); + >('odata/v2/JobApplication', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getJobApplication.parse(response); await logEventFromContext( @@ -41,13 +43,15 @@ export const getJobRequisition: SapsuccessfactorsEndpoints['getJobRequisition'] SapsuccessfactorsEndpointInputSchemas.getJobRequisition.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getJobRequisition'] - >('odata/v2/JobRequisition', ctx.key, { method: 'GET', query }); + >('odata/v2/JobRequisition', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getJobRequisition.parse(response); await logEventFromContext( @@ -67,13 +71,19 @@ export const getJobReqScreeningQuestion: SapsuccessfactorsEndpoints['getJobReqSc SapsuccessfactorsEndpointInputSchemas.getJobReqScreeningQuestion.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getJobReqScreeningQuestion'] - >('odata/v2/JobReqScreeningQuestion', ctx.key, { method: 'GET', query }); + >('odata/v2/JobReqScreeningQuestion', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getJobReqScreeningQuestion.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/learning.ts b/packages/sapsuccessfactors/endpoints/learning.ts index 0fff58410..118eef73a 100644 --- a/packages/sapsuccessfactors/endpoints/learning.ts +++ b/packages/sapsuccessfactors/endpoints/learning.ts @@ -15,6 +15,8 @@ export const createLearningActivitiesBulk: SapsuccessfactorsEndpoints['createLea SapsuccessfactorsEndpointInputSchemas.createLearningActivitiesBulk.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; @@ -23,6 +25,7 @@ export const createLearningActivitiesBulk: SapsuccessfactorsEndpoints['createLea >('odata/v2/LearningActivity', ctx.key, { method: 'POST', body: (body ?? rest) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.createLearningActivitiesBulk.parse( diff --git a/packages/sapsuccessfactors/endpoints/metadata.ts b/packages/sapsuccessfactors/endpoints/metadata.ts index b94035797..d8b40ffaf 100644 --- a/packages/sapsuccessfactors/endpoints/metadata.ts +++ b/packages/sapsuccessfactors/endpoints/metadata.ts @@ -15,11 +15,14 @@ export const refreshMetadataContFeedbackService: SapsuccessfactorsEndpoints['ref SapsuccessfactorsEndpointInputSchemas.refreshMetadataContFeedbackService.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['refreshMetadataContFeedbackService'] >('odata/v4/ContinuousPerformanceManagement.svc/RefreshMetadata', ctx.key, { method: 'POST', body: (validatedInput ?? {}) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.refreshMetadataContFeedbackService.parse( diff --git a/packages/sapsuccessfactors/endpoints/nomination.ts b/packages/sapsuccessfactors/endpoints/nomination.ts index bac9bb00d..7908c85ab 100644 --- a/packages/sapsuccessfactors/endpoints/nomination.ts +++ b/packages/sapsuccessfactors/endpoints/nomination.ts @@ -15,6 +15,8 @@ export const deleteNominationPositionTalentPool: SapsuccessfactorsEndpoints['del SapsuccessfactorsEndpointInputSchemas.deleteNominationPositionTalentPool.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { nomination_id } = (validatedInput ?? {}) as { nomination_id?: string; }; @@ -23,7 +25,7 @@ export const deleteNominationPositionTalentPool: SapsuccessfactorsEndpoints['del : 'odata/v4/NominationService.svc/Nomination'; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['deleteNominationPositionTalentPool'] - >(resourcePath, ctx.key, { method: 'DELETE' }); + >(resourcePath, ctx.key, { method: 'DELETE', apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.deleteNominationPositionTalentPool.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/odata.ts b/packages/sapsuccessfactors/endpoints/odata.ts index 070d67b22..1ed0be76a 100644 --- a/packages/sapsuccessfactors/endpoints/odata.ts +++ b/packages/sapsuccessfactors/endpoints/odata.ts @@ -15,9 +15,14 @@ export const getOdataMetadataCalibSessionService: SapsuccessfactorsEndpoints['ge SapsuccessfactorsEndpointInputSchemas.getOdataMetadataCalibSessionService.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataCalibSessionService'] - >('odata/v4/CalSession.svc/$metadata', ctx.key, { method: 'GET' }); + >('odata/v4/CalSession.svc/$metadata', ctx.key, { + method: 'GET', + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataCalibSessionService.parse( response, @@ -39,9 +44,11 @@ export const getOdataMetadataOnboardingAddl: SapsuccessfactorsEndpoints['getOdat SapsuccessfactorsEndpointInputSchemas.getOdataMetadataOnboardingAddl.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataOnboardingAddl'] - >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataOnboardingAddl.parse( response, @@ -63,9 +70,14 @@ export const getOdataMetadataForNominationService: SapsuccessfactorsEndpoints['g SapsuccessfactorsEndpointInputSchemas.getOdataMetadataForNominationService.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataForNominationService'] - >('odata/v4/NominationService.svc/$metadata', ctx.key, { method: 'GET' }); + >('odata/v4/NominationService.svc/$metadata', ctx.key, { + method: 'GET', + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataForNominationService.parse( response, @@ -87,9 +99,11 @@ export const getOdataUserMetadata: SapsuccessfactorsEndpoints['getOdataUserMetad SapsuccessfactorsEndpointInputSchemas.getOdataUserMetadata.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataUserMetadata'] - >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getOdataUserMetadata.parse( response, @@ -111,9 +125,11 @@ export const getOdataMetadataClockInclockOut: SapsuccessfactorsEndpoints['getOda SapsuccessfactorsEndpointInputSchemas.getOdataMetadataClockInclockOut.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOdataMetadataClockInclockOut'] - >('odata/v2/$metadata', ctx.key, { method: 'GET' }); + >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataClockInclockOut.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/onb2.ts b/packages/sapsuccessfactors/endpoints/onb2.ts index 0b7fc889e..d841038ab 100644 --- a/packages/sapsuccessfactors/endpoints/onb2.ts +++ b/packages/sapsuccessfactors/endpoints/onb2.ts @@ -13,13 +13,15 @@ export const getOnb2Process: SapsuccessfactorsEndpoints['getOnb2Process'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getOnb2Process.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getOnb2Process'] - >('odata/v2/ONB2Process', ctx.key, { method: 'GET', query }); + >('odata/v2/ONB2Process', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getOnb2Process.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/onboardee.ts b/packages/sapsuccessfactors/endpoints/onboardee.ts index 07f03d93d..20ef1ec8f 100644 --- a/packages/sapsuccessfactors/endpoints/onboardee.ts +++ b/packages/sapsuccessfactors/endpoints/onboardee.ts @@ -13,6 +13,8 @@ export const createOnboardee: SapsuccessfactorsEndpoints['createOnboardee'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.createOnboardee.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; @@ -21,6 +23,7 @@ export const createOnboardee: SapsuccessfactorsEndpoints['createOnboardee'] = >('odata/v2/Onboardee', ctx.key, { method: 'POST', body: (body ?? rest) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.createOnboardee.parse(response); diff --git a/packages/sapsuccessfactors/endpoints/pending.ts b/packages/sapsuccessfactors/endpoints/pending.ts index 1596c11b4..82b6c2cdb 100644 --- a/packages/sapsuccessfactors/endpoints/pending.ts +++ b/packages/sapsuccessfactors/endpoints/pending.ts @@ -15,6 +15,8 @@ export const getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoints['get SapsuccessfactorsEndpointInputSchemas.getPendingFeedbackRequestsFeedback.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined @@ -24,6 +26,7 @@ export const getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoints['get >('odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', ctx.key, { method: 'GET', query, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getPendingFeedbackRequestsFeedback.parse( diff --git a/packages/sapsuccessfactors/endpoints/per.ts b/packages/sapsuccessfactors/endpoints/per.ts index c57e3402d..040bc0729 100644 --- a/packages/sapsuccessfactors/endpoints/per.ts +++ b/packages/sapsuccessfactors/endpoints/per.ts @@ -13,6 +13,8 @@ export const getPerPersonById: SapsuccessfactorsEndpoints['getPerPersonById'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { person_id_external, ...query } = (validatedInput ?? {}) as { person_id_external?: string; }; @@ -24,6 +26,7 @@ export const getPerPersonById: SapsuccessfactorsEndpoints['getPerPersonById'] = >(resourcePath, ctx.key, { method: 'GET', query: query as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getPerPersonById.parse(response); @@ -42,13 +45,15 @@ export const listPerPerson: SapsuccessfactorsEndpoints['listPerPerson'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.listPerPerson.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listPerPerson'] - >('odata/v2/PerPerson', ctx.key, { method: 'GET', query }); + >('odata/v2/PerPerson', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.listPerPerson.parse(response); await logEventFromContext( @@ -66,13 +71,15 @@ export const getPerPersonal: SapsuccessfactorsEndpoints['getPerPersonal'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getPerPersonal.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPerPersonal'] - >('odata/v2/PerPersonal', ctx.key, { method: 'GET', query }); + >('odata/v2/PerPersonal', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getPerPersonal.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/picklist.ts b/packages/sapsuccessfactors/endpoints/picklist.ts index 31b97e76e..bc93876f2 100644 --- a/packages/sapsuccessfactors/endpoints/picklist.ts +++ b/packages/sapsuccessfactors/endpoints/picklist.ts @@ -15,13 +15,15 @@ export const getPicklist: SapsuccessfactorsEndpoints['getPicklist'] = async ( ) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getPicklist.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPicklist'] - >('odata/v2/Picklist', ctx.key, { method: 'GET', query }); + >('odata/v2/Picklist', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getPicklist.parse(response); await logEventFromContext( @@ -41,13 +43,15 @@ export const getPicklistOption: SapsuccessfactorsEndpoints['getPicklistOption'] SapsuccessfactorsEndpointInputSchemas.getPicklistOption.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPicklistOption'] - >('odata/v2/PicklistOption', ctx.key, { method: 'GET', query }); + >('odata/v2/PicklistOption', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getPicklistOption.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/position.ts b/packages/sapsuccessfactors/endpoints/position.ts index 35abcc239..0be660e87 100644 --- a/packages/sapsuccessfactors/endpoints/position.ts +++ b/packages/sapsuccessfactors/endpoints/position.ts @@ -15,13 +15,15 @@ export const getPosition: SapsuccessfactorsEndpoints['getPosition'] = async ( ) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getPosition.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getPosition'] - >('odata/v2/Position', ctx.key, { method: 'GET', query }); + >('odata/v2/Position', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getPosition.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/query.ts b/packages/sapsuccessfactors/endpoints/query.ts index c566e73e8..b1eae0a1c 100644 --- a/packages/sapsuccessfactors/endpoints/query.ts +++ b/packages/sapsuccessfactors/endpoints/query.ts @@ -15,13 +15,19 @@ export const queryAllAvailableClockClockOut: SapsuccessfactorsEndpoints['queryAl SapsuccessfactorsEndpointInputSchemas.queryAllAvailableClockClockOut.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['queryAllAvailableClockClockOut'] - >('odata/v2/ClockInClockOutGroup', ctx.key, { method: 'GET', query }); + >('odata/v2/ClockInClockOutGroup', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.queryAllAvailableClockClockOut.parse( response, @@ -43,6 +49,8 @@ export const queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoints['queryC SapsuccessfactorsEndpointInputSchemas.queryClockClockOutGroupCodeTime.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { code, ...query } = (validatedInput ?? {}) as { code?: string }; const resourcePath = code ? `odata/v2/ClockInClockOutGroup('${code}')` @@ -52,6 +60,7 @@ export const queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoints['queryC >(resourcePath, ctx.key, { method: 'GET', query: query as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.queryClockClockOutGroupCodeTime.parse( diff --git a/packages/sapsuccessfactors/endpoints/successor.ts b/packages/sapsuccessfactors/endpoints/successor.ts index 0af4e4c43..419c74995 100644 --- a/packages/sapsuccessfactors/endpoints/successor.ts +++ b/packages/sapsuccessfactors/endpoints/successor.ts @@ -15,6 +15,8 @@ export const createUpdateSuccessorNomination: SapsuccessfactorsEndpoints['create SapsuccessfactorsEndpointInputSchemas.createUpdateSuccessorNomination.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const { body, ...rest } = (validatedInput ?? {}) as { body?: Record; }; @@ -23,6 +25,7 @@ export const createUpdateSuccessorNomination: SapsuccessfactorsEndpoints['create >('odata/v4/NominationService.svc/Nomination', ctx.key, { method: 'POST', body: (body ?? rest) as Record, + apiBaseUrl, }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.createUpdateSuccessorNomination.parse( diff --git a/packages/sapsuccessfactors/endpoints/talent.ts b/packages/sapsuccessfactors/endpoints/talent.ts index 5b5fbdaac..b761d7848 100644 --- a/packages/sapsuccessfactors/endpoints/talent.ts +++ b/packages/sapsuccessfactors/endpoints/talent.ts @@ -13,13 +13,15 @@ export const getTalentPool: SapsuccessfactorsEndpoints['getTalentPool'] = async (ctx, input) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getTalentPool.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getTalentPool'] - >('odata/v2/TalentPool', ctx.key, { method: 'GET', query }); + >('odata/v2/TalentPool', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getTalentPool.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/temporary.ts b/packages/sapsuccessfactors/endpoints/temporary.ts index 08c494480..aa5288bac 100644 --- a/packages/sapsuccessfactors/endpoints/temporary.ts +++ b/packages/sapsuccessfactors/endpoints/temporary.ts @@ -15,13 +15,19 @@ export const getTemporaryTimeInformation: SapsuccessfactorsEndpoints['getTempora SapsuccessfactorsEndpointInputSchemas.getTemporaryTimeInformation.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getTemporaryTimeInformation'] - >('odata/v2/TemporaryTimeInfo', ctx.key, { method: 'GET', query }); + >('odata/v2/TemporaryTimeInfo', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getTemporaryTimeInformation.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/time.ts b/packages/sapsuccessfactors/endpoints/time.ts index 9faa922aa..65dccce9d 100644 --- a/packages/sapsuccessfactors/endpoints/time.ts +++ b/packages/sapsuccessfactors/endpoints/time.ts @@ -15,13 +15,19 @@ export const getTimeAccountSnapshot: SapsuccessfactorsEndpoints['getTimeAccountS SapsuccessfactorsEndpointInputSchemas.getTimeAccountSnapshot.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getTimeAccountSnapshot'] - >('odata/v2/TimeAccountSnapshot', ctx.key, { method: 'GET', query }); + >('odata/v2/TimeAccountSnapshot', ctx.key, { + method: 'GET', + query, + apiBaseUrl, + }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getTimeAccountSnapshot.parse( response, diff --git a/packages/sapsuccessfactors/endpoints/users.ts b/packages/sapsuccessfactors/endpoints/users.ts index 46f28b5b2..d3e11a953 100644 --- a/packages/sapsuccessfactors/endpoints/users.ts +++ b/packages/sapsuccessfactors/endpoints/users.ts @@ -16,13 +16,15 @@ export const listUsers: SapsuccessfactorsEndpoints['listUsers'] = async ( const validatedInput = SapsuccessfactorsEndpointInputSchemas.listUsers.parse( input ?? {}, ); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['listUsers'] - >('odata/v2/User', ctx.key, { method: 'GET', query }); + >('odata/v2/User', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.listUsers.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/endpoints/work.ts b/packages/sapsuccessfactors/endpoints/work.ts index 60229ff34..2ba669a8b 100644 --- a/packages/sapsuccessfactors/endpoints/work.ts +++ b/packages/sapsuccessfactors/endpoints/work.ts @@ -15,13 +15,15 @@ export const getWorkOrder: SapsuccessfactorsEndpoints['getWorkOrder'] = async ( ) => { const validatedInput = SapsuccessfactorsEndpointInputSchemas.getWorkOrder.parse(input ?? {}); + const apiBaseUrl = + (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; const query = validatedInput as Record< string, string | number | boolean | undefined >; const response = await makeSapsuccessfactorsRequest< SapsuccessfactorsEndpointOutputs['getWorkOrder'] - >('odata/v2/WorkOrder', ctx.key, { method: 'GET', query }); + >('odata/v2/WorkOrder', ctx.key, { method: 'GET', query, apiBaseUrl }); const validatedResponse = SapsuccessfactorsEndpointOutputSchemas.getWorkOrder.parse(response); await logEventFromContext( diff --git a/packages/sapsuccessfactors/error-handlers.ts b/packages/sapsuccessfactors/error-handlers.ts index 5a4f4c19f..7395b53ba 100644 --- a/packages/sapsuccessfactors/error-handlers.ts +++ b/packages/sapsuccessfactors/error-handlers.ts @@ -6,21 +6,40 @@ export const errorHandlers = { match: (error: Error) => { if (error instanceof ApiError && error.status === 429) return true; const msg = error.message.toLowerCase(); - return msg.includes('rate_limited') || msg.includes('429'); + return ( + msg.includes('429') || + msg.includes('rate limit') || + msg.includes('too many requests') + ); }, handler: async (error: Error) => { let retryAfterMs: number | undefined; if (error instanceof ApiError && error.retryAfter !== undefined) { retryAfterMs = error.retryAfter; } - return { maxRetries: 5, headersRetryAfterMs: retryAfterMs }; + return { maxRetries: 3, headersRetryAfterMs: retryAfterMs }; }, }, AUTH_ERROR: { match: (error: Error) => { - if (error instanceof ApiError && error.status === 401) return true; + if ( + error instanceof ApiError && + (error.status === 401 || error.status === 403) + ) + return true; const msg = error.message.toLowerCase(); - return msg.includes('unauthorized') || msg.includes('invalid_auth'); + return ( + msg.includes('unauthorized') || + msg.includes('forbidden') || + msg.includes('invalid credentials') + ); + }, + handler: async () => ({ maxRetries: 0 }), + }, + NOT_FOUND: { + match: (error: Error) => { + if (error instanceof ApiError && error.status === 404) return true; + return error.message.toLowerCase().includes('not found'); }, handler: async () => ({ maxRetries: 0 }), }, diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index b1b431de9..9a4ce9275 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -10,12 +10,20 @@ describe('Sapsuccessfactors schema and validation', () => { expect(SapsuccessfactorsSchema.version).toMatch(/^\d+\.\d+\.\d+$/); }); - it('declares an entities map', () => { + it('declares comprehensive entity schemas', () => { expect(typeof SapsuccessfactorsSchema.entities).toBe('object'); - expect(SapsuccessfactorsSchema.entities).not.toBeNull(); + expect(SapsuccessfactorsSchema.entities.user).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.person).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.personal).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.employment).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.calibrationSession).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.goalPlan).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.jobRequisition).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.candidate).toBeDefined(); + expect(SapsuccessfactorsSchema.entities.position).toBeDefined(); }); - it('validates approveCalibrationSession input schema', () => { + it('validates approveCalibrationSession input schema positive and negative cases', () => { const valid = { session_id: 'session-123' }; expect( SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.parse( @@ -29,7 +37,7 @@ describe('Sapsuccessfactors schema and validation', () => { ).toBe(false); }); - it('validates getPersonById input schema', () => { + it('validates getPersonById input schema positive and negative cases', () => { const valid = { person_id_external: 'emp-456' }; expect( SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(valid), diff --git a/packages/sapsuccessfactors/schema/database.ts b/packages/sapsuccessfactors/schema/database.ts index f96177686..e94e9dd86 100644 --- a/packages/sapsuccessfactors/schema/database.ts +++ b/packages/sapsuccessfactors/schema/database.ts @@ -1,19 +1,199 @@ import { z } from 'zod'; -export const SapsuccessfactorsUser = z.object({ - userId: z.string(), - username: z.string().optional(), - status: z.string().optional(), - email: z.string().optional(), -}); - -export const SapsuccessfactorsCalibrationSession = z.object({ - sessionId: z.string(), - sessionName: z.string().optional(), - status: z.string().optional(), -}); - -export type SapsuccessfactorsUser = z.infer; -export type SapsuccessfactorsCalibrationSession = z.infer< - typeof SapsuccessfactorsCalibrationSession +const S = z.string().nullable().optional(); +const N = z.number().nullable().optional(); +const B = z.boolean().nullable().optional(); + +/** + * SAP SuccessFactors User Entity + */ +export const SapsuccessfactorsUserEntity = z + .object({ + userId: z.string(), + username: S, + firstName: S, + lastName: S, + email: S, + title: S, + department: S, + division: S, + location: S, + status: S, + hireDate: S, + lastModifiedDateTime: S, + }) + .passthrough(); +export type SapsuccessfactorsUserEntity = z.infer< + typeof SapsuccessfactorsUserEntity +>; + +/** + * SAP SuccessFactors PerPerson Entity + */ +export const SapsuccessfactorsPersonEntity = z + .object({ + personIdExternal: z.string(), + dateOfBirth: S, + countryOfBirth: S, + placeOfBirth: S, + userId: S, + }) + .passthrough(); +export type SapsuccessfactorsPersonEntity = z.infer< + typeof SapsuccessfactorsPersonEntity +>; + +/** + * SAP SuccessFactors PerPersonal Entity + */ +export const SapsuccessfactorsPersonalEntity = z + .object({ + personIdExternal: z.string(), + startDate: S, + endDate: S, + firstName: S, + lastName: S, + gender: S, + maritalStatus: S, + nationality: S, + }) + .passthrough(); +export type SapsuccessfactorsPersonalEntity = z.infer< + typeof SapsuccessfactorsPersonalEntity +>; + +/** + * SAP SuccessFactors EmpEmployment Entity + */ +export const SapsuccessfactorsEmploymentEntity = z + .object({ + userId: z.string(), + personIdExternal: S, + startDate: S, + endDate: S, + employmentStatus: S, + }) + .passthrough(); +export type SapsuccessfactorsEmploymentEntity = z.infer< + typeof SapsuccessfactorsEmploymentEntity +>; + +/** + * SAP SuccessFactors CalibrationSession Entity + */ +export const SapsuccessfactorsCalibrationSessionEntity = z + .object({ + sessionId: z.string(), + sessionName: S, + sessionType: S, + status: S, + startDate: S, + endDate: S, + }) + .passthrough(); +export type SapsuccessfactorsCalibrationSessionEntity = z.infer< + typeof SapsuccessfactorsCalibrationSessionEntity +>; + +/** + * SAP SuccessFactors GoalPlanTemplate Entity + */ +export const SapsuccessfactorsGoalPlanEntity = z + .object({ + id: z.string(), + name: S, + planType: S, + dueDate: S, + }) + .passthrough(); +export type SapsuccessfactorsGoalPlanEntity = z.infer< + typeof SapsuccessfactorsGoalPlanEntity +>; + +/** + * SAP SuccessFactors Goal Entity + */ +export const SapsuccessfactorsGoalEntity = z + .object({ + id: z.string(), + userId: S, + name: S, + state: S, + metric: S, + done: N, + start: S, + due: S, + }) + .passthrough(); +export type SapsuccessfactorsGoalEntity = z.infer< + typeof SapsuccessfactorsGoalEntity +>; + +/** + * SAP SuccessFactors JobRequisition Entity + */ +export const SapsuccessfactorsJobRequisitionEntity = z + .object({ + jobReqId: z.string(), + jobTitle: S, + department: S, + division: S, + location: S, + status: S, + }) + .passthrough(); +export type SapsuccessfactorsJobRequisitionEntity = z.infer< + typeof SapsuccessfactorsJobRequisitionEntity +>; + +/** + * SAP SuccessFactors Candidate Entity + */ +export const SapsuccessfactorsCandidateEntity = z + .object({ + candidateId: z.string(), + firstName: S, + lastName: S, + primaryEmail: S, + cellPhone: S, + city: S, + country: S, + }) + .passthrough(); +export type SapsuccessfactorsCandidateEntity = z.infer< + typeof SapsuccessfactorsCandidateEntity +>; + +/** + * SAP SuccessFactors JobApplication Entity + */ +export const SapsuccessfactorsJobApplicationEntity = z + .object({ + applicationId: z.string(), + jobReqId: S, + candidateId: S, + appStatusId: S, + applicationDate: S, + }) + .passthrough(); +export type SapsuccessfactorsJobApplicationEntity = z.infer< + typeof SapsuccessfactorsJobApplicationEntity +>; + +/** + * SAP SuccessFactors Position Entity + */ +export const SapsuccessfactorsPositionEntity = z + .object({ + code: z.string(), + externalName: S, + effectiveStartDate: S, + effectiveStatus: S, + jobCode: S, + department: S, + company: S, + }) + .passthrough(); +export type SapsuccessfactorsPositionEntity = z.infer< + typeof SapsuccessfactorsPositionEntity >; diff --git a/packages/sapsuccessfactors/schema/index.ts b/packages/sapsuccessfactors/schema/index.ts index 0a9d56b19..98425dc22 100644 --- a/packages/sapsuccessfactors/schema/index.ts +++ b/packages/sapsuccessfactors/schema/index.ts @@ -1,4 +1,32 @@ +import { + SapsuccessfactorsCalibrationSessionEntity, + SapsuccessfactorsCandidateEntity, + SapsuccessfactorsEmploymentEntity, + SapsuccessfactorsGoalEntity, + SapsuccessfactorsGoalPlanEntity, + SapsuccessfactorsJobApplicationEntity, + SapsuccessfactorsJobRequisitionEntity, + SapsuccessfactorsPersonalEntity, + SapsuccessfactorsPersonEntity, + SapsuccessfactorsPositionEntity, + SapsuccessfactorsUserEntity, +} from './database'; + export const SapsuccessfactorsSchema = { version: '1.0.0', - entities: {}, + entities: { + user: SapsuccessfactorsUserEntity, + person: SapsuccessfactorsPersonEntity, + personal: SapsuccessfactorsPersonalEntity, + employment: SapsuccessfactorsEmploymentEntity, + calibrationSession: SapsuccessfactorsCalibrationSessionEntity, + goalPlan: SapsuccessfactorsGoalPlanEntity, + goal: SapsuccessfactorsGoalEntity, + jobRequisition: SapsuccessfactorsJobRequisitionEntity, + candidate: SapsuccessfactorsCandidateEntity, + jobApplication: SapsuccessfactorsJobApplicationEntity, + position: SapsuccessfactorsPositionEntity, + }, } as const; + +export * from './database'; From ec76b4bea513bc8a917b5357f6c27c39427f4eee Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 01:39:39 +0530 Subject: [PATCH 05/18] test(sapsuccessfactors): eliminate event logging test warnings --- packages/sapsuccessfactors/api.test.ts | 3 ++- packages/sapsuccessfactors/index.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 24725d375..873144e77 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -30,7 +30,8 @@ describe('SapSuccessfactors Plugin', () => { key: 'test-api-token', authType: 'api_key' as const, options: { apiBaseUrl: 'https://api10.successfactors.com' }, - db: {}, + database: undefined, + $getAccountId: async () => 'acc_test_123', log: jest.fn(), } as any; diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts index 59a70aff7..59f641f0d 100644 --- a/packages/sapsuccessfactors/index.ts +++ b/packages/sapsuccessfactors/index.ts @@ -66,6 +66,7 @@ export type SapsuccessfactorsPluginOptions = { /** Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. */ authType?: PickAuth<'api_key'>; key?: string; + apiBaseUrl?: string; webhookSecret?: string; hooks?: InternalSapsuccessfactorsPlugin['hooks']; webhookHooks?: InternalSapsuccessfactorsPlugin['webhookHooks']; From 41f6deba028f3f78bb9b97aac8c8f3828e375574 Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 01:49:01 +0530 Subject: [PATCH 06/18] docs(sapsuccessfactors): document REST/OData scope in webhooks --- packages/sapsuccessfactors/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts index 59f641f0d..1276513a3 100644 --- a/packages/sapsuccessfactors/index.ts +++ b/packages/sapsuccessfactors/index.ts @@ -315,6 +315,8 @@ const sapsuccessfactorsEndpointsNested = { }, } as const; +// SAP SuccessFactors webhook/event subscriptions are not implemented in this +// provider yet; this integration currently exposes REST/OData endpoints only. const sapsuccessfactorsWebhooksNested = {} as const; export const sapsuccessfactorsEndpointSchemas = { From d25a15b2278397eaec689054d1b3b0b912fcf6da Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 02:39:59 +0530 Subject: [PATCH 07/18] feat(sapsuccessfactors): support sandbox api key and flexible odata schemas --- packages/sapsuccessfactors/api.test.ts | 3 +- packages/sapsuccessfactors/client.ts | 24 +- packages/sapsuccessfactors/endpoints/types.ts | 576 ++++++++++-------- packages/sapsuccessfactors/index.ts | 31 +- 4 files changed, 343 insertions(+), 291 deletions(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 873144e77..de67aaec7 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -28,7 +28,6 @@ describe('SapSuccessfactors Plugin', () => { }); const mockCtx = { key: 'test-api-token', - authType: 'api_key' as const, options: { apiBaseUrl: 'https://api10.successfactors.com' }, database: undefined, $getAccountId: async () => 'acc_test_123', @@ -41,7 +40,7 @@ describe('SapSuccessfactors Plugin', () => { it('initializes plugin with correct id and configuration', () => { expect(plugin.id).toBe('sapsuccessfactors'); - expect(plugin.authConfig).toBeDefined(); + expect(plugin.authConfig).toBeUndefined(); expect(plugin.endpoints).toBeDefined(); expect(plugin.schema).toBeDefined(); }); diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts index 445f24444..bb71c9219 100644 --- a/packages/sapsuccessfactors/client.ts +++ b/packages/sapsuccessfactors/client.ts @@ -45,19 +45,31 @@ export async function makeSapsuccessfactorsRequest( query, } = options; + let base = apiBaseUrl.replace(/\/+$/, ''); + const url = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + if (base.endsWith('/odata/v2') && url.startsWith('/odata/v2')) { + base = base.slice(0, -'/odata/v2'.length); + } + + const isSandbox = base.includes('sandbox.api.sap.com'); + const config: OpenAPIConfig = { - BASE: apiBaseUrl.replace(/\/+$/, ''), + BASE: base, VERSION: '1.0.0', WITH_CREDENTIALS: false, CREDENTIALS: 'omit', - TOKEN: apiKey, + TOKEN: isSandbox ? undefined : apiKey, HEADERS: { 'Content-Type': 'application/json', Accept: 'application/json', - Authorization: - apiKey.startsWith('Basic ') || apiKey.startsWith('Bearer ') - ? apiKey - : `Bearer ${apiKey}`, + ...(isSandbox + ? { APIKey: apiKey, apikey: apiKey } + : { + Authorization: + apiKey.startsWith('Basic ') || apiKey.startsWith('Bearer ') + ? apiKey + : `Bearer ${apiKey}`, + }), }, }; diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts index 3d87885ff..8ae70ac70 100644 --- a/packages/sapsuccessfactors/endpoints/types.ts +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -12,11 +12,12 @@ const ApproveCalibrationSessionResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type ApproveCalibrationSessionResponse = z.infer< @@ -37,11 +38,12 @@ const GetCalibrationSessionByIdResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCalibrationSessionByIdResponse = z.infer< @@ -65,11 +67,12 @@ const GetCalibrationSessionsResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCalibrationSessionsResponse = z.infer< @@ -86,11 +89,12 @@ const GetOdataMetadataCalibSessionServiceResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetOdataMetadataCalibSessionServiceResponse = z.infer< @@ -111,11 +115,12 @@ const GetCalibrationSubjectByIdResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCalibrationSubjectByIdResponse = z.infer< @@ -140,11 +145,12 @@ const GetCalibrationSubjectRatingsResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCalibrationSubjectRatingsResponse = z.infer< @@ -164,11 +170,12 @@ const UpdateCalibrationSubjectRatingsResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type UpdateCalibrationSubjectRatingsResponse = z.infer< @@ -185,11 +192,12 @@ const CreateOnboardeeResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type CreateOnboardeeResponse = z.infer< @@ -211,11 +219,12 @@ const GetOnb2ProcessResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetOnb2ProcessResponse = z.infer< @@ -232,11 +241,12 @@ const GetOdataMetadataOnboardingAddlResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetOdataMetadataOnboardingAddlResponse = z.infer< @@ -256,11 +266,12 @@ const UpdateInternalUsernameNewHiresAfterResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type UpdateInternalUsernameNewHiresAfterResponse = z.infer< @@ -279,11 +290,12 @@ const CreateAFeedbackRequestResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type CreateAFeedbackRequestResponse = z.infer< @@ -307,11 +319,12 @@ const GetFeedbackRecordsServiceAvailableResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFeedbackRecordsServiceAvailableResponse = z.infer< @@ -335,11 +348,12 @@ const GetPendingFeedbackRequestsFeedbackResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetPendingFeedbackRequestsFeedbackResponse = z.infer< @@ -358,11 +372,12 @@ const GiveFeedbackOrRespondToAFeedbackRequestResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GiveFeedbackOrRespondToAFeedbackRequestResponse = z.infer< @@ -379,11 +394,12 @@ const RefreshMetadataContFeedbackServiceResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type RefreshMetadataContFeedbackServiceResponse = z.infer< @@ -402,11 +418,12 @@ const CreateUpdateSuccessorNominationResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type CreateUpdateSuccessorNominationResponse = z.infer< @@ -425,11 +442,12 @@ const DeleteNominationPositionTalentPoolResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type DeleteNominationPositionTalentPoolResponse = z.infer< @@ -446,11 +464,12 @@ const GetOdataMetadataForNominationServiceResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetOdataMetadataForNominationServiceResponse = z.infer< @@ -472,11 +491,12 @@ const GetTalentPoolResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetTalentPoolResponse = z.infer; @@ -498,11 +518,12 @@ const GetApplicationInterviewResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetApplicationInterviewResponse = z.infer< @@ -526,11 +547,12 @@ const GetInterviewOverallAssessmentResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetInterviewOverallAssessmentResponse = z.infer< @@ -554,11 +576,12 @@ const GetJobApplicationResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetJobApplicationResponse = z.infer< @@ -582,11 +605,12 @@ const GetJobRequisitionResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetJobRequisitionResponse = z.infer< @@ -610,11 +634,12 @@ const GetJobReqScreeningQuestionResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetJobReqScreeningQuestionResponse = z.infer< @@ -636,11 +661,12 @@ const ListCandidatesResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type ListCandidatesResponse = z.infer< @@ -664,11 +690,12 @@ const GetFoBusinessUnitResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoBusinessUnitResponse = z.infer< @@ -690,11 +717,12 @@ const GetFoCompanyResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoCompanyResponse = z.infer; @@ -714,11 +742,12 @@ const GetFoCostCenterResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoCostCenterResponse = z.infer< @@ -740,11 +769,12 @@ const GetFoDepartmentResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoDepartmentResponse = z.infer< @@ -766,11 +796,12 @@ const GetFoJobCodeResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoJobCodeResponse = z.infer; @@ -790,11 +821,12 @@ const GetFoJobFunctionResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoJobFunctionResponse = z.infer< @@ -816,11 +848,12 @@ const GetFoLocationResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoLocationResponse = z.infer; @@ -840,11 +873,12 @@ const GetFoPayGroupResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFoPayGroupResponse = z.infer; @@ -864,11 +898,12 @@ const GetPositionResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetPositionResponse = z.infer; @@ -891,11 +926,12 @@ const GetCustomMdfObjectResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCustomMdfObjectResponse = z.infer< @@ -917,11 +953,12 @@ const GetPicklistResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetPicklistResponse = z.infer; @@ -943,11 +980,12 @@ const GetPicklistOptionResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetPicklistOptionResponse = z.infer< @@ -965,11 +1003,12 @@ const GetCurrentUserResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCurrentUserResponse = z.infer< @@ -986,11 +1025,12 @@ const GetOdataUserMetadataResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetOdataUserMetadataResponse = z.infer< @@ -1012,11 +1052,12 @@ const ListUsersResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type ListUsersResponse = z.infer; @@ -1033,11 +1074,12 @@ const GetPerPersonByIdResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetPerPersonByIdResponse = z.infer< @@ -1059,11 +1101,12 @@ const ListPerPersonResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type ListPerPersonResponse = z.infer; @@ -1083,11 +1126,12 @@ const GetPerPersonalResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetPerPersonalResponse = z.infer< @@ -1111,11 +1155,12 @@ const GetBackgroundEducationResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetBackgroundEducationResponse = z.infer< @@ -1139,11 +1184,12 @@ const GetBackgroundMobilityResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetBackgroundMobilityResponse = z.infer< @@ -1167,11 +1213,12 @@ const ListEmpEmploymentResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type ListEmpEmploymentResponse = z.infer< @@ -1195,11 +1242,12 @@ const GetEmpEmploymentTerminationResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetEmpEmploymentTerminationResponse = z.infer< @@ -1221,11 +1269,12 @@ const GetWorkOrderResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetWorkOrderResponse = z.infer; @@ -1247,11 +1296,12 @@ const GetEmpPayCompRecurringResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetEmpPayCompRecurringResponse = z.infer< @@ -1275,11 +1325,12 @@ const GetEmpPayCompNonRecurringResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetEmpPayCompNonRecurringResponse = z.infer< @@ -1303,11 +1354,12 @@ const GetGoalPlanTemplateResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetGoalPlanTemplateResponse = z.infer< @@ -1330,11 +1382,12 @@ const GetGoalsByPlanResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetGoalsByPlanResponse = z.infer< @@ -1356,11 +1409,12 @@ const GetFormContentResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetFormContentResponse = z.infer< @@ -1379,11 +1433,12 @@ const CreateLearningActivitiesBulkResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type CreateLearningActivitiesBulkResponse = z.infer< @@ -1400,11 +1455,12 @@ const GetCdpLearningMetadataResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetCdpLearningMetadataResponse = z.infer< @@ -1421,11 +1477,12 @@ const RefreshCdpLearningMetadataResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type RefreshCdpLearningMetadataResponse = z.infer< @@ -1447,11 +1504,12 @@ const GetEmployeeTimeResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetEmployeeTimeResponse = z.infer< @@ -1475,11 +1533,12 @@ const GetEmployeeTimesheetResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetEmployeeTimesheetResponse = z.infer< @@ -1503,11 +1562,12 @@ const GetTemporaryTimeInformationResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetTemporaryTimeInformationResponse = z.infer< @@ -1531,11 +1591,12 @@ const GetTimeAccountSnapshotResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetTimeAccountSnapshotResponse = z.infer< @@ -1552,11 +1613,12 @@ const GetOdataMetadataClockInclockOutResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type GetOdataMetadataClockInclockOutResponse = z.infer< @@ -1580,11 +1642,12 @@ const QueryAllAvailableClockClockOutResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type QueryAllAvailableClockClockOutResponse = z.infer< @@ -1605,11 +1668,12 @@ const QueryClockClockOutGroupCodeTimeResponseSchema = z .object({ d: z .object({ - results: z.array(z.unknown()), - id: z.string(), - status: z.string(), + results: z.array(z.unknown()).optional(), + id: z.string().optional(), + status: z.string().optional(), }) - .catchall(z.unknown()), + .catchall(z.unknown()) + .optional(), }) .passthrough(); export type QueryClockClockOutGroupCodeTimeResponse = z.infer< diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts index 1276513a3..dcdb2395d 100644 --- a/packages/sapsuccessfactors/index.ts +++ b/packages/sapsuccessfactors/index.ts @@ -1,5 +1,4 @@ import type { - AuthTypes, BindEndpoints, BindWebhooks, CorsairEndpoint, @@ -7,12 +6,9 @@ import type { CorsairPlugin, CorsairPluginContext, KeyBuilderContext, - PickAuth, - PluginAuthConfig, PluginPermissionsConfig, RequiredPluginEndpointMeta, } from 'corsair/core'; -import { AuthMissingError } from 'corsair/core'; import { A, Application, @@ -64,10 +60,8 @@ import { SapsuccessfactorsSchema } from './schema'; export type SapsuccessfactorsPluginOptions = { /** Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. */ - authType?: PickAuth<'api_key'>; key?: string; apiBaseUrl?: string; - webhookSecret?: string; hooks?: InternalSapsuccessfactorsPlugin['hooks']; webhookHooks?: InternalSapsuccessfactorsPlugin['webhookHooks']; errorHandlers?: CorsairErrorHandler; @@ -605,8 +599,6 @@ export const sapsuccessfactorsEndpointSchemas = { }, } as const; -const defaultAuthType: AuthTypes = 'api_key' as const; - const sapsuccessfactorsEndpointMeta = { 'approve.approveCalibrationSession': { riskLevel: 'write', @@ -867,12 +859,6 @@ const sapsuccessfactorsEndpointMeta = { }, } satisfies RequiredPluginEndpointMeta; -export const sapsuccessfactorsAuthConfig = { - api_key: { - account: ['tenant_external_id'] as const, - }, -} as const satisfies PluginAuthConfig; - export type BaseSapsuccessfactorsPlugin< T extends SapsuccessfactorsPluginOptions, > = CorsairPlugin< @@ -880,8 +866,7 @@ export type BaseSapsuccessfactorsPlugin< typeof SapsuccessfactorsSchema, typeof sapsuccessfactorsEndpointsNested, typeof sapsuccessfactorsWebhooksNested, - T, - typeof defaultAuthType + T >; export type InternalSapsuccessfactorsPlugin = @@ -896,13 +881,9 @@ export function sapsuccessfactors< incomingOptions: SapsuccessfactorsPluginOptions & T = {} as SapsuccessfactorsPluginOptions & T, ): ExternalSapsuccessfactorsPlugin { - const options = { - ...incomingOptions, - authType: incomingOptions.authType ?? defaultAuthType, - }; + const options = { ...incomingOptions }; return { id: 'sapsuccessfactors', - authConfig: sapsuccessfactorsAuthConfig, schema: SapsuccessfactorsSchema, options, hooks: options.hooks, @@ -912,13 +893,9 @@ export function sapsuccessfactors< endpointMeta: sapsuccessfactorsEndpointMeta, endpointSchemas: sapsuccessfactorsEndpointSchemas, pluginWebhookMatcher: () => false, - keyBuilder: async (ctx: SapsuccessfactorsKeyBuilderContext, source) => { + keyBuilder: async (_ctx: SapsuccessfactorsKeyBuilderContext, source) => { if (source === 'endpoint' && options.key) return options.key; - if (source === 'endpoint' && ctx.authType === 'api_key') { - const res = await ctx.keys.get_api_key(); - return res ?? ''; - } - throw new AuthMissingError('sapsuccessfactors', 'api_key'); + throw new Error('SAP SuccessFactors API key is required'); }, } satisfies InternalSapsuccessfactorsPlugin; } From 3f3fc70b0f5a504359550c0806b701d9843a8006 Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 02:48:07 +0530 Subject: [PATCH 08/18] feat(sapsuccessfactors): robust sandbox and production url normalization --- packages/sapsuccessfactors/api.test.ts | 48 ++++++++++++++++++++++++++ packages/sapsuccessfactors/client.ts | 7 +++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index de67aaec7..97f034452 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -70,6 +70,54 @@ describe('SapSuccessfactors Plugin', () => { ); }); + it('normalizes sandbox and production base URLs properly', async () => { + await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { + apiBaseUrl: 'https://sandbox.api.sap.com/odata/v2', + }); + expect(mockedRequest).toHaveBeenLastCalledWith( + expect.objectContaining({ + BASE: 'https://sandbox.api.sap.com', + TOKEN: undefined, + HEADERS: expect.objectContaining({ + APIKey: 'test-key', + }), + }), + expect.objectContaining({ + url: '/odata/v2/User', + }), + expect.anything(), + ); + + await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { + apiBaseUrl: + 'https://sandbox.api.sap.com/successfactorsfoundation/odata/v2', + }); + expect(mockedRequest).toHaveBeenLastCalledWith( + expect.objectContaining({ + BASE: 'https://sandbox.api.sap.com/successfactorsfoundation', + TOKEN: undefined, + }), + expect.objectContaining({ + url: '/odata/v2/User', + }), + expect.anything(), + ); + + await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { + apiBaseUrl: 'api10.successfactors.com/odata/v2', + }); + expect(mockedRequest).toHaveBeenLastCalledWith( + expect.objectContaining({ + BASE: 'https://api10.successfactors.com', + TOKEN: 'test-key', + }), + expect.objectContaining({ + url: '/odata/v2/User', + }), + expect.anything(), + ); + }); + it('calls approve.approveCalibrationSession endpoint correctly', async () => { const endpoint = (plugin.endpoints as any)?.approve ?.approveCalibrationSession; diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts index bb71c9219..142c2ceaf 100644 --- a/packages/sapsuccessfactors/client.ts +++ b/packages/sapsuccessfactors/client.ts @@ -45,7 +45,12 @@ export async function makeSapsuccessfactorsRequest( query, } = options; - let base = apiBaseUrl.replace(/\/+$/, ''); + let base = (apiBaseUrl || SAP_SUCCESSFACTORS_DEFAULT_API_BASE) + .trim() + .replace(/\/+$/, ''); + if (!base.startsWith('http://') && !base.startsWith('https://')) { + base = `https://${base}`; + } const url = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; if (base.endsWith('/odata/v2') && url.startsWith('/odata/v2')) { base = base.slice(0, -'/odata/v2'.length); From 2ad4f904061a3e1a85b9b66dc5762e5612ba1edb Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 02:59:46 +0530 Subject: [PATCH 09/18] fix(sapsuccessfactors): strip whitespace and newlines from apiBaseUrl --- packages/sapsuccessfactors/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts index 142c2ceaf..685e6d27d 100644 --- a/packages/sapsuccessfactors/client.ts +++ b/packages/sapsuccessfactors/client.ts @@ -46,7 +46,7 @@ export async function makeSapsuccessfactorsRequest( } = options; let base = (apiBaseUrl || SAP_SUCCESSFACTORS_DEFAULT_API_BASE) - .trim() + .replace(/\s+/g, '') .replace(/\/+$/, ''); if (!base.startsWith('http://') && !base.startsWith('https://')) { base = `https://${base}`; From 64fbc3696d4c12bef7288070e2001fecda768fb2 Mon Sep 17 00:00:00 2001 From: Aral-549 Date: Thu, 27 Aug 2026 07:22:42 +0530 Subject: [PATCH 10/18] fix(sapsuccessfactors): resolve CodeQL incomplete URL sanitization --- packages/sapsuccessfactors/client.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts index 685e6d27d..00d020655 100644 --- a/packages/sapsuccessfactors/client.ts +++ b/packages/sapsuccessfactors/client.ts @@ -28,6 +28,15 @@ const SAP_SUCCESSFACTORS_RATE_LIMIT_CONFIG: RateLimitConfig = { }, }; +function isSapSandboxHost(urlStr: string): boolean { + try { + const parsed = new URL(urlStr); + return parsed.hostname === 'sandbox.api.sap.com'; + } catch { + return false; + } +} + export async function makeSapsuccessfactorsRequest( endpoint: string, apiKey: string, @@ -56,7 +65,7 @@ export async function makeSapsuccessfactorsRequest( base = base.slice(0, -'/odata/v2'.length); } - const isSandbox = base.includes('sandbox.api.sap.com'); + const isSandbox = isSapSandboxHost(base); const config: OpenAPIConfig = { BASE: base, From 6f856974da9071ecba834ed12c999f2cceec6b5f Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:12:47 +0530 Subject: [PATCH 11/18] fix(sapsuccessfactors): enforce schemas, auth, and official OData paths --- packages/corsair/core/constants.ts | 2 +- packages/sapsuccessfactors/api.test.ts | 807 ++----- packages/sapsuccessfactors/client.ts | 126 +- packages/sapsuccessfactors/endpoints/a.ts | 41 - .../endpoints/application.ts | 42 - .../sapsuccessfactors/endpoints/approve.ts | 38 - .../sapsuccessfactors/endpoints/background.ts | 76 - .../endpoints/calibration.ts | 185 -- .../sapsuccessfactors/endpoints/candidates.ts | 34 - packages/sapsuccessfactors/endpoints/cdp.ts | 64 - .../sapsuccessfactors/endpoints/current.ts | 34 - .../sapsuccessfactors/endpoints/custom.ts | 47 - packages/sapsuccessfactors/endpoints/emp.ts | 138 -- .../sapsuccessfactors/endpoints/employee.ts | 68 - .../sapsuccessfactors/endpoints/factory.ts | 205 ++ .../sapsuccessfactors/endpoints/feedback.ts | 42 - packages/sapsuccessfactors/endpoints/fo.ts | 222 -- packages/sapsuccessfactors/endpoints/form.ts | 34 - packages/sapsuccessfactors/endpoints/give.ts | 41 - packages/sapsuccessfactors/endpoints/goal.ts | 42 - packages/sapsuccessfactors/endpoints/goals.ts | 41 - packages/sapsuccessfactors/endpoints/index.ts | 324 ++- .../sapsuccessfactors/endpoints/internal.ts | 38 - .../sapsuccessfactors/endpoints/interview.ts | 42 - packages/sapsuccessfactors/endpoints/job.ts | 98 - .../sapsuccessfactors/endpoints/learning.ts | 41 - .../sapsuccessfactors/endpoints/metadata.ts | 38 - .../sapsuccessfactors/endpoints/nomination.ts | 40 - packages/sapsuccessfactors/endpoints/odata.ts | 144 -- packages/sapsuccessfactors/endpoints/onb2.ts | 34 - .../sapsuccessfactors/endpoints/onboardee.ts | 37 - .../sapsuccessfactors/endpoints/pending.ts | 42 - packages/sapsuccessfactors/endpoints/per.ts | 92 - .../sapsuccessfactors/endpoints/picklist.ts | 64 - .../sapsuccessfactors/endpoints/position.ts | 36 - packages/sapsuccessfactors/endpoints/query.ts | 76 - .../sapsuccessfactors/endpoints/routes.ts | 553 +++++ .../sapsuccessfactors/endpoints/successor.ts | 41 - .../sapsuccessfactors/endpoints/talent.ts | 34 - .../sapsuccessfactors/endpoints/temporary.ts | 42 - packages/sapsuccessfactors/endpoints/time.ts | 42 - packages/sapsuccessfactors/endpoints/types.ts | 1985 ++--------------- packages/sapsuccessfactors/endpoints/users.ts | 37 - packages/sapsuccessfactors/endpoints/work.ts | 36 - packages/sapsuccessfactors/index.ts | 928 +------- packages/sapsuccessfactors/jest.config.cjs | 33 +- packages/sapsuccessfactors/package.json | 2 +- packages/sapsuccessfactors/schema.test.ts | 99 +- packages/sapsuccessfactors/schema/database.ts | 200 +- packages/sapsuccessfactors/schema/index.ts | 2 + packages/sapsuccessfactors/tsconfig.test.json | 10 + .../webhooks/tenant-matcher.ts | 8 - 52 files changed, 1665 insertions(+), 5862 deletions(-) delete mode 100644 packages/sapsuccessfactors/endpoints/a.ts delete mode 100644 packages/sapsuccessfactors/endpoints/application.ts delete mode 100644 packages/sapsuccessfactors/endpoints/approve.ts delete mode 100644 packages/sapsuccessfactors/endpoints/background.ts delete mode 100644 packages/sapsuccessfactors/endpoints/calibration.ts delete mode 100644 packages/sapsuccessfactors/endpoints/candidates.ts delete mode 100644 packages/sapsuccessfactors/endpoints/cdp.ts delete mode 100644 packages/sapsuccessfactors/endpoints/current.ts delete mode 100644 packages/sapsuccessfactors/endpoints/custom.ts delete mode 100644 packages/sapsuccessfactors/endpoints/emp.ts delete mode 100644 packages/sapsuccessfactors/endpoints/employee.ts create mode 100644 packages/sapsuccessfactors/endpoints/factory.ts delete mode 100644 packages/sapsuccessfactors/endpoints/feedback.ts delete mode 100644 packages/sapsuccessfactors/endpoints/fo.ts delete mode 100644 packages/sapsuccessfactors/endpoints/form.ts delete mode 100644 packages/sapsuccessfactors/endpoints/give.ts delete mode 100644 packages/sapsuccessfactors/endpoints/goal.ts delete mode 100644 packages/sapsuccessfactors/endpoints/goals.ts delete mode 100644 packages/sapsuccessfactors/endpoints/internal.ts delete mode 100644 packages/sapsuccessfactors/endpoints/interview.ts delete mode 100644 packages/sapsuccessfactors/endpoints/job.ts delete mode 100644 packages/sapsuccessfactors/endpoints/learning.ts delete mode 100644 packages/sapsuccessfactors/endpoints/metadata.ts delete mode 100644 packages/sapsuccessfactors/endpoints/nomination.ts delete mode 100644 packages/sapsuccessfactors/endpoints/odata.ts delete mode 100644 packages/sapsuccessfactors/endpoints/onb2.ts delete mode 100644 packages/sapsuccessfactors/endpoints/onboardee.ts delete mode 100644 packages/sapsuccessfactors/endpoints/pending.ts delete mode 100644 packages/sapsuccessfactors/endpoints/per.ts delete mode 100644 packages/sapsuccessfactors/endpoints/picklist.ts delete mode 100644 packages/sapsuccessfactors/endpoints/position.ts delete mode 100644 packages/sapsuccessfactors/endpoints/query.ts create mode 100644 packages/sapsuccessfactors/endpoints/routes.ts delete mode 100644 packages/sapsuccessfactors/endpoints/successor.ts delete mode 100644 packages/sapsuccessfactors/endpoints/talent.ts delete mode 100644 packages/sapsuccessfactors/endpoints/temporary.ts delete mode 100644 packages/sapsuccessfactors/endpoints/time.ts delete mode 100644 packages/sapsuccessfactors/endpoints/users.ts delete mode 100644 packages/sapsuccessfactors/endpoints/work.ts create mode 100644 packages/sapsuccessfactors/tsconfig.test.json delete mode 100644 packages/sapsuccessfactors/webhooks/tenant-matcher.ts diff --git a/packages/corsair/core/constants.ts b/packages/corsair/core/constants.ts index fc72a43ff..a7a992c57 100644 --- a/packages/corsair/core/constants.ts +++ b/packages/corsair/core/constants.ts @@ -362,7 +362,7 @@ export const ProviderDisplayNames = { resend: 'Resend', retailed: 'Retailed', salesforce: 'Salesforce', - sapsuccessfactors: 'SapSuccessfactors', + sapsuccessfactors: 'SAP SuccessFactors', securitytrails: 'SecurityTrails', sentry: 'Sentry', serpapi: 'Serpapi', diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 97f034452..3ded273a2 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -1,11 +1,15 @@ import { request } from 'corsair/http'; import { makeSapsuccessfactorsRequest } from './client'; +import { executeSapOperation } from './endpoints/factory'; +import { getSapRoute, sapRoutes } from './endpoints/routes'; +import { SapsuccessfactorsEndpointInputSchemas } from './endpoints/types'; import { errorHandlers } from './error-handlers'; +import type { SapsuccessfactorsContext } from './index'; import { sapsuccessfactors } from './index'; jest.mock('corsair/http', () => ({ request: jest.fn().mockResolvedValue({ - d: { results: [{ id: 'test-123' }], id: 'test-123', status: 'OK' }, + d: { results: [{ userId: 'cgrant' }] }, }), ApiError: class ApiError extends Error { constructor( @@ -19,648 +23,237 @@ jest.mock('corsair/http', () => ({ }, })); -const mockedRequest = request as any; +const mockedRequest = request as jest.MockedFunction; -describe('SapSuccessfactors Plugin', () => { - const plugin = sapsuccessfactors({ - key: 'test-api-token', - apiBaseUrl: 'https://api10.successfactors.com', - }); - const mockCtx = { - key: 'test-api-token', - options: { apiBaseUrl: 'https://api10.successfactors.com' }, - database: undefined, - $getAccountId: async () => 'acc_test_123', - log: jest.fn(), - } as any; +const plugin = sapsuccessfactors({ + authType: 'api_key', + key: 'test-token', + host: 'api10.successfactors.com', + companyId: 'ACME', +}); +const mockCtx = { + key: 'test-token', + options: { host: 'api10.successfactors.com' }, + $getAccountId: async () => 'acc_test', + log: jest.fn(), +} as unknown as SapsuccessfactorsContext; + +function run(name: Parameters[0], input: unknown) { + return executeSapOperation(mockCtx, input as never, getSapRoute(name)); +} + +function lastCall() { + expect(mockedRequest).toHaveBeenCalled(); + const [, opts] = mockedRequest.mock.calls.at(-1) ?? []; + return opts as { + method?: string; + url?: string; + query?: Record; + }; +} + +const fixtures: Record> = { + approveCalibrationSession: { session_id: 's1' }, + getCalibrationSessionById: { session_id: 's1' }, + getCalibrationSessions: { top: 10 }, + getOdataMetadataCalibSessionService: {}, + getCalibrationSubjectById: { subject_id: 'sub1' }, + getCalibrationSubjectRatings: { session_id: 's1' }, + updateCalibrationSubjectRatings: { subject_id: 'sub1', body: { rating: 3 } }, + createOnboardee: { userId: 'nhire1', username: 'nhire1' }, + getOnb2Process: { top: 5 }, + getOdataMetadataOnboardingAddl: {}, + updateInternalUsernameNewHiresAfter: { + userId: 'nhire1', + newUsername: 'nhire1.int', + }, + createAFeedbackRequest: { + questions: [{ question: 'What should they start doing?' }], + }, + getFeedbackRecordsServiceAvailable: { top: 5 }, + getPendingFeedbackRequestsFeedback: { top: 5 }, + giveFeedbackOrRespondToAFeedbackRequest: { + questions: [{ question: 'Strengths', answer: 'Clear communicator' }], + }, + refreshMetadataContFeedbackService: {}, + createUpdateSuccessorNomination: { userId: 'cgrant', positionCode: 'POS-1' }, + deleteNominationPositionTalentPool: { + nominationTargetId: 'nt-1', + userId: 'cgrant', + isPoolNomination: true, + }, + getOdataMetadataForNominationService: {}, + getTalentPool: { top: 5 }, + getApplicationInterview: { applicationId: '1001' }, + getInterviewOverallAssessment: { top: 5 }, + getJobApplication: { top: 5 }, + getJobRequisition: { top: 5 }, + getJobReqScreeningQuestion: { top: 5 }, + listCandidates: { top: 5 }, + getFoBusinessUnit: { top: 5 }, + getFoCompany: { top: 5 }, + getFoCostCenter: { top: 5 }, + getFoDepartment: { top: 5 }, + getFoJobCode: { top: 5 }, + getFoJobFunction: { top: 5 }, + getFoLocation: { top: 5 }, + getFoPayGroup: { top: 5 }, + getPosition: { top: 5 }, + getCustomMdfObject: { custom_object: 'cust_TeamGoal' }, + getPicklist: { top: 5 }, + getPicklistOption: { top: 5 }, + getCurrentUser: {}, + getOdataUserMetadata: {}, + listUsers: { top: 10, filter: "status eq 't'" }, + getPerPersonById: { person_id_external: 'p1' }, + listPerPerson: { top: 5 }, + getPerPersonal: { top: 5 }, + getBackgroundEducation: { top: 5 }, + getBackgroundMobility: { top: 5 }, + listEmpEmployment: { top: 5 }, + getEmpEmploymentTermination: { top: 5 }, + getWorkOrder: { top: 5 }, + getEmpPayCompRecurring: { top: 5 }, + getEmpPayCompNonRecurring: { top: 5 }, + getGoalPlanTemplate: { top: 5 }, + getGoalsByPlan: { goal_plan_id: '11' }, + getFormContent: { top: 5 }, + createLearningActivitiesBulk: { body: { activities: [] } }, + getCdpLearningMetadata: {}, + refreshCdpLearningMetadata: {}, + getEmployeeTime: { top: 5 }, + getEmployeeTimesheet: { top: 5 }, + getTemporaryTimeInformation: { top: 5 }, + getTimeAccountSnapshot: { top: 5 }, + getOdataMetadataClockInclockOut: {}, + queryAllAvailableClockClockOut: { top: 5 }, + queryClockClockOutGroupCodeTime: { code: 'CICO1' }, +}; + +describe('SAP SuccessFactors plugin', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('initializes plugin with correct id and configuration', () => { + it('registers oauth_2 and api_key auth', () => { expect(plugin.id).toBe('sapsuccessfactors'); - expect(plugin.authConfig).toBeUndefined(); - expect(plugin.endpoints).toBeDefined(); - expect(plugin.schema).toBeDefined(); - }); - - it('handles rate limit error matching in errorHandlers', async () => { - const handler = errorHandlers.RATE_LIMIT_ERROR; - expect(handler.match(new Error('Rate limit 429'))).toBe(true); - const res = await handler.handler(new Error('429')); - expect(res.maxRetries).toBe(3); + expect(plugin.authConfig).toEqual( + expect.objectContaining({ + oauth_2: expect.anything(), + api_key: expect.anything(), + }), + ); + expect(plugin.oauthConfig?.tokenUrl).toBe( + 'https://api10.successfactors.com/oauth/token', + ); + expect(plugin.errorHandlers?.RATE_LIMIT_ERROR).toBeDefined(); }); - it('translates query parameters into OData v2 format ($top, $filter)', async () => { - await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { + it('maps OData query keys and sends query on GET', async () => { + await makeSapsuccessfactorsRequest('odata/v2/User', 'k', { method: 'GET', - query: { top: 10, filter: "status eq 'ACTIVE'" }, + query: { top: 10, filter: "status eq 't'" }, + host: 'api10.successfactors.com', }); - expect(mockedRequest).toHaveBeenCalledWith( - expect.anything(), + expect(lastCall().query).toEqual( expect.objectContaining({ - query: expect.objectContaining({ - $format: 'json', - $top: 10, - $filter: "status eq 'ACTIVE'", - }), + $format: 'json', + $top: 10, + $filter: "status eq 't'", }), - expect.anything(), ); }); - it('normalizes sandbox and production base URLs properly', async () => { - await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { - apiBaseUrl: 'https://sandbox.api.sap.com/odata/v2', + it('uses APIKey header on SAP API Business Hub sandbox', async () => { + await makeSapsuccessfactorsRequest('odata/v2/User', 'hub-key', { + host: 'sandbox.api.sap.com', }); - expect(mockedRequest).toHaveBeenLastCalledWith( + const [config, opts] = mockedRequest.mock.calls.at(-1) ?? []; + expect(config).toEqual( expect.objectContaining({ BASE: 'https://sandbox.api.sap.com', - TOKEN: undefined, - HEADERS: expect.objectContaining({ - APIKey: 'test-key', - }), + HEADERS: expect.objectContaining({ APIKey: 'hub-key' }), }), - expect.objectContaining({ - url: '/odata/v2/User', - }), - expect.anything(), ); + expect(opts).toEqual(expect.objectContaining({ url: '/odata/v2/User' })); + }); - await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { - apiBaseUrl: - 'https://sandbox.api.sap.com/successfactorsfoundation/odata/v2', + it('rejects User as a custom MDF entity', async () => { + await expect( + run('getCustomMdfObject', { custom_object: 'User' }), + ).rejects.toThrow(/cust_/); + expect(mockedRequest).not.toHaveBeenCalled(); + }); + + it('encodes cust_* MDF names into the OData path', async () => { + await run('getCustomMdfObject', { custom_object: 'cust_TeamGoal' }); + expect(lastCall().url).toBe('/odata/v2/cust_TeamGoal'); + }); + + it('deletes NominationTarget with userId and isPoolNomination', async () => { + await run('deleteNominationPositionTalentPool', { + nominationTargetId: 'nt-1', + userId: 'cgrant', + isPoolNomination: true, }); - expect(mockedRequest).toHaveBeenLastCalledWith( - expect.objectContaining({ - BASE: 'https://sandbox.api.sap.com/successfactorsfoundation', - TOKEN: undefined, - }), + expect(lastCall()).toEqual( expect.objectContaining({ - url: '/odata/v2/User', + method: 'DELETE', + url: "/odata/v4/NominationService.svc/NominationTarget('nt-1')", + query: expect.objectContaining({ + userId: 'cgrant', + isPoolNomination: true, + }), }), - expect.anything(), ); + }); - await makeSapsuccessfactorsRequest('odata/v2/User', 'test-key', { - apiBaseUrl: 'api10.successfactors.com/odata/v2', - }); - expect(mockedRequest).toHaveBeenLastCalledWith( + it('requires applicationId for Interview Central', async () => { + await expect(run('getApplicationInterview', {})).rejects.toThrow( + /applicationId/, + ); + await run('getApplicationInterview', { applicationId: '1001' }); + expect(lastCall().query).toEqual( expect.objectContaining({ - BASE: 'https://api10.successfactors.com', - TOKEN: 'test-key', + $filter: "applicationId eq '1001'", }), - expect.objectContaining({ - url: '/odata/v2/User', - }), - expect.anything(), ); }); - it('calls approve.approveCalibrationSession endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.approve - ?.approveCalibrationSession; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { session_id: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls calibration.getCalibrationSessionById endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.calibration - ?.getCalibrationSessionById; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { session_id: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls calibration.getCalibrationSessions endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.calibration - ?.getCalibrationSessions; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls calibration.getCalibrationSubjectById endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.calibration - ?.getCalibrationSubjectById; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { subject_id: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); + it('maps Goal_11 plan ids to the Goal_11 entity set', async () => { + await run('getGoalsByPlan', { goal_plan_id: 'Goal_11' }); + expect(lastCall().url).toBe('/odata/v2/Goal_11'); }); - it('calls calibration.getCalibrationSubjectRatings endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.calibration - ?.getCalibrationSubjectRatings; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { session_id: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls calibration.updateCalibrationSubjectRatings endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.calibration - ?.updateCalibrationSubjectRatings; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { - subject_id: 'test_value', - body: { test: 'data' }, - } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls odata.getOdataMetadataCalibSessionService endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.odata - ?.getOdataMetadataCalibSessionService; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls odata.getOdataMetadataOnboardingAddl endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.odata - ?.getOdataMetadataOnboardingAddl; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls odata.getOdataMetadataForNominationService endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.odata - ?.getOdataMetadataForNominationService; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls odata.getOdataUserMetadata endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.odata?.getOdataUserMetadata; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls odata.getOdataMetadataClockInclockOut endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.odata - ?.getOdataMetadataClockInclockOut; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls onboardee.createOnboardee endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.onboardee?.createOnboardee; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls onb2.getOnb2Process endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.onb2?.getOnb2Process; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls internal.updateInternalUsernameNewHiresAfter endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.internal - ?.updateInternalUsernameNewHiresAfter; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { - user_id: 'test_value', - new_username: 'test_value', - } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls a.createAFeedbackRequest endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.a?.createAFeedbackRequest; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls feedback.getFeedbackRecordsServiceAvailable endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.feedback - ?.getFeedbackRecordsServiceAvailable; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls pending.getPendingFeedbackRequestsFeedback endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.pending - ?.getPendingFeedbackRequestsFeedback; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls give.giveFeedbackOrRespondToAFeedbackRequest endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.give - ?.giveFeedbackOrRespondToAFeedbackRequest; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls metadata.refreshMetadataContFeedbackService endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.metadata - ?.refreshMetadataContFeedbackService; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls successor.createUpdateSuccessorNomination endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.successor - ?.createUpdateSuccessorNomination; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls nomination.deleteNominationPositionTalentPool endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.nomination - ?.deleteNominationPositionTalentPool; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { nomination_id: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls talent.getTalentPool endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.talent?.getTalentPool; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls application.getApplicationInterview endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.application - ?.getApplicationInterview; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls interview.getInterviewOverallAssessment endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.interview - ?.getInterviewOverallAssessment; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls job.getJobApplication endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.job?.getJobApplication; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls job.getJobRequisition endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.job?.getJobRequisition; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls job.getJobReqScreeningQuestion endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.job?.getJobReqScreeningQuestion; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls candidates.listCandidates endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.candidates?.listCandidates; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoBusinessUnit endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoBusinessUnit; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoCompany endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoCompany; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoCostCenter endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoCostCenter; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoDepartment endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoDepartment; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoJobCode endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoJobCode; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoJobFunction endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoJobFunction; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoLocation endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoLocation; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls fo.getFoPayGroup endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.fo?.getFoPayGroup; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls position.getPosition endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.position?.getPosition; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls custom.getCustomMdfObject endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.custom?.getCustomMdfObject; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { custom_object: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls picklist.getPicklist endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.picklist?.getPicklist; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls picklist.getPicklistOption endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.picklist?.getPicklistOption; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls current.getCurrentUser endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.current?.getCurrentUser; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls users.listUsers endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.users?.listUsers; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls per.getPerPersonById endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.per?.getPerPersonById; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { - person_id_external: 'test_value', - } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls per.listPerPerson endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.per?.listPerPerson; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls per.getPerPersonal endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.per?.getPerPersonal; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls background.getBackgroundEducation endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.background - ?.getBackgroundEducation; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls background.getBackgroundMobility endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.background - ?.getBackgroundMobility; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls emp.listEmpEmployment endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.emp?.listEmpEmployment; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls emp.getEmpEmploymentTermination endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.emp - ?.getEmpEmploymentTermination; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls emp.getEmpPayCompRecurring endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.emp?.getEmpPayCompRecurring; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls emp.getEmpPayCompNonRecurring endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.emp?.getEmpPayCompNonRecurring; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls work.getWorkOrder endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.work?.getWorkOrder; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls goal.getGoalPlanTemplate endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.goal?.getGoalPlanTemplate; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls goals.getGoalsByPlan endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.goals?.getGoalsByPlan; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { goal_plan_id: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls form.getFormContent endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.form?.getFormContent; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls learning.createLearningActivitiesBulk endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.learning - ?.createLearningActivitiesBulk; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { body: { test: 'data' } } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls cdp.getCdpLearningMetadata endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.cdp?.getCdpLearningMetadata; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls cdp.refreshCdpLearningMetadata endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.cdp?.refreshCdpLearningMetadata; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls employee.getEmployeeTime endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.employee?.getEmployeeTime; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls employee.getEmployeeTimesheet endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.employee?.getEmployeeTimesheet; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls temporary.getTemporaryTimeInformation endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.temporary - ?.getTemporaryTimeInformation; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); - - it('calls time.getTimeAccountSnapshot endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.time?.getTimeAccountSnapshot; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); + it('filters current user with $loggedInUser', async () => { + await run('getCurrentUser', {}); + expect(lastCall()).toEqual( + expect.objectContaining({ + method: 'GET', + url: '/odata/v2/User', + query: expect.objectContaining({ + $filter: "userId eq '$loggedInUser'", + }), + }), + ); }); - it('calls query.queryAllAvailableClockClockOut endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.query - ?.queryAllAvailableClockClockOut; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, {} as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); + it('matches rate-limit errors', async () => { + expect(errorHandlers.RATE_LIMIT_ERROR.match(new Error('429'))).toBe(true); + const res = await errorHandlers.RATE_LIMIT_ERROR.handler(new Error('429')); + expect(res.maxRetries).toBe(3); }); - it('calls query.queryClockClockOutGroupCodeTime endpoint correctly', async () => { - const endpoint = (plugin.endpoints as any)?.query - ?.queryClockClockOutGroupCodeTime; - expect(endpoint).toBeDefined(); - const res = await endpoint(mockCtx, { code: 'test_value' } as any); - expect(res).toBeDefined(); - expect(mockedRequest).toHaveBeenCalled(); - }); + it.each(sapRoutes.map((route) => [route.name, route.method] as const))( + '%s sends %s', + async (name, method) => { + const input = fixtures[name]; + expect(input).toBeDefined(); + SapsuccessfactorsEndpointInputSchemas[name].parse(input); + await run(name, input); + expect(lastCall().method).toBe(method); + expect(lastCall().url).toMatch(/^\//); + }, + ); }); diff --git a/packages/sapsuccessfactors/client.ts b/packages/sapsuccessfactors/client.ts index 00d020655..1167774bc 100644 --- a/packages/sapsuccessfactors/client.ts +++ b/packages/sapsuccessfactors/client.ts @@ -15,26 +15,73 @@ export class SapsuccessfactorsAPIError extends Error { } } -export const SAP_SUCCESSFACTORS_DEFAULT_API_BASE = - 'https://api10.successfactors.com'; +export const SAP_SUCCESSFACTORS_DEFAULT_HOST = 'api10.successfactors.com'; -const SAP_SUCCESSFACTORS_RATE_LIMIT_CONFIG: RateLimitConfig = { +const HOST_PATTERN = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:\d{1,5})?$/i; + +const RATE_LIMIT: RateLimitConfig = { enabled: true, maxRetries: 3, initialRetryDelay: 1000, backoffMultiplier: 2, - headerNames: { - retryAfter: 'Retry-After', - }, + headerNames: { retryAfter: 'Retry-After' }, }; -function isSapSandboxHost(urlStr: string): boolean { - try { - const parsed = new URL(urlStr); - return parsed.hostname === 'sandbox.api.sap.com'; - } catch { - return false; +export type SapsuccessfactorsConnection = { + host: string; + companyId?: string; +}; + +export function normalizeSapsuccessfactorsHost(host: string): string { + const trimmed = host.trim(); + if (!trimmed) throw new Error('[sapsuccessfactors] host is required'); + + let value = trimmed; + if (trimmed.includes('://')) { + let url: URL; + try { + url = new URL(trimmed); + } catch { + throw new Error('[sapsuccessfactors] host is not a valid URL'); + } + if (url.protocol !== 'https:') { + throw new Error('[sapsuccessfactors] host must use https'); + } + if (url.username || url.password) { + throw new Error('[sapsuccessfactors] host must not contain credentials'); + } + value = url.host; + } + + while (value.endsWith('/')) { + value = value.slice(0, -1); + } + if (value.includes('/')) { + value = value.split('/')[0] ?? value; } + if (!HOST_PATTERN.test(value)) { + throw new Error('[sapsuccessfactors] host must be a bare hostname'); + } + return value; +} + +export function sapSuccessfactorsOAuthUrls(host: string) { + const normalized = normalizeSapsuccessfactorsHost(host); + const base = `https://${normalized}/oauth`; + return { + authUrl: `${base}/authorize`, + tokenUrl: `${base}/token`, + }; +} + +function isSapSandboxHost(host: string): boolean { + return host === 'sandbox.api.sap.com'; +} + +function authorizationHeader(apiKey: string): string { + if (apiKey.startsWith('Basic ') || apiKey.startsWith('Bearer ')) + return apiKey; + return `Bearer ${apiKey}`; } export async function makeSapsuccessfactorsRequest( @@ -42,56 +89,35 @@ export async function makeSapsuccessfactorsRequest( apiKey: string, options: { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; - apiBaseUrl?: string; + host?: string; body?: Record; query?: Record; } = {}, ): Promise { - const { - method = 'GET', - apiBaseUrl = SAP_SUCCESSFACTORS_DEFAULT_API_BASE, - body, - query, - } = options; - - let base = (apiBaseUrl || SAP_SUCCESSFACTORS_DEFAULT_API_BASE) - .replace(/\s+/g, '') - .replace(/\/+$/, ''); - if (!base.startsWith('http://') && !base.startsWith('https://')) { - base = `https://${base}`; - } + const { method = 'GET', body, query } = options; + const host = normalizeSapsuccessfactorsHost( + options.host ?? SAP_SUCCESSFACTORS_DEFAULT_HOST, + ); + const sandbox = isSapSandboxHost(host); const url = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; - if (base.endsWith('/odata/v2') && url.startsWith('/odata/v2')) { - base = base.slice(0, -'/odata/v2'.length); - } - - const isSandbox = isSapSandboxHost(base); const config: OpenAPIConfig = { - BASE: base, + BASE: `https://${host}`, VERSION: '1.0.0', WITH_CREDENTIALS: false, CREDENTIALS: 'omit', - TOKEN: isSandbox ? undefined : apiKey, + TOKEN: sandbox ? undefined : apiKey, HEADERS: { 'Content-Type': 'application/json', Accept: 'application/json', - ...(isSandbox + ...(sandbox ? { APIKey: apiKey, apikey: apiKey } - : { - Authorization: - apiKey.startsWith('Basic ') || apiKey.startsWith('Bearer ') - ? apiKey - : `Bearer ${apiKey}`, - }), + : { Authorization: authorizationHeader(apiKey) }), }, }; - // Map query keys to standard OData v2 parameters const formattedQuery: Record = - { - $format: 'json', - }; + url.includes('$metadata') ? {} : { $format: 'json' }; if (query) { const odataKeys = new Set([ 'filter', @@ -102,27 +128,25 @@ export async function makeSapsuccessfactorsRequest( 'orderby', ]); for (const [k, v] of Object.entries(query)) { - if (v !== undefined) { - const targetKey = odataKeys.has(k) ? `$${k}` : k; - formattedQuery[targetKey] = v; - } + if (v === undefined) continue; + formattedQuery[odataKeys.has(k) ? `$${k}` : k] = v; } } const requestOptions: ApiRequestOptions = { method, - url: endpoint.startsWith('/') ? endpoint : `/${endpoint}`, + url, body: method === 'POST' || method === 'PUT' || method === 'PATCH' ? body : undefined, mediaType: 'application/json; charset=utf-8', - query: method === 'GET' ? formattedQuery : undefined, + query: Object.keys(formattedQuery).length > 0 ? formattedQuery : undefined, }; try { return await request(config, requestOptions, { - rateLimitConfig: SAP_SUCCESSFACTORS_RATE_LIMIT_CONFIG, + rateLimitConfig: RATE_LIMIT, }); } catch (error) { if (error instanceof ApiError) throw error; diff --git a/packages/sapsuccessfactors/endpoints/a.ts b/packages/sapsuccessfactors/endpoints/a.ts deleted file mode 100644 index f8da5247c..000000000 --- a/packages/sapsuccessfactors/endpoints/a.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Create a Feedback Request -// Request performance feedback from one employee about another. -export const createAFeedbackRequest: SapsuccessfactorsEndpoints['createAFeedbackRequest'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.createAFeedbackRequest.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { body, ...rest } = (validatedInput ?? {}) as { - body?: Record; - }; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['createAFeedbackRequest'] - >('odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', ctx.key, { - method: 'POST', - body: (body ?? rest) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.createAFeedbackRequest.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.a.createAFeedbackRequest', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/application.ts b/packages/sapsuccessfactors/endpoints/application.ts deleted file mode 100644 index 99eaf8a9c..000000000 --- a/packages/sapsuccessfactors/endpoints/application.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Application Interview -// Retrieve interview info from Interview Central (first 1000 records; filter by applicationId). -export const getApplicationInterview: SapsuccessfactorsEndpoints['getApplicationInterview'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getApplicationInterview.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getApplicationInterview'] - >('odata/v2/ApplicationInterview', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getApplicationInterview.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.application.getApplicationInterview', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/approve.ts b/packages/sapsuccessfactors/endpoints/approve.ts deleted file mode 100644 index abe38d0ea..000000000 --- a/packages/sapsuccessfactors/endpoints/approve.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Approve Calibration Session -// Finalize a calibration session that is In Progress or Approving. -export const approveCalibrationSession: SapsuccessfactorsEndpoints['approveCalibrationSession'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['approveCalibrationSession'] - >('odata/v4/CalSession.svc/Approve', ctx.key, { - method: 'POST', - body: (validatedInput ?? {}) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.approveCalibrationSession.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.approve.approveCalibrationSession', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/background.ts b/packages/sapsuccessfactors/endpoints/background.ts deleted file mode 100644 index f42431989..000000000 --- a/packages/sapsuccessfactors/endpoints/background.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Background Education -// Retrieve background education records (key: backgroundElementId). -export const getBackgroundEducation: SapsuccessfactorsEndpoints['getBackgroundEducation'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getBackgroundEducation.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getBackgroundEducation'] - >('odata/v2/BackgroundEducation', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getBackgroundEducation.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.background.getBackgroundEducation', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Background Mobility -// Retrieve relocation willingness / geographic mobility preferences. -export const getBackgroundMobility: SapsuccessfactorsEndpoints['getBackgroundMobility'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getBackgroundMobility.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getBackgroundMobility'] - >('odata/v2/BackgroundMobility', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getBackgroundMobility.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.background.getBackgroundMobility', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/calibration.ts b/packages/sapsuccessfactors/endpoints/calibration.ts deleted file mode 100644 index 4c53ad3b9..000000000 --- a/packages/sapsuccessfactors/endpoints/calibration.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Calibration Session By ID -// Get a specific calibration session by session ID. -export const getCalibrationSessionById: SapsuccessfactorsEndpoints['getCalibrationSessionById'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCalibrationSessionById.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { session_id, ...query } = (validatedInput ?? {}) as { - session_id?: string; - }; - const resourcePath = session_id - ? `odata/v4/CalSession.svc/CalibrationSession('${session_id}')` - : 'odata/v4/CalSession.svc/CalibrationSession'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCalibrationSessionById'] - >(resourcePath, ctx.key, { - method: 'GET', - query: query as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessionById.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.calibration.getCalibrationSessionById', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Calibration Sessions -// Query all calibration sessions the current user can access. -export const getCalibrationSessions: SapsuccessfactorsEndpoints['getCalibrationSessions'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCalibrationSessions.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCalibrationSessions'] - >('odata/v4/CalSession.svc/CalibrationSession', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessions.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.calibration.getCalibrationSessions', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Calibration Subject By ID -// Query a subject's competency ratings within a calibration session. -export const getCalibrationSubjectById: SapsuccessfactorsEndpoints['getCalibrationSubjectById'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectById.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { subject_id, ...query } = (validatedInput ?? {}) as { - subject_id?: string; - }; - const resourcePath = subject_id - ? `odata/v4/CalSession.svc/CalibrationSubject('${subject_id}')` - : 'odata/v4/CalSession.svc/CalibrationSubject'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCalibrationSubjectById'] - >(resourcePath, ctx.key, { - method: 'GET', - query: query as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectById.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.calibration.getCalibrationSubjectById', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Calibration Subject Ratings -// Query a subject's ratings/competency ratings/comments by session ID. -export const getCalibrationSubjectRatings: SapsuccessfactorsEndpoints['getCalibrationSubjectRatings'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectRatings.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCalibrationSubjectRatings'] - >('odata/v4/CalSession.svc/CalibrationSubject', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectRatings.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.calibration.getCalibrationSubjectRatings', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Update Calibration Subject Ratings -// Update a subject's competency ratings in a calibration session. -export const updateCalibrationSubjectRatings: SapsuccessfactorsEndpoints['updateCalibrationSubjectRatings'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.updateCalibrationSubjectRatings.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { subject_id, body, ...rest } = (validatedInput ?? {}) as { - subject_id?: string; - body?: Record; - }; - const resourcePath = subject_id - ? `odata/v4/CalSession.svc/CalibrationSubject(${subject_id})` - : 'odata/v4/CalSession.svc/CalibrationSubject'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['updateCalibrationSubjectRatings'] - >(resourcePath, ctx.key, { - method: 'PATCH', - body: (body ?? rest) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.updateCalibrationSubjectRatings.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.calibration.updateCalibrationSubjectRatings', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/candidates.ts b/packages/sapsuccessfactors/endpoints/candidates.ts deleted file mode 100644 index 11893e239..000000000 --- a/packages/sapsuccessfactors/endpoints/candidates.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// List Candidates -// Retrieve a list of candidates. -export const listCandidates: SapsuccessfactorsEndpoints['listCandidates'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.listCandidates.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['listCandidates'] - >('odata/v2/Candidate', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.listCandidates.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.candidates.listCandidates', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/cdp.ts b/packages/sapsuccessfactors/endpoints/cdp.ts deleted file mode 100644 index f75f90a8c..000000000 --- a/packages/sapsuccessfactors/endpoints/cdp.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get CDP Learning Metadata -// Get metadata for the Career Development Planning Learning service. -export const getCdpLearningMetadata: SapsuccessfactorsEndpoints['getCdpLearningMetadata'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCdpLearningMetadata.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCdpLearningMetadata'] - >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCdpLearningMetadata.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.cdp.getCdpLearningMetadata', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Refresh CDP Learning Metadata -// Refresh metadata for the CDP Learning service. -export const refreshCdpLearningMetadata: SapsuccessfactorsEndpoints['refreshCdpLearningMetadata'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.refreshCdpLearningMetadata.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['refreshCdpLearningMetadata'] - >('odata/v2/RefreshCDPLearningMetadata', ctx.key, { - method: 'POST', - body: (validatedInput ?? {}) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.refreshCdpLearningMetadata.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.cdp.refreshCdpLearningMetadata', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/current.ts b/packages/sapsuccessfactors/endpoints/current.ts deleted file mode 100644 index f86ef32f4..000000000 --- a/packages/sapsuccessfactors/endpoints/current.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Current User -// Retrieve the currently authenticated user's information. -export const getCurrentUser: SapsuccessfactorsEndpoints['getCurrentUser'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCurrentUser.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = (validatedInput ?? {}) as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCurrentUser'] - >('odata/v2/User', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCurrentUser.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.current.getCurrentUser', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/custom.ts b/packages/sapsuccessfactors/endpoints/custom.ts deleted file mode 100644 index 0006399a4..000000000 --- a/packages/sapsuccessfactors/endpoints/custom.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Custom MDF Object -// Retrieve custom MDF objects (names begin with cust_). -export const getCustomMdfObject: SapsuccessfactorsEndpoints['getCustomMdfObject'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getCustomMdfObject.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { custom_object, ...rest } = (validatedInput ?? {}) as { - custom_object?: string; - }; - const rawName = (custom_object || 'cust_object').replace( - /[^A-Za-z0-9_]/g, - '', - ); - const sanitizedObj = rawName.startsWith('cust_') - ? rawName - : `cust_${rawName}`; - const resourcePath = `odata/v2/${sanitizedObj}`; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getCustomMdfObject'] - >(resourcePath, ctx.key, { - method: 'GET', - query: rest as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getCustomMdfObject.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.custom.getCustomMdfObject', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/emp.ts b/packages/sapsuccessfactors/endpoints/emp.ts deleted file mode 100644 index f7951f7b5..000000000 --- a/packages/sapsuccessfactors/endpoints/emp.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// List Employee Employment Records -// Retrieve employment records (start dates, types, assignment classes). -export const listEmpEmployment: SapsuccessfactorsEndpoints['listEmpEmployment'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.listEmpEmployment.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['listEmpEmployment'] - >('odata/v2/EmpEmployment', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.listEmpEmployment.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.emp.listEmpEmployment', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Employee Employment Termination -// Retrieve termination records (date, reason). -export const getEmpEmploymentTermination: SapsuccessfactorsEndpoints['getEmpEmploymentTermination'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getEmpEmploymentTermination.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getEmpEmploymentTermination'] - >('odata/v2/EmpEmploymentTermination', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getEmpEmploymentTermination.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.emp.getEmpEmploymentTermination', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Recurring Pay Components -// Retrieve recurring pay components (salary, allowances, benefits). -export const getEmpPayCompRecurring: SapsuccessfactorsEndpoints['getEmpPayCompRecurring'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getEmpPayCompRecurring.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getEmpPayCompRecurring'] - >('odata/v2/EmpPayCompRecurring', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompRecurring.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.emp.getEmpPayCompRecurring', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Non-Recurring Pay Components -// Retrieve non-recurring pay components (bonuses, one-time payments). -export const getEmpPayCompNonRecurring: SapsuccessfactorsEndpoints['getEmpPayCompNonRecurring'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getEmpPayCompNonRecurring.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getEmpPayCompNonRecurring'] - >('odata/v2/EmpPayCompNonRecurring', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompNonRecurring.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.emp.getEmpPayCompNonRecurring', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/employee.ts b/packages/sapsuccessfactors/endpoints/employee.ts deleted file mode 100644 index 0c3389d1f..000000000 --- a/packages/sapsuccessfactors/endpoints/employee.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Employee Time -// Retrieve employee time entries incl. time off (filter by userId/status/type/date). -export const getEmployeeTime: SapsuccessfactorsEndpoints['getEmployeeTime'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getEmployeeTime.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getEmployeeTime'] - >('odata/v2/EmployeeTime', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getEmployeeTime.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.employee.getEmployeeTime', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Employee Timesheet -// Retrieve timesheet records: attendance, overtime, on-call, allowances. -export const getEmployeeTimesheet: SapsuccessfactorsEndpoints['getEmployeeTimesheet'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getEmployeeTimesheet.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getEmployeeTimesheet'] - >('odata/v2/EmployeeTimeSheet', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getEmployeeTimesheet.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.employee.getEmployeeTimesheet', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/factory.ts b/packages/sapsuccessfactors/endpoints/factory.ts new file mode 100644 index 000000000..a8257e173 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/factory.ts @@ -0,0 +1,205 @@ +import type { CorsairEndpoint } from 'corsair/core'; +import { AuthMissingError, logEventFromContext } from 'corsair/core'; +import { makeSapsuccessfactorsRequest } from '../client'; +import type { + SapsuccessfactorsContext, + SapsuccessfactorsKeyBuilderContext, +} from '../index'; +import type { SapRoute, SapRouteName } from './routes'; +import { getSapRoute } from './routes'; +import type { + SapsuccessfactorsEndpointInput, + SapsuccessfactorsEndpointOutputs, +} from './types'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; + +export type SapEndpoint = CorsairEndpoint< + SapsuccessfactorsContext, + SapsuccessfactorsEndpointInput, + unknown +>; + +const QUERY_KEYS = new Set([ + 'filter', + 'select', + 'expand', + 'top', + 'skip', + 'orderby', +]); + +function odataLiteral(value: unknown): string { + if (value === undefined || value === null || value === '') { + throw new Error('[sapsuccessfactors] missing required path parameter'); + } + return `'${String(value).replace(/'/g, "''")}'`; +} + +function resolveHost( + ctx: Pick & { + keys?: Partial; + }, +): string | undefined { + return ctx.options?.host ?? ctx.options?.apiBaseUrl; +} + +function escapeODataString(value: string): string { + return value.replace(/'/g, "''"); +} + +function resolvePath(route: SapRoute, input: Record): string { + if (route.special === 'customMdf') { + const name = String(input.custom_object ?? ''); + if (!/^cust_[A-Za-z0-9_]+$/.test(name)) { + throw new Error( + '[sapsuccessfactors] custom_object must be a cust_* MDF entity', + ); + } + return `odata/v2/${name}`; + } + if (route.special === 'goalPlan') { + const raw = String(input.goal_plan_id ?? ''); + const id = raw.replace(/^Goal_/i, '').replace(/[^0-9]/g, ''); + if (!id) { + throw new Error( + '[sapsuccessfactors] goal_plan_id must include the numeric plan id', + ); + } + return `odata/v2/Goal_${id}`; + } + return route.path.replace(/\{([^}]+)\}/g, (_, key: string) => + odataLiteral(input[key]), + ); +} + +function buildQuery( + route: SapRoute, + input: Record, +): Record { + const query: Record = {}; + for (const key of QUERY_KEYS) { + const value = input[key]; + if ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' + ) { + query[key] = value; + } + } + if (route.special === 'currentUser' && !query.filter) { + query.filter = "userId eq '$loggedInUser'"; + } + if (route.special === 'applicationInterview') { + const applicationId = input.applicationId; + if (typeof applicationId === 'string' && applicationId && !query.filter) { + query.filter = `applicationId eq '${escapeODataString(applicationId)}'`; + } + } + if (route.special === 'nominationDelete') { + const userId = input.userId; + if (typeof userId === 'string') query.userId = userId; + if (input.isPoolNomination === true) query.isPoolNomination = true; + } + if ( + route.name === 'getCalibrationSubjectRatings' && + typeof input.session_id === 'string' && + !query.filter + ) { + query.filter = `sessionId eq '${escapeODataString(input.session_id)}'`; + } + return query; +} + +const PATH_AND_CONTROL = new Set([ + 'body', + 'filter', + 'select', + 'expand', + 'top', + 'skip', + 'orderby', + 'session_id', + 'subject_id', + 'person_id_external', + 'goal_plan_id', + 'custom_object', + 'nominationTargetId', + 'applicationId', + 'code', +]); + +function requestBody( + route: SapRoute, + input: Record, +): Record | undefined { + if (route.method === 'GET' || route.method === 'DELETE') return undefined; + if (input.body && typeof input.body === 'object') { + return input.body as Record; + } + const body = Object.fromEntries( + Object.entries(input).filter( + ([key, value]) => !PATH_AND_CONTROL.has(key) && value !== undefined, + ), + ); + return Object.keys(body).length > 0 ? body : undefined; +} + +export async function executeSapOperation( + ctx: SapsuccessfactorsContext, + rawInput: SapsuccessfactorsEndpointInput | undefined, + route: SapRoute, +) { + if (!ctx.key) { + throw new AuthMissingError('sapsuccessfactors', 'oauth_2'); + } + const parsed = SapsuccessfactorsEndpointInputSchemas[ + route.name as SapRouteName + ].parse(rawInput ?? {}); + const input = parsed as Record; + const path = resolvePath(route, input); + const host = resolveHost(ctx); + + let status: 'completed' | 'failed' = 'completed'; + try { + const response = await makeSapsuccessfactorsRequest( + path, + ctx.key, + { + method: route.method, + body: requestBody(route, input), + query: buildQuery(route, input), + host, + }, + ); + return SapsuccessfactorsEndpointOutputSchemas[ + route.name as SapRouteName + ].parse(response) as SapsuccessfactorsEndpointOutputs[SapRouteName]; + } catch (error) { + status = 'failed'; + throw error; + } finally { + try { + await logEventFromContext( + ctx, + `sapsuccessfactors.${route.group}.${route.name}`, + { method: route.method, path }, + status, + ); + } catch (logError) { + console.warn( + '[sapsuccessfactors] Failed to log operation event:', + logError, + ); + } + } +} + +export function createSapEndpoint(name: SapRouteName): SapEndpoint { + const route = getSapRoute(name); + return (async (ctx, input) => + executeSapOperation(ctx, input ?? {}, route)) as SapEndpoint; +} diff --git a/packages/sapsuccessfactors/endpoints/feedback.ts b/packages/sapsuccessfactors/endpoints/feedback.ts deleted file mode 100644 index 74ee84e7a..000000000 --- a/packages/sapsuccessfactors/endpoints/feedback.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Feedback Records -// Query continuous feedback records (OData v4). -export const getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoints['getFeedbackRecordsServiceAvailable'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFeedbackRecordsServiceAvailable.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFeedbackRecordsServiceAvailable'] - >('odata/v4/ContinuousPerformanceManagement.svc/Feedback', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFeedbackRecordsServiceAvailable.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.feedback.getFeedbackRecordsServiceAvailable', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/fo.ts b/packages/sapsuccessfactors/endpoints/fo.ts deleted file mode 100644 index 713413969..000000000 --- a/packages/sapsuccessfactors/endpoints/fo.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get FOBusinessUnit -// Retrieve business unit records for org structure hierarchy. -export const getFoBusinessUnit: SapsuccessfactorsEndpoints['getFoBusinessUnit'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoBusinessUnit.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoBusinessUnit'] - >('odata/v2/FOBusinessUnit', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoBusinessUnit.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoBusinessUnit', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get FOCompany Records -// Retrieve company records (display_name, legal_name, entityOID). -export const getFoCompany: SapsuccessfactorsEndpoints['getFoCompany'] = async ( - ctx, - input, -) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoCompany.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoCompany'] - >('odata/v2/FOCompany', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoCompany.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoCompany', - input ?? {}, - 'completed', - ); - return validatedResponse; -}; - -// Get Foundation Object Cost Centers -// Retrieve cost center records for org structure. -export const getFoCostCenter: SapsuccessfactorsEndpoints['getFoCostCenter'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoCostCenter.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoCostCenter'] - >('odata/v2/FOCostCenter', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoCostCenter.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoCostCenter', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get FODepartment Records -// Retrieve department records (team/group org structure). -export const getFoDepartment: SapsuccessfactorsEndpoints['getFoDepartment'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoDepartment.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoDepartment'] - >('odata/v2/FODepartment', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoDepartment.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoDepartment', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Foundation Object Job Codes -// Retrieve job code records with associated position metadata. -export const getFoJobCode: SapsuccessfactorsEndpoints['getFoJobCode'] = async ( - ctx, - input, -) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoJobCode.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoJobCode'] - >('odata/v2/FOJobCode', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoJobCode.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoJobCode', - input ?? {}, - 'completed', - ); - return validatedResponse; -}; - -// Get Job Functions -// Retrieve job function records for categorizing job roles. -export const getFoJobFunction: SapsuccessfactorsEndpoints['getFoJobFunction'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoJobFunction.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoJobFunction'] - >('odata/v2/FOJobFunction', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoJobFunction.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoJobFunction', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Foundation Object Location -// Retrieve work location records (names, status, timezones, address). -export const getFoLocation: SapsuccessfactorsEndpoints['getFoLocation'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoLocation.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoLocation'] - >('odata/v2/FOLocation', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoLocation.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoLocation', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get FOPayGroup -// Retrieve pay group records for compensation/payroll groupings. -export const getFoPayGroup: SapsuccessfactorsEndpoints['getFoPayGroup'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFoPayGroup.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFoPayGroup'] - >('odata/v2/FOPayGroup', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFoPayGroup.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.fo.getFoPayGroup', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/form.ts b/packages/sapsuccessfactors/endpoints/form.ts deleted file mode 100644 index c0fb99464..000000000 --- a/packages/sapsuccessfactors/endpoints/form.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Form Content -// Retrieve performance form content (filter by template ID, modified date). -export const getFormContent: SapsuccessfactorsEndpoints['getFormContent'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getFormContent.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getFormContent'] - >('odata/v2/FormContent', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getFormContent.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.form.getFormContent', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/give.ts b/packages/sapsuccessfactors/endpoints/give.ts deleted file mode 100644 index 653de3548..000000000 --- a/packages/sapsuccessfactors/endpoints/give.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Give Feedback or Respond to Feedback Request -// Give feedback or respond to a feedback request (up to 3 Q&A pairs). -export const giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoints['giveFeedbackOrRespondToAFeedbackRequest'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.giveFeedbackOrRespondToAFeedbackRequest.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { body, ...rest } = (validatedInput ?? {}) as { - body?: Record; - }; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['giveFeedbackOrRespondToAFeedbackRequest'] - >('odata/v4/ContinuousPerformanceManagement.svc/Feedback', ctx.key, { - method: 'POST', - body: (body ?? rest) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.giveFeedbackOrRespondToAFeedbackRequest.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.give.giveFeedbackOrRespondToAFeedbackRequest', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/goal.ts b/packages/sapsuccessfactors/endpoints/goal.ts deleted file mode 100644 index 9b3ce76ce..000000000 --- a/packages/sapsuccessfactors/endpoints/goal.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Goal Plan Template -// Retrieve goal plan template configuration (structure via DTD file). -export const getGoalPlanTemplate: SapsuccessfactorsEndpoints['getGoalPlanTemplate'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getGoalPlanTemplate.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getGoalPlanTemplate'] - >('odata/v2/GoalPlanTemplate', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getGoalPlanTemplate.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.goal.getGoalPlanTemplate', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/goals.ts b/packages/sapsuccessfactors/endpoints/goals.ts deleted file mode 100644 index 10dc6c148..000000000 --- a/packages/sapsuccessfactors/endpoints/goals.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Goals By Plan -// Retrieve goals for a specific plan (e.g. Goal_11), optionally by userId. -export const getGoalsByPlan: SapsuccessfactorsEndpoints['getGoalsByPlan'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getGoalsByPlan.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { goal_plan_id, ...rest } = (validatedInput ?? {}) as { - goal_plan_id?: string; - }; - const safeId = (goal_plan_id || 'Goal').replace(/[^A-Za-z0-9_]/g, ''); - const resourcePath = goal_plan_id - ? `odata/v2/Goal_${safeId}` - : 'odata/v2/Goal'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getGoalsByPlan'] - >(resourcePath, ctx.key, { - method: 'GET', - query: rest as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getGoalsByPlan.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.goals.getGoalsByPlan', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/index.ts b/packages/sapsuccessfactors/endpoints/index.ts index dbf2895b4..b97b14966 100644 --- a/packages/sapsuccessfactors/endpoints/index.ts +++ b/packages/sapsuccessfactors/endpoints/index.ts @@ -1,178 +1,146 @@ -import { approveCalibrationSession } from './approve'; -export const Approve = { approveCalibrationSession }; - -import { - getCalibrationSessionById, - getCalibrationSessions, - getCalibrationSubjectById, - getCalibrationSubjectRatings, - updateCalibrationSubjectRatings, -} from './calibration'; -export const Calibration = { - getCalibrationSessionById, - getCalibrationSessions, - getCalibrationSubjectById, - getCalibrationSubjectRatings, - updateCalibrationSubjectRatings, -}; - -import { - getOdataMetadataCalibSessionService, - getOdataMetadataClockInclockOut, - getOdataMetadataForNominationService, - getOdataMetadataOnboardingAddl, - getOdataUserMetadata, -} from './odata'; -export const Odata = { - getOdataMetadataCalibSessionService, - getOdataMetadataOnboardingAddl, - getOdataMetadataForNominationService, - getOdataUserMetadata, - getOdataMetadataClockInclockOut, -}; - -import { createOnboardee } from './onboardee'; -export const Onboardee = { createOnboardee }; - -import { getOnb2Process } from './onb2'; -export const Onb2 = { getOnb2Process }; - -import { updateInternalUsernameNewHiresAfter } from './internal'; -export const Internal = { updateInternalUsernameNewHiresAfter }; - -import { createAFeedbackRequest } from './a'; -export const A = { createAFeedbackRequest }; - -import { getFeedbackRecordsServiceAvailable } from './feedback'; -export const Feedback = { getFeedbackRecordsServiceAvailable }; - -import { getPendingFeedbackRequestsFeedback } from './pending'; -export const Pending = { getPendingFeedbackRequestsFeedback }; - -import { giveFeedbackOrRespondToAFeedbackRequest } from './give'; -export const Give = { giveFeedbackOrRespondToAFeedbackRequest }; - -import { refreshMetadataContFeedbackService } from './metadata'; -export const Metadata = { refreshMetadataContFeedbackService }; - -import { createUpdateSuccessorNomination } from './successor'; -export const Successor = { createUpdateSuccessorNomination }; - -import { deleteNominationPositionTalentPool } from './nomination'; -export const Nomination = { deleteNominationPositionTalentPool }; - -import { getTalentPool } from './talent'; -export const Talent = { getTalentPool }; - -import { getApplicationInterview } from './application'; -export const Application = { getApplicationInterview }; - -import { getInterviewOverallAssessment } from './interview'; -export const Interview = { getInterviewOverallAssessment }; - -import { - getJobApplication, - getJobReqScreeningQuestion, - getJobRequisition, -} from './job'; -export const Job = { - getJobApplication, - getJobRequisition, - getJobReqScreeningQuestion, -}; - -import { listCandidates } from './candidates'; -export const Candidates = { listCandidates }; - -import { - getFoBusinessUnit, - getFoCompany, - getFoCostCenter, - getFoDepartment, - getFoJobCode, - getFoJobFunction, - getFoLocation, - getFoPayGroup, -} from './fo'; -export const Fo = { - getFoBusinessUnit, - getFoCompany, - getFoCostCenter, - getFoDepartment, - getFoJobCode, - getFoJobFunction, - getFoLocation, - getFoPayGroup, -}; - -import { getPosition } from './position'; -export const Position = { getPosition }; - -import { getCustomMdfObject } from './custom'; -export const Custom = { getCustomMdfObject }; - -import { getPicklist, getPicklistOption } from './picklist'; -export const Picklist = { getPicklist, getPicklistOption }; - -import { getCurrentUser } from './current'; -export const Current = { getCurrentUser }; - -import { listUsers } from './users'; -export const Users = { listUsers }; - -import { getPerPersonal, getPerPersonById, listPerPerson } from './per'; -export const Per = { getPerPersonById, listPerPerson, getPerPersonal }; - -import { getBackgroundEducation, getBackgroundMobility } from './background'; -export const Background = { getBackgroundEducation, getBackgroundMobility }; - -import { - getEmpEmploymentTermination, - getEmpPayCompNonRecurring, - getEmpPayCompRecurring, - listEmpEmployment, -} from './emp'; -export const Emp = { - listEmpEmployment, - getEmpEmploymentTermination, - getEmpPayCompRecurring, - getEmpPayCompNonRecurring, -}; - -import { getWorkOrder } from './work'; -export const Work = { getWorkOrder }; - -import { getGoalPlanTemplate } from './goal'; -export const Goal = { getGoalPlanTemplate }; - -import { getGoalsByPlan } from './goals'; -export const Goals = { getGoalsByPlan }; - -import { getFormContent } from './form'; -export const Form = { getFormContent }; - -import { createLearningActivitiesBulk } from './learning'; -export const Learning = { createLearningActivitiesBulk }; - -import { getCdpLearningMetadata, refreshCdpLearningMetadata } from './cdp'; -export const Cdp = { getCdpLearningMetadata, refreshCdpLearningMetadata }; - -import { getEmployeeTime, getEmployeeTimesheet } from './employee'; -export const Employee = { getEmployeeTime, getEmployeeTimesheet }; - -import { getTemporaryTimeInformation } from './temporary'; -export const Temporary = { getTemporaryTimeInformation }; - -import { getTimeAccountSnapshot } from './time'; -export const Time = { getTimeAccountSnapshot }; - -import { - queryAllAvailableClockClockOut, - queryClockClockOutGroupCodeTime, -} from './query'; -export const Query = { - queryAllAvailableClockClockOut, - queryClockClockOutGroupCodeTime, -}; - -export * from './types'; +import { createSapEndpoint } from './factory'; +import type { SapRouteName } from './routes'; +import { sapRoutes } from './routes'; + +export const sapOperations = Object.fromEntries( + sapRoutes.map((route) => [route.name, createSapEndpoint(route.name)]), +) as { [K in SapRouteName]: ReturnType }; + +export const sapsuccessfactorsEndpointsNested = { + approve: { + approveCalibrationSession: sapOperations.approveCalibrationSession, + }, + calibration: { + getCalibrationSessionById: sapOperations.getCalibrationSessionById, + getCalibrationSessions: sapOperations.getCalibrationSessions, + getCalibrationSubjectById: sapOperations.getCalibrationSubjectById, + getCalibrationSubjectRatings: sapOperations.getCalibrationSubjectRatings, + updateCalibrationSubjectRatings: + sapOperations.updateCalibrationSubjectRatings, + }, + odata: { + getOdataMetadataCalibSessionService: + sapOperations.getOdataMetadataCalibSessionService, + getOdataMetadataOnboardingAddl: + sapOperations.getOdataMetadataOnboardingAddl, + getOdataMetadataForNominationService: + sapOperations.getOdataMetadataForNominationService, + getOdataUserMetadata: sapOperations.getOdataUserMetadata, + getOdataMetadataClockInclockOut: + sapOperations.getOdataMetadataClockInclockOut, + }, + onboardee: { createOnboardee: sapOperations.createOnboardee }, + onb2: { getOnb2Process: sapOperations.getOnb2Process }, + internal: { + updateInternalUsernameNewHiresAfter: + sapOperations.updateInternalUsernameNewHiresAfter, + }, + a: { createAFeedbackRequest: sapOperations.createAFeedbackRequest }, + feedback: { + getFeedbackRecordsServiceAvailable: + sapOperations.getFeedbackRecordsServiceAvailable, + }, + pending: { + getPendingFeedbackRequestsFeedback: + sapOperations.getPendingFeedbackRequestsFeedback, + }, + give: { + giveFeedbackOrRespondToAFeedbackRequest: + sapOperations.giveFeedbackOrRespondToAFeedbackRequest, + }, + metadata: { + refreshMetadataContFeedbackService: + sapOperations.refreshMetadataContFeedbackService, + }, + successor: { + createUpdateSuccessorNomination: + sapOperations.createUpdateSuccessorNomination, + }, + nomination: { + deleteNominationPositionTalentPool: + sapOperations.deleteNominationPositionTalentPool, + }, + talent: { getTalentPool: sapOperations.getTalentPool }, + application: { + getApplicationInterview: sapOperations.getApplicationInterview, + }, + interview: { + getInterviewOverallAssessment: sapOperations.getInterviewOverallAssessment, + }, + job: { + getJobApplication: sapOperations.getJobApplication, + getJobRequisition: sapOperations.getJobRequisition, + getJobReqScreeningQuestion: sapOperations.getJobReqScreeningQuestion, + }, + candidates: { listCandidates: sapOperations.listCandidates }, + fo: { + getFoBusinessUnit: sapOperations.getFoBusinessUnit, + getFoCompany: sapOperations.getFoCompany, + getFoCostCenter: sapOperations.getFoCostCenter, + getFoDepartment: sapOperations.getFoDepartment, + getFoJobCode: sapOperations.getFoJobCode, + getFoJobFunction: sapOperations.getFoJobFunction, + getFoLocation: sapOperations.getFoLocation, + getFoPayGroup: sapOperations.getFoPayGroup, + }, + position: { getPosition: sapOperations.getPosition }, + custom: { getCustomMdfObject: sapOperations.getCustomMdfObject }, + picklist: { + getPicklist: sapOperations.getPicklist, + getPicklistOption: sapOperations.getPicklistOption, + }, + current: { getCurrentUser: sapOperations.getCurrentUser }, + users: { listUsers: sapOperations.listUsers }, + per: { + getPerPersonById: sapOperations.getPerPersonById, + listPerPerson: sapOperations.listPerPerson, + getPerPersonal: sapOperations.getPerPersonal, + }, + background: { + getBackgroundEducation: sapOperations.getBackgroundEducation, + getBackgroundMobility: sapOperations.getBackgroundMobility, + }, + emp: { + listEmpEmployment: sapOperations.listEmpEmployment, + getEmpEmploymentTermination: sapOperations.getEmpEmploymentTermination, + getEmpPayCompRecurring: sapOperations.getEmpPayCompRecurring, + getEmpPayCompNonRecurring: sapOperations.getEmpPayCompNonRecurring, + }, + work: { getWorkOrder: sapOperations.getWorkOrder }, + goal: { getGoalPlanTemplate: sapOperations.getGoalPlanTemplate }, + goals: { getGoalsByPlan: sapOperations.getGoalsByPlan }, + form: { getFormContent: sapOperations.getFormContent }, + learning: { + createLearningActivitiesBulk: sapOperations.createLearningActivitiesBulk, + }, + cdp: { + getCdpLearningMetadata: sapOperations.getCdpLearningMetadata, + refreshCdpLearningMetadata: sapOperations.refreshCdpLearningMetadata, + }, + employee: { + getEmployeeTime: sapOperations.getEmployeeTime, + getEmployeeTimesheet: sapOperations.getEmployeeTimesheet, + }, + temporary: { + getTemporaryTimeInformation: sapOperations.getTemporaryTimeInformation, + }, + time: { getTimeAccountSnapshot: sapOperations.getTimeAccountSnapshot }, + query: { + queryAllAvailableClockClockOut: + sapOperations.queryAllAvailableClockClockOut, + queryClockClockOutGroupCodeTime: + sapOperations.queryClockClockOutGroupCodeTime, + }, +} as const; + +export { createSapEndpoint, executeSapOperation } from './factory'; +export type { SapRoute, SapRouteName } from './routes'; +export { getSapRoute, sapRouteByName, sapRoutes } from './routes'; +export type { + SapsuccessfactorsEndpointInputs, + SapsuccessfactorsEndpointOutputs, +} from './types'; +export { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, +} from './types'; diff --git a/packages/sapsuccessfactors/endpoints/internal.ts b/packages/sapsuccessfactors/endpoints/internal.ts deleted file mode 100644 index 8245fdf39..000000000 --- a/packages/sapsuccessfactors/endpoints/internal.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Update Username Post Hiring -// Update a new hire's internal username after MPH submit, pre day-1. -export const updateInternalUsernameNewHiresAfter: SapsuccessfactorsEndpoints['updateInternalUsernameNewHiresAfter'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.updateInternalUsernameNewHiresAfter.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['updateInternalUsernameNewHiresAfter'] - >('odata/v2/updateUserNamePostHiring', ctx.key, { - method: 'POST', - body: (validatedInput ?? {}) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.updateInternalUsernameNewHiresAfter.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.internal.updateInternalUsernameNewHiresAfter', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/interview.ts b/packages/sapsuccessfactors/endpoints/interview.ts deleted file mode 100644 index 8fc2fe95c..000000000 --- a/packages/sapsuccessfactors/endpoints/interview.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Interview Overall Assessment -// Retrieve overall interview ratings, recommendations, and comments. -export const getInterviewOverallAssessment: SapsuccessfactorsEndpoints['getInterviewOverallAssessment'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getInterviewOverallAssessment.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getInterviewOverallAssessment'] - >('odata/v2/OverallInterviewAssessment', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getInterviewOverallAssessment.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.interview.getInterviewOverallAssessment', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/job.ts b/packages/sapsuccessfactors/endpoints/job.ts deleted file mode 100644 index c5d518275..000000000 --- a/packages/sapsuccessfactors/endpoints/job.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Job Application -// Retrieve job application records linking candidates to requisitions. -export const getJobApplication: SapsuccessfactorsEndpoints['getJobApplication'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getJobApplication.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getJobApplication'] - >('odata/v2/JobApplication', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getJobApplication.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.job.getJobApplication', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Job Requisition -// Retrieve job requisition records from Recruiting Management. -export const getJobRequisition: SapsuccessfactorsEndpoints['getJobRequisition'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getJobRequisition.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getJobRequisition'] - >('odata/v2/JobRequisition', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getJobRequisition.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.job.getJobRequisition', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Job Requisition Screening Questions -// Retrieve screening questions for a job requisition. -export const getJobReqScreeningQuestion: SapsuccessfactorsEndpoints['getJobReqScreeningQuestion'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getJobReqScreeningQuestion.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getJobReqScreeningQuestion'] - >('odata/v2/JobReqScreeningQuestion', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getJobReqScreeningQuestion.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.job.getJobReqScreeningQuestion', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/learning.ts b/packages/sapsuccessfactors/endpoints/learning.ts deleted file mode 100644 index 118eef73a..000000000 --- a/packages/sapsuccessfactors/endpoints/learning.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Create Learning Activities Bulk -// Create learning activities linked to dev goals in bulk (3rd-party LMS). -export const createLearningActivitiesBulk: SapsuccessfactorsEndpoints['createLearningActivitiesBulk'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.createLearningActivitiesBulk.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { body, ...rest } = (validatedInput ?? {}) as { - body?: Record; - }; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['createLearningActivitiesBulk'] - >('odata/v2/LearningActivity', ctx.key, { - method: 'POST', - body: (body ?? rest) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.createLearningActivitiesBulk.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.learning.createLearningActivitiesBulk', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/metadata.ts b/packages/sapsuccessfactors/endpoints/metadata.ts deleted file mode 100644 index d8b40ffaf..000000000 --- a/packages/sapsuccessfactors/endpoints/metadata.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Refresh Metadata for Continuous Feedback -// Refresh the metadata cache for the Continuous Feedback service. -export const refreshMetadataContFeedbackService: SapsuccessfactorsEndpoints['refreshMetadataContFeedbackService'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.refreshMetadataContFeedbackService.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['refreshMetadataContFeedbackService'] - >('odata/v4/ContinuousPerformanceManagement.svc/RefreshMetadata', ctx.key, { - method: 'POST', - body: (validatedInput ?? {}) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.refreshMetadataContFeedbackService.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.metadata.refreshMetadataContFeedbackService', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/nomination.ts b/packages/sapsuccessfactors/endpoints/nomination.ts deleted file mode 100644 index 7908c85ab..000000000 --- a/packages/sapsuccessfactors/endpoints/nomination.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Delete Nomination -// Remove a nominee from a position or talent pool nomination. -export const deleteNominationPositionTalentPool: SapsuccessfactorsEndpoints['deleteNominationPositionTalentPool'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.deleteNominationPositionTalentPool.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { nomination_id } = (validatedInput ?? {}) as { - nomination_id?: string; - }; - const resourcePath = nomination_id - ? `odata/v4/NominationService.svc/Nomination(${nomination_id})` - : 'odata/v4/NominationService.svc/Nomination'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['deleteNominationPositionTalentPool'] - >(resourcePath, ctx.key, { method: 'DELETE', apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.deleteNominationPositionTalentPool.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.nomination.deleteNominationPositionTalentPool', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/odata.ts b/packages/sapsuccessfactors/endpoints/odata.ts deleted file mode 100644 index 1ed0be76a..000000000 --- a/packages/sapsuccessfactors/endpoints/odata.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Calibration Session Metadata -// Get OData metadata / available entity sets for CalSession.svc. -export const getOdataMetadataCalibSessionService: SapsuccessfactorsEndpoints['getOdataMetadataCalibSessionService'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataCalibSessionService.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getOdataMetadataCalibSessionService'] - >('odata/v4/CalSession.svc/$metadata', ctx.key, { - method: 'GET', - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataCalibSessionService.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.odata.getOdataMetadataCalibSessionService', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Onboarding Additional Services Metadata -// Get metadata for Onboarding Additional Services (incl. username update ops). -export const getOdataMetadataOnboardingAddl: SapsuccessfactorsEndpoints['getOdataMetadataOnboardingAddl'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataOnboardingAddl.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getOdataMetadataOnboardingAddl'] - >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataOnboardingAddl.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.odata.getOdataMetadataOnboardingAddl', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Nomination Service Metadata -// Get OData metadata for the Nomination service. -export const getOdataMetadataForNominationService: SapsuccessfactorsEndpoints['getOdataMetadataForNominationService'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataForNominationService.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getOdataMetadataForNominationService'] - >('odata/v4/NominationService.svc/$metadata', ctx.key, { - method: 'GET', - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataForNominationService.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.odata.getOdataMetadataForNominationService', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get User Entity Metadata -// Retrieve OData metadata for the User entity. -export const getOdataUserMetadata: SapsuccessfactorsEndpoints['getOdataUserMetadata'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getOdataUserMetadata.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getOdataUserMetadata'] - >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getOdataUserMetadata.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.odata.getOdataUserMetadata', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Clock In/Out Integration Metadata -// Get OData metadata for the Clock In/Clock Out Integration service. -export const getOdataMetadataClockInclockOut: SapsuccessfactorsEndpoints['getOdataMetadataClockInclockOut'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataClockInclockOut.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getOdataMetadataClockInclockOut'] - >('odata/v2/$metadata', ctx.key, { method: 'GET', apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataClockInclockOut.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.odata.getOdataMetadataClockInclockOut', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/onb2.ts b/packages/sapsuccessfactors/endpoints/onb2.ts deleted file mode 100644 index d841038ab..000000000 --- a/packages/sapsuccessfactors/endpoints/onb2.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Onboarding 2.0 Processes -// Retrieve Onboarding 2.0 process records for new hires. -export const getOnb2Process: SapsuccessfactorsEndpoints['getOnb2Process'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getOnb2Process.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getOnb2Process'] - >('odata/v2/ONB2Process', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getOnb2Process.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.onb2.getOnb2Process', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/onboardee.ts b/packages/sapsuccessfactors/endpoints/onboardee.ts deleted file mode 100644 index 20ef1ec8f..000000000 --- a/packages/sapsuccessfactors/endpoints/onboardee.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Create Onboardee -// Create a new onboardee in Onboarding 2.0 (new hire or rehire). -export const createOnboardee: SapsuccessfactorsEndpoints['createOnboardee'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.createOnboardee.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { body, ...rest } = (validatedInput ?? {}) as { - body?: Record; - }; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['createOnboardee'] - >('odata/v2/Onboardee', ctx.key, { - method: 'POST', - body: (body ?? rest) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.createOnboardee.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.onboardee.createOnboardee', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/pending.ts b/packages/sapsuccessfactors/endpoints/pending.ts deleted file mode 100644 index 82b6c2cdb..000000000 --- a/packages/sapsuccessfactors/endpoints/pending.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Pending Feedback Requests -// Query pending feedback requests. -export const getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoints['getPendingFeedbackRequestsFeedback'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getPendingFeedbackRequestsFeedback.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getPendingFeedbackRequestsFeedback'] - >('odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getPendingFeedbackRequestsFeedback.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.pending.getPendingFeedbackRequestsFeedback', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/per.ts b/packages/sapsuccessfactors/endpoints/per.ts deleted file mode 100644 index 040bc0729..000000000 --- a/packages/sapsuccessfactors/endpoints/per.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Person by ID -// Retrieve core person info for an employee by external person ID. -export const getPerPersonById: SapsuccessfactorsEndpoints['getPerPersonById'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { person_id_external, ...query } = (validatedInput ?? {}) as { - person_id_external?: string; - }; - const resourcePath = person_id_external - ? `odata/v2/PerPerson('${person_id_external}')` - : 'odata/v2/PerPerson'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getPerPersonById'] - >(resourcePath, ctx.key, { - method: 'GET', - query: query as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getPerPersonById.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.per.getPerPersonById', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// List Person Records -// Retrieve person records (latest active record per person). -export const listPerPerson: SapsuccessfactorsEndpoints['listPerPerson'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.listPerPerson.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['listPerPerson'] - >('odata/v2/PerPerson', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.listPerPerson.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.per.listPerPerson', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Get Personal Information Records -// Retrieve biographical info, emergency contacts, social/email data. -export const getPerPersonal: SapsuccessfactorsEndpoints['getPerPersonal'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getPerPersonal.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getPerPersonal'] - >('odata/v2/PerPersonal', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getPerPersonal.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.per.getPerPersonal', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/picklist.ts b/packages/sapsuccessfactors/endpoints/picklist.ts deleted file mode 100644 index bc93876f2..000000000 --- a/packages/sapsuccessfactors/endpoints/picklist.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Picklist -// Retrieve picklist definitions (selectable value lists). -export const getPicklist: SapsuccessfactorsEndpoints['getPicklist'] = async ( - ctx, - input, -) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getPicklist.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getPicklist'] - >('odata/v2/Picklist', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getPicklist.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.picklist.getPicklist', - input ?? {}, - 'completed', - ); - return validatedResponse; -}; - -// Get Picklist Option -// Retrieve picklist option values with localized labels. -export const getPicklistOption: SapsuccessfactorsEndpoints['getPicklistOption'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getPicklistOption.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getPicklistOption'] - >('odata/v2/PicklistOption', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getPicklistOption.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.picklist.getPicklistOption', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/position.ts b/packages/sapsuccessfactors/endpoints/position.ts deleted file mode 100644 index 0be660e87..000000000 --- a/packages/sapsuccessfactors/endpoints/position.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Position -// Retrieve position management records (structure and hierarchy). -export const getPosition: SapsuccessfactorsEndpoints['getPosition'] = async ( - ctx, - input, -) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getPosition.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getPosition'] - >('odata/v2/Position', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getPosition.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.position.getPosition', - input ?? {}, - 'completed', - ); - return validatedResponse; -}; diff --git a/packages/sapsuccessfactors/endpoints/query.ts b/packages/sapsuccessfactors/endpoints/query.ts deleted file mode 100644 index b1eae0a1c..000000000 --- a/packages/sapsuccessfactors/endpoints/query.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Query All Available Clock In/Clock Out Groups -// Retrieve all configured clock in/clock out groups. -export const queryAllAvailableClockClockOut: SapsuccessfactorsEndpoints['queryAllAvailableClockClockOut'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.queryAllAvailableClockClockOut.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['queryAllAvailableClockClockOut'] - >('odata/v2/ClockInClockOutGroup', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.queryAllAvailableClockClockOut.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.query.queryAllAvailableClockClockOut', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; - -// Query Clock In/Clock Out Group By Code -// Retrieve one clock in/out group by code, optionally with time event types. -export const queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoints['queryClockClockOutGroupCodeTime'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.queryClockClockOutGroupCodeTime.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { code, ...query } = (validatedInput ?? {}) as { code?: string }; - const resourcePath = code - ? `odata/v2/ClockInClockOutGroup('${code}')` - : 'odata/v2/ClockInClockOutGroup'; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['queryClockClockOutGroupCodeTime'] - >(resourcePath, ctx.key, { - method: 'GET', - query: query as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.queryClockClockOutGroupCodeTime.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.query.queryClockClockOutGroupCodeTime', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/routes.ts b/packages/sapsuccessfactors/endpoints/routes.ts new file mode 100644 index 000000000..80845ecb6 --- /dev/null +++ b/packages/sapsuccessfactors/endpoints/routes.ts @@ -0,0 +1,553 @@ +export type SapRisk = 'read' | 'write' | 'destructive'; + +export type SapSpecial = + | 'customMdf' + | 'goalPlan' + | 'nominationDelete' + | 'applicationInterview' + | 'currentUser'; + +export type SapRoute = { + name: string; + group: string; + method: 'GET' | 'POST' | 'PATCH' | 'DELETE'; + path: string; + description: string; + riskLevel: SapRisk; + irreversible?: true; + special?: SapSpecial; +}; + +export const sapRoutes = [ + { + name: 'approveCalibrationSession', + group: 'approve', + method: 'POST', + path: 'odata/v4/CalSession.svc/Approve', + description: + 'Finalize a calibration session that is In Progress or Approving', + riskLevel: 'write', + }, + { + name: 'getCalibrationSessionById', + group: 'calibration', + method: 'GET', + path: 'odata/v4/CalSession.svc/CalibrationSession({session_id})', + description: 'Get a specific calibration session by session ID', + riskLevel: 'read', + }, + { + name: 'getCalibrationSessions', + group: 'calibration', + method: 'GET', + path: 'odata/v4/CalSession.svc/CalibrationSession', + description: 'Query all calibration sessions the current user can access', + riskLevel: 'read', + }, + { + name: 'getCalibrationSubjectById', + group: 'calibration', + method: 'GET', + path: 'odata/v4/CalSession.svc/CalibrationSubject({subject_id})', + description: + "Query a subject's competency ratings within a calibration session", + riskLevel: 'read', + }, + { + name: 'getCalibrationSubjectRatings', + group: 'calibration', + method: 'GET', + path: 'odata/v4/CalSession.svc/CalibrationSubject', + description: "Query a subject's ratings by session ID", + riskLevel: 'read', + }, + { + name: 'updateCalibrationSubjectRatings', + group: 'calibration', + method: 'PATCH', + path: 'odata/v4/CalSession.svc/CalibrationSubject({subject_id})', + description: + "Update a subject's competency ratings in a calibration session", + riskLevel: 'write', + }, + { + name: 'getOdataMetadataCalibSessionService', + group: 'odata', + method: 'GET', + path: 'odata/v4/CalSession.svc/$metadata', + description: 'Get OData metadata for Calibration Session service', + riskLevel: 'read', + }, + { + name: 'getOdataMetadataOnboardingAddl', + group: 'odata', + method: 'GET', + path: 'odata/v2/$metadata', + description: 'Get OData metadata for Onboarding Additional Services', + riskLevel: 'read', + }, + { + name: 'getOdataMetadataForNominationService', + group: 'odata', + method: 'GET', + path: 'odata/v4/NominationService.svc/$metadata', + description: 'Get OData metadata for Nomination service', + riskLevel: 'read', + }, + { + name: 'getOdataUserMetadata', + group: 'odata', + method: 'GET', + path: 'odata/v2/User/$metadata', + description: 'Get OData metadata for the User entity', + riskLevel: 'read', + }, + { + name: 'getOdataMetadataClockInclockOut', + group: 'odata', + method: 'GET', + path: 'odata/v2/ClockInClockOutGroup/$metadata', + description: 'Get OData metadata for Clock In/Clock Out Integration', + riskLevel: 'read', + }, + { + name: 'createOnboardee', + group: 'onboardee', + method: 'POST', + path: 'odata/v2/User', + description: 'Create a new onboardee (User) for Onboarding 2.0', + riskLevel: 'write', + }, + { + name: 'getOnb2Process', + group: 'onb2', + method: 'GET', + path: 'odata/v2/ONB2Process', + description: 'Retrieve Onboarding 2.0 process records', + riskLevel: 'read', + }, + { + name: 'updateInternalUsernameNewHiresAfter', + group: 'internal', + method: 'POST', + path: 'odata/v2/updateUserNamePostHiring', + description: 'Update internal username of new hires after MPH submit', + riskLevel: 'write', + }, + { + name: 'createAFeedbackRequest', + group: 'a', + method: 'POST', + path: 'odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', + description: 'Create a continuous feedback request', + riskLevel: 'write', + }, + { + name: 'getFeedbackRecordsServiceAvailable', + group: 'feedback', + method: 'GET', + path: 'odata/v4/ContinuousPerformanceManagement.svc/Feedback', + description: 'Retrieve continuous feedback records (OData V4)', + riskLevel: 'read', + }, + { + name: 'getPendingFeedbackRequestsFeedback', + group: 'pending', + method: 'GET', + path: 'odata/v4/ContinuousPerformanceManagement.svc/FeedbackRequest', + description: 'Retrieve pending feedback requests', + riskLevel: 'read', + }, + { + name: 'giveFeedbackOrRespondToAFeedbackRequest', + group: 'give', + method: 'POST', + path: 'odata/v4/ContinuousPerformanceManagement.svc/Feedback', + description: 'Give feedback or respond to a feedback request', + riskLevel: 'write', + }, + { + name: 'refreshMetadataContFeedbackService', + group: 'metadata', + method: 'POST', + path: 'odata/v4/ContinuousPerformanceManagement.svc/RefreshMetadata', + description: 'Refresh metadata cache for Continuous Feedback', + riskLevel: 'write', + }, + { + name: 'createUpdateSuccessorNomination', + group: 'successor', + method: 'POST', + path: 'odata/v4/NominationService.svc/NominationTarget', + description: 'Create or update a successor nomination', + riskLevel: 'write', + }, + { + name: 'deleteNominationPositionTalentPool', + group: 'nomination', + method: 'DELETE', + path: 'odata/v4/NominationService.svc/NominationTarget({nominationTargetId})', + description: 'Delete a nomination for a position or talent pool', + riskLevel: 'destructive', + irreversible: true, + special: 'nominationDelete', + }, + { + name: 'getTalentPool', + group: 'talent', + method: 'GET', + path: 'odata/v2/TalentPool', + description: 'Retrieve talent pool records', + riskLevel: 'read', + }, + { + name: 'getApplicationInterview', + group: 'application', + method: 'GET', + path: 'odata/v2/ApplicationInterview', + description: 'Retrieve interview information for job applications', + riskLevel: 'read', + special: 'applicationInterview', + }, + { + name: 'getInterviewOverallAssessment', + group: 'interview', + method: 'GET', + path: 'odata/v2/OverallInterviewAssessment', + description: 'Retrieve overall interview ratings', + riskLevel: 'read', + }, + { + name: 'getJobApplication', + group: 'job', + method: 'GET', + path: 'odata/v2/JobApplication', + description: 'Retrieve job application records', + riskLevel: 'read', + }, + { + name: 'getJobRequisition', + group: 'job', + method: 'GET', + path: 'odata/v2/JobRequisition', + description: 'Retrieve job requisition records', + riskLevel: 'read', + }, + { + name: 'getJobReqScreeningQuestion', + group: 'job', + method: 'GET', + path: 'odata/v2/JobReqScreeningQuestion', + description: 'Retrieve screening questions for job requisitions', + riskLevel: 'read', + }, + { + name: 'listCandidates', + group: 'candidates', + method: 'GET', + path: 'odata/v2/Candidate', + description: 'Retrieve candidates', + riskLevel: 'read', + }, + { + name: 'getFoBusinessUnit', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOBusinessUnit', + description: 'Retrieve FOBusinessUnit records', + riskLevel: 'read', + }, + { + name: 'getFoCompany', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOCompany', + description: 'Retrieve FOCompany records', + riskLevel: 'read', + }, + { + name: 'getFoCostCenter', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOCostCenter', + description: 'Retrieve FOCostCenter records', + riskLevel: 'read', + }, + { + name: 'getFoDepartment', + group: 'fo', + method: 'GET', + path: 'odata/v2/FODepartment', + description: 'Retrieve FODepartment records', + riskLevel: 'read', + }, + { + name: 'getFoJobCode', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOJobCode', + description: 'Retrieve FOJobCode records', + riskLevel: 'read', + }, + { + name: 'getFoJobFunction', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOJobFunction', + description: 'Retrieve FOJobFunction records', + riskLevel: 'read', + }, + { + name: 'getFoLocation', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOLocation', + description: 'Retrieve FOLocation records', + riskLevel: 'read', + }, + { + name: 'getFoPayGroup', + group: 'fo', + method: 'GET', + path: 'odata/v2/FOPayGroup', + description: 'Retrieve FOPayGroup records', + riskLevel: 'read', + }, + { + name: 'getPosition', + group: 'position', + method: 'GET', + path: 'odata/v2/Position', + description: 'Retrieve position management records', + riskLevel: 'read', + }, + { + name: 'getCustomMdfObject', + group: 'custom', + method: 'GET', + path: 'odata/v2/{custom_object}', + description: 'Retrieve custom MDF objects (cust_* entities)', + riskLevel: 'read', + special: 'customMdf', + }, + { + name: 'getPicklist', + group: 'picklist', + method: 'GET', + path: 'odata/v2/Picklist', + description: 'Retrieve picklist definitions', + riskLevel: 'read', + }, + { + name: 'getPicklistOption', + group: 'picklist', + method: 'GET', + path: 'odata/v2/PicklistOption', + description: 'Retrieve picklist option values', + riskLevel: 'read', + }, + { + name: 'getCurrentUser', + group: 'current', + method: 'GET', + path: 'odata/v2/User', + description: 'Retrieve the currently authenticated user', + riskLevel: 'read', + special: 'currentUser', + }, + { + name: 'listUsers', + group: 'users', + method: 'GET', + path: 'odata/v2/User', + description: 'List User entity records', + riskLevel: 'read', + }, + { + name: 'getPerPersonById', + group: 'per', + method: 'GET', + path: 'odata/v2/PerPerson({person_id_external})', + description: 'Retrieve PerPerson by personIdExternal', + riskLevel: 'read', + }, + { + name: 'listPerPerson', + group: 'per', + method: 'GET', + path: 'odata/v2/PerPerson', + description: 'List PerPerson records', + riskLevel: 'read', + }, + { + name: 'getPerPersonal', + group: 'per', + method: 'GET', + path: 'odata/v2/PerPersonal', + description: 'Retrieve PerPersonal biographical records', + riskLevel: 'read', + }, + { + name: 'getBackgroundEducation', + group: 'background', + method: 'GET', + path: 'odata/v2/Background_Education', + description: 'Retrieve Background_Education records', + riskLevel: 'read', + }, + { + name: 'getBackgroundMobility', + group: 'background', + method: 'GET', + path: 'odata/v2/Background_Mobility', + description: 'Retrieve Background_Mobility records', + riskLevel: 'read', + }, + { + name: 'listEmpEmployment', + group: 'emp', + method: 'GET', + path: 'odata/v2/EmpEmployment', + description: 'List EmpEmployment records', + riskLevel: 'read', + }, + { + name: 'getEmpEmploymentTermination', + group: 'emp', + method: 'GET', + path: 'odata/v2/EmpEmploymentTermination', + description: 'Retrieve EmpEmploymentTermination records', + riskLevel: 'read', + }, + { + name: 'getEmpPayCompRecurring', + group: 'emp', + method: 'GET', + path: 'odata/v2/EmpPayCompRecurring', + description: 'Retrieve EmpPayCompRecurring records', + riskLevel: 'read', + }, + { + name: 'getEmpPayCompNonRecurring', + group: 'emp', + method: 'GET', + path: 'odata/v2/EmpPayCompNonRecurring', + description: 'Retrieve EmpPayCompNonRecurring records', + riskLevel: 'read', + }, + { + name: 'getWorkOrder', + group: 'work', + method: 'GET', + path: 'odata/v2/WorkOrder', + description: 'Retrieve WorkOrder records for contingent workers', + riskLevel: 'read', + }, + { + name: 'getGoalPlanTemplate', + group: 'goal', + method: 'GET', + path: 'odata/v2/GoalPlanTemplate', + description: 'Retrieve goal plan template records', + riskLevel: 'read', + }, + { + name: 'getGoalsByPlan', + group: 'goals', + method: 'GET', + path: 'odata/v2/Goal_{goal_plan_id}', + description: 'Retrieve goals for a Goal_ entity', + riskLevel: 'read', + special: 'goalPlan', + }, + { + name: 'getFormContent', + group: 'form', + method: 'GET', + path: 'odata/v2/FormContent', + description: 'Retrieve performance form content', + riskLevel: 'read', + }, + { + name: 'createLearningActivitiesBulk', + group: 'learning', + method: 'POST', + path: 'odata/v2/LearningActivity', + description: 'Create learning activities in bulk', + riskLevel: 'write', + }, + { + name: 'getCdpLearningMetadata', + group: 'cdp', + method: 'GET', + path: 'odata/v2/$metadata', + description: 'Get metadata for Career Development Planning Learning', + riskLevel: 'read', + }, + { + name: 'refreshCdpLearningMetadata', + group: 'cdp', + method: 'POST', + path: 'odata/v2/refreshCDPLearningMetadata', + description: 'Refresh CDP Learning metadata', + riskLevel: 'write', + }, + { + name: 'getEmployeeTime', + group: 'employee', + method: 'GET', + path: 'odata/v2/EmployeeTime', + description: 'Retrieve EmployeeTime records', + riskLevel: 'read', + }, + { + name: 'getEmployeeTimesheet', + group: 'employee', + method: 'GET', + path: 'odata/v2/EmployeeTimeSheet', + description: 'Retrieve EmployeeTimeSheet records', + riskLevel: 'read', + }, + { + name: 'getTemporaryTimeInformation', + group: 'temporary', + method: 'GET', + path: 'odata/v2/TemporaryTimeInformation', + description: 'Retrieve TemporaryTimeInformation records', + riskLevel: 'read', + }, + { + name: 'getTimeAccountSnapshot', + group: 'time', + method: 'GET', + path: 'odata/v2/TimeAccountSnapshot', + description: 'Retrieve TimeAccountSnapshot records', + riskLevel: 'read', + }, + { + name: 'queryAllAvailableClockClockOut', + group: 'query', + method: 'GET', + path: 'odata/v2/ClockInClockOutGroup', + description: 'Query all clock in/out groups', + riskLevel: 'read', + }, + { + name: 'queryClockClockOutGroupCodeTime', + group: 'query', + method: 'GET', + path: 'odata/v2/ClockInClockOutGroup({code})', + description: 'Query a clock in/out group by code', + riskLevel: 'read', + }, +] as const satisfies readonly SapRoute[]; + +export type SapRouteName = (typeof sapRoutes)[number]['name']; + +export const sapRouteByName = Object.fromEntries( + sapRoutes.map((route) => [route.name, route]), +) as { [K in SapRouteName]: Extract<(typeof sapRoutes)[number], { name: K }> }; + +export function getSapRoute(name: SapRouteName): SapRoute { + return sapRouteByName[name]; +} diff --git a/packages/sapsuccessfactors/endpoints/successor.ts b/packages/sapsuccessfactors/endpoints/successor.ts deleted file mode 100644 index 419c74995..000000000 --- a/packages/sapsuccessfactors/endpoints/successor.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Create or Update Successor Nomination -// Create/update a successor nomination for a position or talent pool. -export const createUpdateSuccessorNomination: SapsuccessfactorsEndpoints['createUpdateSuccessorNomination'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.createUpdateSuccessorNomination.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const { body, ...rest } = (validatedInput ?? {}) as { - body?: Record; - }; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['createUpdateSuccessorNomination'] - >('odata/v4/NominationService.svc/Nomination', ctx.key, { - method: 'POST', - body: (body ?? rest) as Record, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.createUpdateSuccessorNomination.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.successor.createUpdateSuccessorNomination', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/talent.ts b/packages/sapsuccessfactors/endpoints/talent.ts deleted file mode 100644 index b761d7848..000000000 --- a/packages/sapsuccessfactors/endpoints/talent.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Talent Pool -// Retrieve talent pool records including members and nominations. -export const getTalentPool: SapsuccessfactorsEndpoints['getTalentPool'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getTalentPool.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getTalentPool'] - >('odata/v2/TalentPool', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getTalentPool.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.talent.getTalentPool', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/temporary.ts b/packages/sapsuccessfactors/endpoints/temporary.ts deleted file mode 100644 index aa5288bac..000000000 --- a/packages/sapsuccessfactors/endpoints/temporary.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Temporary Time Information -// Retrieve temporary work schedules assigned to employees. -export const getTemporaryTimeInformation: SapsuccessfactorsEndpoints['getTemporaryTimeInformation'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getTemporaryTimeInformation.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getTemporaryTimeInformation'] - >('odata/v2/TemporaryTimeInfo', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getTemporaryTimeInformation.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.temporary.getTemporaryTimeInformation', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/time.ts b/packages/sapsuccessfactors/endpoints/time.ts deleted file mode 100644 index 65dccce9d..000000000 --- a/packages/sapsuccessfactors/endpoints/time.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Time Account Snapshot -// Retrieve time account balances for leave liability / payroll as-of a date. -export const getTimeAccountSnapshot: SapsuccessfactorsEndpoints['getTimeAccountSnapshot'] = - async (ctx, input) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getTimeAccountSnapshot.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getTimeAccountSnapshot'] - >('odata/v2/TimeAccountSnapshot', ctx.key, { - method: 'GET', - query, - apiBaseUrl, - }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getTimeAccountSnapshot.parse( - response, - ); - await logEventFromContext( - ctx, - 'sapsuccessfactors.time.getTimeAccountSnapshot', - input ?? {}, - 'completed', - ); - return validatedResponse; - }; diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts index 8ae70ac70..08a2dc2c2 100644 --- a/packages/sapsuccessfactors/endpoints/types.ts +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -1,1758 +1,169 @@ import { z } from 'zod'; -// Approve Calibration Session -const ApproveCalibrationSessionInputSchema = z.object({ - session_id: z.string(), -}); -export type ApproveCalibrationSessionInput = z.infer< - typeof ApproveCalibrationSessionInputSchema ->; - -const ApproveCalibrationSessionResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type ApproveCalibrationSessionResponse = z.infer< - typeof ApproveCalibrationSessionResponseSchema ->; - -// Get Calibration Session By ID -const GetCalibrationSessionByIdInputSchema = z.object({ - session_id: z.string(), - select: z.string().optional(), - expand: z.string().optional(), -}); -export type GetCalibrationSessionByIdInput = z.infer< - typeof GetCalibrationSessionByIdInputSchema ->; - -const GetCalibrationSessionByIdResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCalibrationSessionByIdResponse = z.infer< - typeof GetCalibrationSessionByIdResponseSchema ->; - -// Get Calibration Sessions -const GetCalibrationSessionsInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetCalibrationSessionsInput = z.infer< - typeof GetCalibrationSessionsInputSchema ->; - -const GetCalibrationSessionsResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCalibrationSessionsResponse = z.infer< - typeof GetCalibrationSessionsResponseSchema ->; - -// Get Calibration Session Metadata -const GetOdataMetadataCalibSessionServiceInputSchema = z.object({}).optional(); -export type GetOdataMetadataCalibSessionServiceInput = z.infer< - typeof GetOdataMetadataCalibSessionServiceInputSchema ->; - -const GetOdataMetadataCalibSessionServiceResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetOdataMetadataCalibSessionServiceResponse = z.infer< - typeof GetOdataMetadataCalibSessionServiceResponseSchema ->; - -// Get Calibration Subject By ID -const GetCalibrationSubjectByIdInputSchema = z.object({ - subject_id: z.string(), - select: z.string().optional(), - expand: z.string().optional(), -}); -export type GetCalibrationSubjectByIdInput = z.infer< - typeof GetCalibrationSubjectByIdInputSchema ->; - -const GetCalibrationSubjectByIdResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCalibrationSubjectByIdResponse = z.infer< - typeof GetCalibrationSubjectByIdResponseSchema ->; - -// Get Calibration Subject Ratings -const GetCalibrationSubjectRatingsInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), - session_id: z.string(), -}); -export type GetCalibrationSubjectRatingsInput = z.infer< - typeof GetCalibrationSubjectRatingsInputSchema ->; - -const GetCalibrationSubjectRatingsResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCalibrationSubjectRatingsResponse = z.infer< - typeof GetCalibrationSubjectRatingsResponseSchema ->; - -// Update Calibration Subject Ratings -const UpdateCalibrationSubjectRatingsInputSchema = z.object({ - subject_id: z.string(), - body: z.record(z.string(), z.unknown()), -}); -export type UpdateCalibrationSubjectRatingsInput = z.infer< - typeof UpdateCalibrationSubjectRatingsInputSchema ->; - -const UpdateCalibrationSubjectRatingsResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type UpdateCalibrationSubjectRatingsResponse = z.infer< - typeof UpdateCalibrationSubjectRatingsResponseSchema ->; - -// Create Onboardee -const CreateOnboardeeInputSchema = z.object({ - body: z.record(z.string(), z.unknown()), -}); -export type CreateOnboardeeInput = z.infer; - -const CreateOnboardeeResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type CreateOnboardeeResponse = z.infer< - typeof CreateOnboardeeResponseSchema ->; - -// Get Onboarding 2.0 Processes -const GetOnb2ProcessInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetOnb2ProcessInput = z.infer; - -const GetOnb2ProcessResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetOnb2ProcessResponse = z.infer< - typeof GetOnb2ProcessResponseSchema ->; - -// Get Onboarding Additional Services Metadata -const GetOdataMetadataOnboardingAddlInputSchema = z.object({}).optional(); -export type GetOdataMetadataOnboardingAddlInput = z.infer< - typeof GetOdataMetadataOnboardingAddlInputSchema ->; - -const GetOdataMetadataOnboardingAddlResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetOdataMetadataOnboardingAddlResponse = z.infer< - typeof GetOdataMetadataOnboardingAddlResponseSchema ->; - -// Update Username Post Hiring -const UpdateInternalUsernameNewHiresAfterInputSchema = z.object({ - user_id: z.string(), - new_username: z.string(), -}); -export type UpdateInternalUsernameNewHiresAfterInput = z.infer< - typeof UpdateInternalUsernameNewHiresAfterInputSchema ->; - -const UpdateInternalUsernameNewHiresAfterResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type UpdateInternalUsernameNewHiresAfterResponse = z.infer< - typeof UpdateInternalUsernameNewHiresAfterResponseSchema ->; - -// Create a Feedback Request -const CreateAFeedbackRequestInputSchema = z.object({ - body: z.record(z.string(), z.unknown()), -}); -export type CreateAFeedbackRequestInput = z.infer< - typeof CreateAFeedbackRequestInputSchema ->; - -const CreateAFeedbackRequestResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type CreateAFeedbackRequestResponse = z.infer< - typeof CreateAFeedbackRequestResponseSchema ->; - -// Get Feedback Records -const GetFeedbackRecordsServiceAvailableInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFeedbackRecordsServiceAvailableInput = z.infer< - typeof GetFeedbackRecordsServiceAvailableInputSchema ->; - -const GetFeedbackRecordsServiceAvailableResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFeedbackRecordsServiceAvailableResponse = z.infer< - typeof GetFeedbackRecordsServiceAvailableResponseSchema ->; - -// Get Pending Feedback Requests -const GetPendingFeedbackRequestsFeedbackInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetPendingFeedbackRequestsFeedbackInput = z.infer< - typeof GetPendingFeedbackRequestsFeedbackInputSchema ->; - -const GetPendingFeedbackRequestsFeedbackResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetPendingFeedbackRequestsFeedbackResponse = z.infer< - typeof GetPendingFeedbackRequestsFeedbackResponseSchema ->; - -// Give Feedback or Respond to Feedback Request -const GiveFeedbackOrRespondToAFeedbackRequestInputSchema = z.object({ - body: z.record(z.string(), z.unknown()), -}); -export type GiveFeedbackOrRespondToAFeedbackRequestInput = z.infer< - typeof GiveFeedbackOrRespondToAFeedbackRequestInputSchema ->; - -const GiveFeedbackOrRespondToAFeedbackRequestResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GiveFeedbackOrRespondToAFeedbackRequestResponse = z.infer< - typeof GiveFeedbackOrRespondToAFeedbackRequestResponseSchema ->; - -// Refresh Metadata for Continuous Feedback -const RefreshMetadataContFeedbackServiceInputSchema = z.object({}).optional(); -export type RefreshMetadataContFeedbackServiceInput = z.infer< - typeof RefreshMetadataContFeedbackServiceInputSchema ->; - -const RefreshMetadataContFeedbackServiceResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type RefreshMetadataContFeedbackServiceResponse = z.infer< - typeof RefreshMetadataContFeedbackServiceResponseSchema ->; - -// Create or Update Successor Nomination -const CreateUpdateSuccessorNominationInputSchema = z.object({ - body: z.record(z.string(), z.unknown()), -}); -export type CreateUpdateSuccessorNominationInput = z.infer< - typeof CreateUpdateSuccessorNominationInputSchema ->; - -const CreateUpdateSuccessorNominationResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type CreateUpdateSuccessorNominationResponse = z.infer< - typeof CreateUpdateSuccessorNominationResponseSchema ->; - -// Delete Nomination -const DeleteNominationPositionTalentPoolInputSchema = z.object({ - nomination_id: z.string(), -}); -export type DeleteNominationPositionTalentPoolInput = z.infer< - typeof DeleteNominationPositionTalentPoolInputSchema ->; - -const DeleteNominationPositionTalentPoolResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type DeleteNominationPositionTalentPoolResponse = z.infer< - typeof DeleteNominationPositionTalentPoolResponseSchema ->; - -// Get Nomination Service Metadata -const GetOdataMetadataForNominationServiceInputSchema = z.object({}).optional(); -export type GetOdataMetadataForNominationServiceInput = z.infer< - typeof GetOdataMetadataForNominationServiceInputSchema ->; - -const GetOdataMetadataForNominationServiceResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetOdataMetadataForNominationServiceResponse = z.infer< - typeof GetOdataMetadataForNominationServiceResponseSchema ->; - -// Get Talent Pool -const GetTalentPoolInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetTalentPoolInput = z.infer; - -const GetTalentPoolResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetTalentPoolResponse = z.infer; - -// Get Application Interview -const GetApplicationInterviewInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetApplicationInterviewInput = z.infer< - typeof GetApplicationInterviewInputSchema ->; - -const GetApplicationInterviewResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetApplicationInterviewResponse = z.infer< - typeof GetApplicationInterviewResponseSchema ->; - -// Get Interview Overall Assessment -const GetInterviewOverallAssessmentInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetInterviewOverallAssessmentInput = z.infer< - typeof GetInterviewOverallAssessmentInputSchema ->; - -const GetInterviewOverallAssessmentResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetInterviewOverallAssessmentResponse = z.infer< - typeof GetInterviewOverallAssessmentResponseSchema ->; - -// Get Job Application -const GetJobApplicationInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetJobApplicationInput = z.infer< - typeof GetJobApplicationInputSchema ->; - -const GetJobApplicationResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetJobApplicationResponse = z.infer< - typeof GetJobApplicationResponseSchema ->; - -// Get Job Requisition -const GetJobRequisitionInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetJobRequisitionInput = z.infer< - typeof GetJobRequisitionInputSchema ->; - -const GetJobRequisitionResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetJobRequisitionResponse = z.infer< - typeof GetJobRequisitionResponseSchema ->; - -// Get Job Requisition Screening Questions -const GetJobReqScreeningQuestionInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetJobReqScreeningQuestionInput = z.infer< - typeof GetJobReqScreeningQuestionInputSchema ->; - -const GetJobReqScreeningQuestionResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetJobReqScreeningQuestionResponse = z.infer< - typeof GetJobReqScreeningQuestionResponseSchema ->; - -// List Candidates -const ListCandidatesInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type ListCandidatesInput = z.infer; - -const ListCandidatesResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type ListCandidatesResponse = z.infer< - typeof ListCandidatesResponseSchema ->; - -// Get FOBusinessUnit -const GetFoBusinessUnitInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoBusinessUnitInput = z.infer< - typeof GetFoBusinessUnitInputSchema ->; - -const GetFoBusinessUnitResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoBusinessUnitResponse = z.infer< - typeof GetFoBusinessUnitResponseSchema ->; - -// Get FOCompany Records -const GetFoCompanyInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoCompanyInput = z.infer; - -const GetFoCompanyResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoCompanyResponse = z.infer; - -// Get Foundation Object Cost Centers -const GetFoCostCenterInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoCostCenterInput = z.infer; - -const GetFoCostCenterResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoCostCenterResponse = z.infer< - typeof GetFoCostCenterResponseSchema ->; - -// Get FODepartment Records -const GetFoDepartmentInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoDepartmentInput = z.infer; - -const GetFoDepartmentResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoDepartmentResponse = z.infer< - typeof GetFoDepartmentResponseSchema ->; - -// Get Foundation Object Job Codes -const GetFoJobCodeInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoJobCodeInput = z.infer; - -const GetFoJobCodeResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoJobCodeResponse = z.infer; - -// Get Job Functions -const GetFoJobFunctionInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoJobFunctionInput = z.infer; - -const GetFoJobFunctionResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoJobFunctionResponse = z.infer< - typeof GetFoJobFunctionResponseSchema ->; - -// Get Foundation Object Location -const GetFoLocationInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoLocationInput = z.infer; - -const GetFoLocationResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoLocationResponse = z.infer; - -// Get FOPayGroup -const GetFoPayGroupInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFoPayGroupInput = z.infer; - -const GetFoPayGroupResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFoPayGroupResponse = z.infer; - -// Get Position -const GetPositionInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetPositionInput = z.infer; - -const GetPositionResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetPositionResponse = z.infer; - -// Get Custom MDF Object -const GetCustomMdfObjectInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), - custom_object: z.string(), -}); -export type GetCustomMdfObjectInput = z.infer< - typeof GetCustomMdfObjectInputSchema ->; - -const GetCustomMdfObjectResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCustomMdfObjectResponse = z.infer< - typeof GetCustomMdfObjectResponseSchema ->; - -// Get Picklist -const GetPicklistInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetPicklistInput = z.infer; - -const GetPicklistResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetPicklistResponse = z.infer; - -// Get Picklist Option -const GetPicklistOptionInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetPicklistOptionInput = z.infer< - typeof GetPicklistOptionInputSchema ->; - -const GetPicklistOptionResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetPicklistOptionResponse = z.infer< - typeof GetPicklistOptionResponseSchema ->; - -// Get Current User -const GetCurrentUserInputSchema = z.object({ - select: z.string().optional(), - expand: z.string().optional(), -}); -export type GetCurrentUserInput = z.infer; - -const GetCurrentUserResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCurrentUserResponse = z.infer< - typeof GetCurrentUserResponseSchema ->; - -// Get User Entity Metadata -const GetOdataUserMetadataInputSchema = z.object({}).optional(); -export type GetOdataUserMetadataInput = z.infer< - typeof GetOdataUserMetadataInputSchema ->; - -const GetOdataUserMetadataResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetOdataUserMetadataResponse = z.infer< - typeof GetOdataUserMetadataResponseSchema ->; - -// List Users -const ListUsersInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type ListUsersInput = z.infer; - -const ListUsersResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type ListUsersResponse = z.infer; - -// Get Person by ID -const GetPerPersonByIdInputSchema = z.object({ - person_id_external: z.string(), - select: z.string().optional(), - expand: z.string().optional(), -}); -export type GetPerPersonByIdInput = z.infer; - -const GetPerPersonByIdResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetPerPersonByIdResponse = z.infer< - typeof GetPerPersonByIdResponseSchema ->; - -// List Person Records -const ListPerPersonInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type ListPerPersonInput = z.infer; - -const ListPerPersonResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type ListPerPersonResponse = z.infer; - -// Get Personal Information Records -const GetPerPersonalInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetPerPersonalInput = z.infer; - -const GetPerPersonalResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetPerPersonalResponse = z.infer< - typeof GetPerPersonalResponseSchema ->; - -// Get Background Education -const GetBackgroundEducationInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetBackgroundEducationInput = z.infer< - typeof GetBackgroundEducationInputSchema ->; - -const GetBackgroundEducationResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetBackgroundEducationResponse = z.infer< - typeof GetBackgroundEducationResponseSchema ->; - -// Get Background Mobility -const GetBackgroundMobilityInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetBackgroundMobilityInput = z.infer< - typeof GetBackgroundMobilityInputSchema ->; - -const GetBackgroundMobilityResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetBackgroundMobilityResponse = z.infer< - typeof GetBackgroundMobilityResponseSchema ->; - -// List Employee Employment Records -const ListEmpEmploymentInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type ListEmpEmploymentInput = z.infer< - typeof ListEmpEmploymentInputSchema ->; - -const ListEmpEmploymentResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type ListEmpEmploymentResponse = z.infer< - typeof ListEmpEmploymentResponseSchema ->; - -// Get Employee Employment Termination -const GetEmpEmploymentTerminationInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetEmpEmploymentTerminationInput = z.infer< - typeof GetEmpEmploymentTerminationInputSchema ->; - -const GetEmpEmploymentTerminationResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetEmpEmploymentTerminationResponse = z.infer< - typeof GetEmpEmploymentTerminationResponseSchema ->; - -// Get Work Order -const GetWorkOrderInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetWorkOrderInput = z.infer; - -const GetWorkOrderResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetWorkOrderResponse = z.infer; - -// Get Recurring Pay Components -const GetEmpPayCompRecurringInputSchema = z.object({ +const odataQuery = { filter: z.string().optional(), select: z.string().optional(), expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), + top: z.number().int().min(1).optional(), + skip: z.number().int().min(0).optional(), orderby: z.string().optional(), -}); -export type GetEmpPayCompRecurringInput = z.infer< - typeof GetEmpPayCompRecurringInputSchema ->; - -const GetEmpPayCompRecurringResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetEmpPayCompRecurringResponse = z.infer< - typeof GetEmpPayCompRecurringResponseSchema ->; - -// Get Non-Recurring Pay Components -const GetEmpPayCompNonRecurringInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetEmpPayCompNonRecurringInput = z.infer< - typeof GetEmpPayCompNonRecurringInputSchema ->; - -const GetEmpPayCompNonRecurringResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetEmpPayCompNonRecurringResponse = z.infer< - typeof GetEmpPayCompNonRecurringResponseSchema ->; - -// Get Goal Plan Template -const GetGoalPlanTemplateInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetGoalPlanTemplateInput = z.infer< - typeof GetGoalPlanTemplateInputSchema ->; - -const GetGoalPlanTemplateResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetGoalPlanTemplateResponse = z.infer< - typeof GetGoalPlanTemplateResponseSchema ->; - -// Get Goals By Plan -const GetGoalsByPlanInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), - goal_plan_id: z.string(), -}); -export type GetGoalsByPlanInput = z.infer; - -const GetGoalsByPlanResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetGoalsByPlanResponse = z.infer< - typeof GetGoalsByPlanResponseSchema ->; - -// Get Form Content -const GetFormContentInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetFormContentInput = z.infer; - -const GetFormContentResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetFormContentResponse = z.infer< - typeof GetFormContentResponseSchema ->; - -// Create Learning Activities Bulk -const CreateLearningActivitiesBulkInputSchema = z.object({ - body: z.record(z.string(), z.unknown()), -}); -export type CreateLearningActivitiesBulkInput = z.infer< - typeof CreateLearningActivitiesBulkInputSchema ->; - -const CreateLearningActivitiesBulkResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type CreateLearningActivitiesBulkResponse = z.infer< - typeof CreateLearningActivitiesBulkResponseSchema ->; - -// Get CDP Learning Metadata -const GetCdpLearningMetadataInputSchema = z.object({}).optional(); -export type GetCdpLearningMetadataInput = z.infer< - typeof GetCdpLearningMetadataInputSchema ->; - -const GetCdpLearningMetadataResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetCdpLearningMetadataResponse = z.infer< - typeof GetCdpLearningMetadataResponseSchema ->; - -// Refresh CDP Learning Metadata -const RefreshCdpLearningMetadataInputSchema = z.object({}).optional(); -export type RefreshCdpLearningMetadataInput = z.infer< - typeof RefreshCdpLearningMetadataInputSchema ->; - -const RefreshCdpLearningMetadataResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type RefreshCdpLearningMetadataResponse = z.infer< - typeof RefreshCdpLearningMetadataResponseSchema ->; - -// Get Employee Time -const GetEmployeeTimeInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetEmployeeTimeInput = z.infer; - -const GetEmployeeTimeResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetEmployeeTimeResponse = z.infer< - typeof GetEmployeeTimeResponseSchema ->; - -// Get Employee Timesheet -const GetEmployeeTimesheetInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetEmployeeTimesheetInput = z.infer< - typeof GetEmployeeTimesheetInputSchema ->; - -const GetEmployeeTimesheetResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetEmployeeTimesheetResponse = z.infer< - typeof GetEmployeeTimesheetResponseSchema ->; - -// Get Temporary Time Information -const GetTemporaryTimeInformationInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetTemporaryTimeInformationInput = z.infer< - typeof GetTemporaryTimeInformationInputSchema ->; - -const GetTemporaryTimeInformationResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetTemporaryTimeInformationResponse = z.infer< - typeof GetTemporaryTimeInformationResponseSchema ->; - -// Get Time Account Snapshot -const GetTimeAccountSnapshotInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type GetTimeAccountSnapshotInput = z.infer< - typeof GetTimeAccountSnapshotInputSchema ->; - -const GetTimeAccountSnapshotResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetTimeAccountSnapshotResponse = z.infer< - typeof GetTimeAccountSnapshotResponseSchema ->; - -// Get Clock In/Out Integration Metadata -const GetOdataMetadataClockInclockOutInputSchema = z.object({}).optional(); -export type GetOdataMetadataClockInclockOutInput = z.infer< - typeof GetOdataMetadataClockInclockOutInputSchema ->; +}; -const GetOdataMetadataClockInclockOutResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type GetOdataMetadataClockInclockOutResponse = z.infer< - typeof GetOdataMetadataClockInclockOutResponseSchema ->; +const ODataQuery = z.object(odataQuery); +const Empty = z.object({}).optional(); -// Query All Available Clock In/Clock Out Groups -const QueryAllAvailableClockClockOutInputSchema = z.object({ - filter: z.string().optional(), - select: z.string().optional(), - expand: z.string().optional(), - top: z.number().int().optional(), - skip: z.number().int().optional(), - orderby: z.string().optional(), -}); -export type QueryAllAvailableClockClockOutInput = z.infer< - typeof QueryAllAvailableClockClockOutInputSchema ->; +/** OData V2 `{ d }` and V4 `{ value }` plus metadata XML/JSON. */ +export const SapResponseSchema = z.union([ + z + .object({ + d: z.unknown().optional(), + value: z.array(z.unknown()).optional(), + }) + .passthrough(), + z.string(), + z.record(z.string(), z.unknown()), + z.null(), +]); -const QueryAllAvailableClockClockOutResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type QueryAllAvailableClockClockOutResponse = z.infer< - typeof QueryAllAvailableClockClockOutResponseSchema ->; +const Body = z.record(z.string(), z.unknown()).optional(); -// Query Clock In/Clock Out Group By Code -const QueryClockClockOutGroupCodeTimeInputSchema = z.object({ - code: z.string(), - select: z.string().optional(), - expand: z.string().optional(), +const FeedbackQuestion = z.object({ + question: z.string().min(1), + answer: z.string().max(4000).optional(), }); -export type QueryClockClockOutGroupCodeTimeInput = z.infer< - typeof QueryClockClockOutGroupCodeTimeInputSchema ->; - -const QueryClockClockOutGroupCodeTimeResponseSchema = z - .object({ - d: z - .object({ - results: z.array(z.unknown()).optional(), - id: z.string().optional(), - status: z.string().optional(), - }) - .catchall(z.unknown()) - .optional(), - }) - .passthrough(); -export type QueryClockClockOutGroupCodeTimeResponse = z.infer< - typeof QueryClockClockOutGroupCodeTimeResponseSchema ->; export const SapsuccessfactorsEndpointInputSchemas = { - approveCalibrationSession: ApproveCalibrationSessionInputSchema, - getCalibrationSessionById: GetCalibrationSessionByIdInputSchema, - getCalibrationSessions: GetCalibrationSessionsInputSchema, - getOdataMetadataCalibSessionService: - GetOdataMetadataCalibSessionServiceInputSchema, - getCalibrationSubjectById: GetCalibrationSubjectByIdInputSchema, - getCalibrationSubjectRatings: GetCalibrationSubjectRatingsInputSchema, - updateCalibrationSubjectRatings: UpdateCalibrationSubjectRatingsInputSchema, - createOnboardee: CreateOnboardeeInputSchema, - getOnb2Process: GetOnb2ProcessInputSchema, - getOdataMetadataOnboardingAddl: GetOdataMetadataOnboardingAddlInputSchema, - updateInternalUsernameNewHiresAfter: - UpdateInternalUsernameNewHiresAfterInputSchema, - createAFeedbackRequest: CreateAFeedbackRequestInputSchema, - getFeedbackRecordsServiceAvailable: - GetFeedbackRecordsServiceAvailableInputSchema, - getPendingFeedbackRequestsFeedback: - GetPendingFeedbackRequestsFeedbackInputSchema, - giveFeedbackOrRespondToAFeedbackRequest: - GiveFeedbackOrRespondToAFeedbackRequestInputSchema, - refreshMetadataContFeedbackService: - RefreshMetadataContFeedbackServiceInputSchema, - createUpdateSuccessorNomination: CreateUpdateSuccessorNominationInputSchema, - deleteNominationPositionTalentPool: - DeleteNominationPositionTalentPoolInputSchema, - getOdataMetadataForNominationService: - GetOdataMetadataForNominationServiceInputSchema, - getTalentPool: GetTalentPoolInputSchema, - getApplicationInterview: GetApplicationInterviewInputSchema, - getInterviewOverallAssessment: GetInterviewOverallAssessmentInputSchema, - getJobApplication: GetJobApplicationInputSchema, - getJobRequisition: GetJobRequisitionInputSchema, - getJobReqScreeningQuestion: GetJobReqScreeningQuestionInputSchema, - listCandidates: ListCandidatesInputSchema, - getFoBusinessUnit: GetFoBusinessUnitInputSchema, - getFoCompany: GetFoCompanyInputSchema, - getFoCostCenter: GetFoCostCenterInputSchema, - getFoDepartment: GetFoDepartmentInputSchema, - getFoJobCode: GetFoJobCodeInputSchema, - getFoJobFunction: GetFoJobFunctionInputSchema, - getFoLocation: GetFoLocationInputSchema, - getFoPayGroup: GetFoPayGroupInputSchema, - getPosition: GetPositionInputSchema, - getCustomMdfObject: GetCustomMdfObjectInputSchema, - getPicklist: GetPicklistInputSchema, - getPicklistOption: GetPicklistOptionInputSchema, - getCurrentUser: GetCurrentUserInputSchema, - getOdataUserMetadata: GetOdataUserMetadataInputSchema, - listUsers: ListUsersInputSchema, - getPerPersonById: GetPerPersonByIdInputSchema, - listPerPerson: ListPerPersonInputSchema, - getPerPersonal: GetPerPersonalInputSchema, - getBackgroundEducation: GetBackgroundEducationInputSchema, - getBackgroundMobility: GetBackgroundMobilityInputSchema, - listEmpEmployment: ListEmpEmploymentInputSchema, - getEmpEmploymentTermination: GetEmpEmploymentTerminationInputSchema, - getWorkOrder: GetWorkOrderInputSchema, - getEmpPayCompRecurring: GetEmpPayCompRecurringInputSchema, - getEmpPayCompNonRecurring: GetEmpPayCompNonRecurringInputSchema, - getGoalPlanTemplate: GetGoalPlanTemplateInputSchema, - getGoalsByPlan: GetGoalsByPlanInputSchema, - getFormContent: GetFormContentInputSchema, - createLearningActivitiesBulk: CreateLearningActivitiesBulkInputSchema, - getCdpLearningMetadata: GetCdpLearningMetadataInputSchema, - refreshCdpLearningMetadata: RefreshCdpLearningMetadataInputSchema, - getEmployeeTime: GetEmployeeTimeInputSchema, - getEmployeeTimesheet: GetEmployeeTimesheetInputSchema, - getTemporaryTimeInformation: GetTemporaryTimeInformationInputSchema, - getTimeAccountSnapshot: GetTimeAccountSnapshotInputSchema, - getOdataMetadataClockInclockOut: GetOdataMetadataClockInclockOutInputSchema, - queryAllAvailableClockClockOut: QueryAllAvailableClockClockOutInputSchema, - queryClockClockOutGroupCodeTime: QueryClockClockOutGroupCodeTimeInputSchema, + approveCalibrationSession: z.object({ session_id: z.string().min(1) }), + getCalibrationSessionById: z.object({ + session_id: z.string().min(1), + select: odataQuery.select, + expand: odataQuery.expand, + }), + getCalibrationSessions: ODataQuery, + getOdataMetadataCalibSessionService: Empty, + getCalibrationSubjectById: z.object({ + subject_id: z.string().min(1), + select: odataQuery.select, + expand: odataQuery.expand, + }), + getCalibrationSubjectRatings: ODataQuery.extend({ + session_id: z.string().min(1), + }), + updateCalibrationSubjectRatings: z.object({ + subject_id: z.string().min(1), + body: Body, + }), + createOnboardee: z.object({ + userId: z.string().min(1).optional(), + username: z.string().min(1).optional(), + status: z.string().optional(), + body: Body, + }), + getOnb2Process: ODataQuery, + getOdataMetadataOnboardingAddl: Empty, + updateInternalUsernameNewHiresAfter: z.object({ + userId: z.string().min(1).optional(), + user_id: z.string().min(1).optional(), + newUsername: z.string().min(1).optional(), + new_username: z.string().min(1).optional(), + }), + createAFeedbackRequest: z + .object({ + questions: z.array(FeedbackQuestion).min(1).max(3).optional(), + body: Body, + }) + .refine((v) => (v.questions?.length ?? 0) > 0 || v.body != null, { + message: 'At least one question must be provided', + }), + getFeedbackRecordsServiceAvailable: ODataQuery, + getPendingFeedbackRequestsFeedback: ODataQuery, + giveFeedbackOrRespondToAFeedbackRequest: z.object({ + questions: z.array(FeedbackQuestion).max(3).optional(), + body: Body, + }), + refreshMetadataContFeedbackService: Empty, + createUpdateSuccessorNomination: z.object({ + userId: z.string().optional(), + positionCode: z.string().optional(), + isPoolNomination: z.boolean().optional(), + body: Body, + }), + deleteNominationPositionTalentPool: z.object({ + nominationTargetId: z.string().min(1), + userId: z.string().min(1), + isPoolNomination: z.boolean().optional(), + }), + getOdataMetadataForNominationService: Empty, + getTalentPool: ODataQuery, + getApplicationInterview: z + .object({ + applicationId: z.string().min(1).optional(), + ...odataQuery, + }) + .refine((v) => Boolean(v.applicationId || v.filter), { + message: + 'applicationId (or $filter including applicationId) is required; Interview Central only scans the first 1000 rows', + }), + getInterviewOverallAssessment: ODataQuery, + getJobApplication: ODataQuery, + getJobRequisition: ODataQuery, + getJobReqScreeningQuestion: ODataQuery, + listCandidates: ODataQuery, + getFoBusinessUnit: ODataQuery, + getFoCompany: ODataQuery, + getFoCostCenter: ODataQuery, + getFoDepartment: ODataQuery, + getFoJobCode: ODataQuery, + getFoJobFunction: ODataQuery, + getFoLocation: ODataQuery, + getFoPayGroup: ODataQuery, + getPosition: ODataQuery, + getCustomMdfObject: ODataQuery.extend({ + custom_object: z + .string() + .regex( + /^cust_[A-Za-z0-9_]+$/, + 'custom_object must be a cust_* MDF entity name', + ), + }), + getPicklist: ODataQuery, + getPicklistOption: ODataQuery, + getCurrentUser: ODataQuery, + getOdataUserMetadata: Empty, + listUsers: ODataQuery, + getPerPersonById: z.object({ + person_id_external: z.string().min(1), + select: odataQuery.select, + expand: odataQuery.expand, + }), + listPerPerson: ODataQuery, + getPerPersonal: ODataQuery, + getBackgroundEducation: ODataQuery, + getBackgroundMobility: ODataQuery, + listEmpEmployment: ODataQuery, + getEmpEmploymentTermination: ODataQuery, + getWorkOrder: ODataQuery, + getEmpPayCompRecurring: ODataQuery, + getEmpPayCompNonRecurring: ODataQuery, + getGoalPlanTemplate: ODataQuery, + getGoalsByPlan: ODataQuery.extend({ + goal_plan_id: z.string().min(1), + }), + getFormContent: ODataQuery, + createLearningActivitiesBulk: z.object({ body: Body }), + getCdpLearningMetadata: Empty, + refreshCdpLearningMetadata: Empty, + getEmployeeTime: ODataQuery, + getEmployeeTimesheet: ODataQuery, + getTemporaryTimeInformation: ODataQuery, + getTimeAccountSnapshot: ODataQuery, + getOdataMetadataClockInclockOut: Empty, + queryAllAvailableClockClockOut: ODataQuery, + queryClockClockOutGroupCodeTime: z.object({ + code: z.string().min(1), + expand: odataQuery.expand, + select: odataQuery.select, + }), } as const; export type SapsuccessfactorsEndpointInputs = { @@ -1761,87 +172,21 @@ export type SapsuccessfactorsEndpointInputs = { >; }; -export const SapsuccessfactorsEndpointOutputSchemas = { - approveCalibrationSession: ApproveCalibrationSessionResponseSchema, - getCalibrationSessionById: GetCalibrationSessionByIdResponseSchema, - getCalibrationSessions: GetCalibrationSessionsResponseSchema, - getOdataMetadataCalibSessionService: - GetOdataMetadataCalibSessionServiceResponseSchema, - getCalibrationSubjectById: GetCalibrationSubjectByIdResponseSchema, - getCalibrationSubjectRatings: GetCalibrationSubjectRatingsResponseSchema, - updateCalibrationSubjectRatings: - UpdateCalibrationSubjectRatingsResponseSchema, - createOnboardee: CreateOnboardeeResponseSchema, - getOnb2Process: GetOnb2ProcessResponseSchema, - getOdataMetadataOnboardingAddl: GetOdataMetadataOnboardingAddlResponseSchema, - updateInternalUsernameNewHiresAfter: - UpdateInternalUsernameNewHiresAfterResponseSchema, - createAFeedbackRequest: CreateAFeedbackRequestResponseSchema, - getFeedbackRecordsServiceAvailable: - GetFeedbackRecordsServiceAvailableResponseSchema, - getPendingFeedbackRequestsFeedback: - GetPendingFeedbackRequestsFeedbackResponseSchema, - giveFeedbackOrRespondToAFeedbackRequest: - GiveFeedbackOrRespondToAFeedbackRequestResponseSchema, - refreshMetadataContFeedbackService: - RefreshMetadataContFeedbackServiceResponseSchema, - createUpdateSuccessorNomination: - CreateUpdateSuccessorNominationResponseSchema, - deleteNominationPositionTalentPool: - DeleteNominationPositionTalentPoolResponseSchema, - getOdataMetadataForNominationService: - GetOdataMetadataForNominationServiceResponseSchema, - getTalentPool: GetTalentPoolResponseSchema, - getApplicationInterview: GetApplicationInterviewResponseSchema, - getInterviewOverallAssessment: GetInterviewOverallAssessmentResponseSchema, - getJobApplication: GetJobApplicationResponseSchema, - getJobRequisition: GetJobRequisitionResponseSchema, - getJobReqScreeningQuestion: GetJobReqScreeningQuestionResponseSchema, - listCandidates: ListCandidatesResponseSchema, - getFoBusinessUnit: GetFoBusinessUnitResponseSchema, - getFoCompany: GetFoCompanyResponseSchema, - getFoCostCenter: GetFoCostCenterResponseSchema, - getFoDepartment: GetFoDepartmentResponseSchema, - getFoJobCode: GetFoJobCodeResponseSchema, - getFoJobFunction: GetFoJobFunctionResponseSchema, - getFoLocation: GetFoLocationResponseSchema, - getFoPayGroup: GetFoPayGroupResponseSchema, - getPosition: GetPositionResponseSchema, - getCustomMdfObject: GetCustomMdfObjectResponseSchema, - getPicklist: GetPicklistResponseSchema, - getPicklistOption: GetPicklistOptionResponseSchema, - getCurrentUser: GetCurrentUserResponseSchema, - getOdataUserMetadata: GetOdataUserMetadataResponseSchema, - listUsers: ListUsersResponseSchema, - getPerPersonById: GetPerPersonByIdResponseSchema, - listPerPerson: ListPerPersonResponseSchema, - getPerPersonal: GetPerPersonalResponseSchema, - getBackgroundEducation: GetBackgroundEducationResponseSchema, - getBackgroundMobility: GetBackgroundMobilityResponseSchema, - listEmpEmployment: ListEmpEmploymentResponseSchema, - getEmpEmploymentTermination: GetEmpEmploymentTerminationResponseSchema, - getWorkOrder: GetWorkOrderResponseSchema, - getEmpPayCompRecurring: GetEmpPayCompRecurringResponseSchema, - getEmpPayCompNonRecurring: GetEmpPayCompNonRecurringResponseSchema, - getGoalPlanTemplate: GetGoalPlanTemplateResponseSchema, - getGoalsByPlan: GetGoalsByPlanResponseSchema, - getFormContent: GetFormContentResponseSchema, - createLearningActivitiesBulk: CreateLearningActivitiesBulkResponseSchema, - getCdpLearningMetadata: GetCdpLearningMetadataResponseSchema, - refreshCdpLearningMetadata: RefreshCdpLearningMetadataResponseSchema, - getEmployeeTime: GetEmployeeTimeResponseSchema, - getEmployeeTimesheet: GetEmployeeTimesheetResponseSchema, - getTemporaryTimeInformation: GetTemporaryTimeInformationResponseSchema, - getTimeAccountSnapshot: GetTimeAccountSnapshotResponseSchema, - getOdataMetadataClockInclockOut: - GetOdataMetadataClockInclockOutResponseSchema, - queryAllAvailableClockClockOut: QueryAllAvailableClockClockOutResponseSchema, - queryClockClockOutGroupCodeTime: - QueryClockClockOutGroupCodeTimeResponseSchema, -} as const; +export const SapsuccessfactorsEndpointOutputSchemas = Object.fromEntries( + Object.keys(SapsuccessfactorsEndpointInputSchemas).map((key) => [ + key, + SapResponseSchema, + ]), +) as { + [K in keyof typeof SapsuccessfactorsEndpointInputSchemas]: typeof SapResponseSchema; +}; export type SapsuccessfactorsEndpointOutputs = { [K in keyof typeof SapsuccessfactorsEndpointOutputSchemas]: z.infer< (typeof SapsuccessfactorsEndpointOutputSchemas)[K] >; }; + +export type SapsuccessfactorsEndpointInput = + SapsuccessfactorsEndpointInputs[keyof SapsuccessfactorsEndpointInputs] & + Record; diff --git a/packages/sapsuccessfactors/endpoints/users.ts b/packages/sapsuccessfactors/endpoints/users.ts deleted file mode 100644 index d3e11a953..000000000 --- a/packages/sapsuccessfactors/endpoints/users.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// List Users -// Retrieve a list of all employee users. -export const listUsers: SapsuccessfactorsEndpoints['listUsers'] = async ( - ctx, - input, -) => { - const validatedInput = SapsuccessfactorsEndpointInputSchemas.listUsers.parse( - input ?? {}, - ); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['listUsers'] - >('odata/v2/User', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.listUsers.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.users.listUsers', - input ?? {}, - 'completed', - ); - return validatedResponse; -}; diff --git a/packages/sapsuccessfactors/endpoints/work.ts b/packages/sapsuccessfactors/endpoints/work.ts deleted file mode 100644 index 2ba669a8b..000000000 --- a/packages/sapsuccessfactors/endpoints/work.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { logEventFromContext } from 'corsair/core'; -import type { SapsuccessfactorsEndpoints } from '..'; -import { makeSapsuccessfactorsRequest } from '../client'; -import type { SapsuccessfactorsEndpointOutputs } from './types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './types'; - -// Get Work Order -// Retrieve work order records for contingent worker management. -export const getWorkOrder: SapsuccessfactorsEndpoints['getWorkOrder'] = async ( - ctx, - input, -) => { - const validatedInput = - SapsuccessfactorsEndpointInputSchemas.getWorkOrder.parse(input ?? {}); - const apiBaseUrl = - (ctx as any)?.options?.apiBaseUrl ?? (ctx as any)?.options?.baseUrl; - const query = validatedInput as Record< - string, - string | number | boolean | undefined - >; - const response = await makeSapsuccessfactorsRequest< - SapsuccessfactorsEndpointOutputs['getWorkOrder'] - >('odata/v2/WorkOrder', ctx.key, { method: 'GET', query, apiBaseUrl }); - const validatedResponse = - SapsuccessfactorsEndpointOutputSchemas.getWorkOrder.parse(response); - await logEventFromContext( - ctx, - 'sapsuccessfactors.work.getWorkOrder', - input ?? {}, - 'completed', - ); - return validatedResponse; -}; diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts index dcdb2395d..5d44c12e3 100644 --- a/packages/sapsuccessfactors/index.ts +++ b/packages/sapsuccessfactors/index.ts @@ -1,69 +1,56 @@ import type { + AuthTypes, BindEndpoints, - BindWebhooks, CorsairEndpoint, CorsairErrorHandler, CorsairPlugin, CorsairPluginContext, KeyBuilderContext, + PickAuth, + PluginAuthConfig, PluginPermissionsConfig, RequiredPluginEndpointMeta, + RequiredPluginEndpointSchemas, } from 'corsair/core'; +import { AuthMissingError } from 'corsair/core'; import { - A, - Application, - Approve, - Background, - Calibration, - Candidates, - Cdp, - Current, - Custom, - Emp, - Employee, - Feedback, - Fo, - Form, - Give, - Goal, - Goals, - Internal, - Interview, - Job, - Learning, - Metadata, - Nomination, - Odata, - Onb2, - Onboardee, - Pending, - Per, - Picklist, - Position, - Query, - Successor, - Talent, - Temporary, - Time, - Users, - Work, + normalizeSapsuccessfactorsHost, + SAP_SUCCESSFACTORS_DEFAULT_HOST, + sapSuccessfactorsOAuthUrls, +} from './client'; +import { + SapsuccessfactorsEndpointInputSchemas, + SapsuccessfactorsEndpointOutputSchemas, + sapRoutes, + sapsuccessfactorsEndpointsNested, } from './endpoints'; import type { SapsuccessfactorsEndpointInputs, SapsuccessfactorsEndpointOutputs, } from './endpoints/types'; -import { - SapsuccessfactorsEndpointInputSchemas, - SapsuccessfactorsEndpointOutputSchemas, -} from './endpoints/types'; +import { errorHandlers } from './error-handlers'; import { SapsuccessfactorsSchema } from './schema'; +export const sapsuccessfactorsAuthConfig = { + api_key: { + account: ['host', 'company_id'] as const, + }, + oauth_2: { + account: ['host', 'company_id'] as const, + }, +} as const satisfies PluginAuthConfig; + export type SapsuccessfactorsPluginOptions = { - /** Cloud-based human capital management software covering Employee Central, Recruiting, Performance & Goals, Learning, Compensation, and more. */ + authType?: PickAuth<'api_key' | 'oauth_2'>; + /** Bearer token or `Basic …` (tests / BYO). */ key?: string; + /** API hostname, e.g. api10.successfactors.com */ + host?: string; + /** Alias for host (older option name). */ apiBaseUrl?: string; + /** SuccessFactors company ID (OAuth token request). */ + companyId?: string; hooks?: InternalSapsuccessfactorsPlugin['hooks']; - webhookHooks?: InternalSapsuccessfactorsPlugin['webhookHooks']; errorHandlers?: CorsairErrorHandler; permissions?: PluginPermissionsConfig< typeof sapsuccessfactorsEndpointsNested @@ -72,10 +59,14 @@ export type SapsuccessfactorsPluginOptions = { export type SapsuccessfactorsContext = CorsairPluginContext< typeof SapsuccessfactorsSchema, - SapsuccessfactorsPluginOptions + SapsuccessfactorsPluginOptions, + undefined, + typeof sapsuccessfactorsAuthConfig +>; +export type SapsuccessfactorsKeyBuilderContext = KeyBuilderContext< + SapsuccessfactorsPluginOptions, + typeof sapsuccessfactorsAuthConfig >; -export type SapsuccessfactorsKeyBuilderContext = - KeyBuilderContext; export type SapsuccessfactorsBoundEndpoints = BindEndpoints< typeof sapsuccessfactorsEndpointsNested >; @@ -89,775 +80,37 @@ type SapsuccessfactorsEndpoint< >; export type SapsuccessfactorsEndpoints = { - approveCalibrationSession: SapsuccessfactorsEndpoint<'approveCalibrationSession'>; - getCalibrationSessionById: SapsuccessfactorsEndpoint<'getCalibrationSessionById'>; - getCalibrationSessions: SapsuccessfactorsEndpoint<'getCalibrationSessions'>; - getCalibrationSubjectById: SapsuccessfactorsEndpoint<'getCalibrationSubjectById'>; - getCalibrationSubjectRatings: SapsuccessfactorsEndpoint<'getCalibrationSubjectRatings'>; - updateCalibrationSubjectRatings: SapsuccessfactorsEndpoint<'updateCalibrationSubjectRatings'>; - getOdataMetadataCalibSessionService: SapsuccessfactorsEndpoint<'getOdataMetadataCalibSessionService'>; - getOdataMetadataOnboardingAddl: SapsuccessfactorsEndpoint<'getOdataMetadataOnboardingAddl'>; - getOdataMetadataForNominationService: SapsuccessfactorsEndpoint<'getOdataMetadataForNominationService'>; - getOdataUserMetadata: SapsuccessfactorsEndpoint<'getOdataUserMetadata'>; - getOdataMetadataClockInclockOut: SapsuccessfactorsEndpoint<'getOdataMetadataClockInclockOut'>; - createOnboardee: SapsuccessfactorsEndpoint<'createOnboardee'>; - getOnb2Process: SapsuccessfactorsEndpoint<'getOnb2Process'>; - updateInternalUsernameNewHiresAfter: SapsuccessfactorsEndpoint<'updateInternalUsernameNewHiresAfter'>; - createAFeedbackRequest: SapsuccessfactorsEndpoint<'createAFeedbackRequest'>; - getFeedbackRecordsServiceAvailable: SapsuccessfactorsEndpoint<'getFeedbackRecordsServiceAvailable'>; - getPendingFeedbackRequestsFeedback: SapsuccessfactorsEndpoint<'getPendingFeedbackRequestsFeedback'>; - giveFeedbackOrRespondToAFeedbackRequest: SapsuccessfactorsEndpoint<'giveFeedbackOrRespondToAFeedbackRequest'>; - refreshMetadataContFeedbackService: SapsuccessfactorsEndpoint<'refreshMetadataContFeedbackService'>; - createUpdateSuccessorNomination: SapsuccessfactorsEndpoint<'createUpdateSuccessorNomination'>; - deleteNominationPositionTalentPool: SapsuccessfactorsEndpoint<'deleteNominationPositionTalentPool'>; - getTalentPool: SapsuccessfactorsEndpoint<'getTalentPool'>; - getApplicationInterview: SapsuccessfactorsEndpoint<'getApplicationInterview'>; - getInterviewOverallAssessment: SapsuccessfactorsEndpoint<'getInterviewOverallAssessment'>; - getJobApplication: SapsuccessfactorsEndpoint<'getJobApplication'>; - getJobRequisition: SapsuccessfactorsEndpoint<'getJobRequisition'>; - getJobReqScreeningQuestion: SapsuccessfactorsEndpoint<'getJobReqScreeningQuestion'>; - listCandidates: SapsuccessfactorsEndpoint<'listCandidates'>; - getFoBusinessUnit: SapsuccessfactorsEndpoint<'getFoBusinessUnit'>; - getFoCompany: SapsuccessfactorsEndpoint<'getFoCompany'>; - getFoCostCenter: SapsuccessfactorsEndpoint<'getFoCostCenter'>; - getFoDepartment: SapsuccessfactorsEndpoint<'getFoDepartment'>; - getFoJobCode: SapsuccessfactorsEndpoint<'getFoJobCode'>; - getFoJobFunction: SapsuccessfactorsEndpoint<'getFoJobFunction'>; - getFoLocation: SapsuccessfactorsEndpoint<'getFoLocation'>; - getFoPayGroup: SapsuccessfactorsEndpoint<'getFoPayGroup'>; - getPosition: SapsuccessfactorsEndpoint<'getPosition'>; - getCustomMdfObject: SapsuccessfactorsEndpoint<'getCustomMdfObject'>; - getPicklist: SapsuccessfactorsEndpoint<'getPicklist'>; - getPicklistOption: SapsuccessfactorsEndpoint<'getPicklistOption'>; - getCurrentUser: SapsuccessfactorsEndpoint<'getCurrentUser'>; - listUsers: SapsuccessfactorsEndpoint<'listUsers'>; - getPerPersonById: SapsuccessfactorsEndpoint<'getPerPersonById'>; - listPerPerson: SapsuccessfactorsEndpoint<'listPerPerson'>; - getPerPersonal: SapsuccessfactorsEndpoint<'getPerPersonal'>; - getBackgroundEducation: SapsuccessfactorsEndpoint<'getBackgroundEducation'>; - getBackgroundMobility: SapsuccessfactorsEndpoint<'getBackgroundMobility'>; - listEmpEmployment: SapsuccessfactorsEndpoint<'listEmpEmployment'>; - getEmpEmploymentTermination: SapsuccessfactorsEndpoint<'getEmpEmploymentTermination'>; - getEmpPayCompRecurring: SapsuccessfactorsEndpoint<'getEmpPayCompRecurring'>; - getEmpPayCompNonRecurring: SapsuccessfactorsEndpoint<'getEmpPayCompNonRecurring'>; - getWorkOrder: SapsuccessfactorsEndpoint<'getWorkOrder'>; - getGoalPlanTemplate: SapsuccessfactorsEndpoint<'getGoalPlanTemplate'>; - getGoalsByPlan: SapsuccessfactorsEndpoint<'getGoalsByPlan'>; - getFormContent: SapsuccessfactorsEndpoint<'getFormContent'>; - createLearningActivitiesBulk: SapsuccessfactorsEndpoint<'createLearningActivitiesBulk'>; - getCdpLearningMetadata: SapsuccessfactorsEndpoint<'getCdpLearningMetadata'>; - refreshCdpLearningMetadata: SapsuccessfactorsEndpoint<'refreshCdpLearningMetadata'>; - getEmployeeTime: SapsuccessfactorsEndpoint<'getEmployeeTime'>; - getEmployeeTimesheet: SapsuccessfactorsEndpoint<'getEmployeeTimesheet'>; - getTemporaryTimeInformation: SapsuccessfactorsEndpoint<'getTemporaryTimeInformation'>; - getTimeAccountSnapshot: SapsuccessfactorsEndpoint<'getTimeAccountSnapshot'>; - queryAllAvailableClockClockOut: SapsuccessfactorsEndpoint<'queryAllAvailableClockClockOut'>; - queryClockClockOutGroupCodeTime: SapsuccessfactorsEndpoint<'queryClockClockOutGroupCodeTime'>; + [K in keyof SapsuccessfactorsEndpointOutputs]: SapsuccessfactorsEndpoint; }; -export type SapsuccessfactorsBoundWebhooks = BindWebhooks< - Record +const sapsuccessfactorsEndpointSchemas = Object.fromEntries( + sapRoutes.map((route) => [ + `${route.group}.${route.name}`, + { + input: SapsuccessfactorsEndpointInputSchemas[route.name], + output: SapsuccessfactorsEndpointOutputSchemas[route.name], + }, + ]), +) as unknown as RequiredPluginEndpointSchemas< + typeof sapsuccessfactorsEndpointsNested >; -const sapsuccessfactorsEndpointsNested = { - approve: { - approveCalibrationSession: Approve.approveCalibrationSession, - }, - calibration: { - getCalibrationSessionById: Calibration.getCalibrationSessionById, - getCalibrationSessions: Calibration.getCalibrationSessions, - getCalibrationSubjectById: Calibration.getCalibrationSubjectById, - getCalibrationSubjectRatings: Calibration.getCalibrationSubjectRatings, - updateCalibrationSubjectRatings: - Calibration.updateCalibrationSubjectRatings, - }, - odata: { - getOdataMetadataCalibSessionService: - Odata.getOdataMetadataCalibSessionService, - getOdataMetadataOnboardingAddl: Odata.getOdataMetadataOnboardingAddl, - getOdataMetadataForNominationService: - Odata.getOdataMetadataForNominationService, - getOdataUserMetadata: Odata.getOdataUserMetadata, - getOdataMetadataClockInclockOut: Odata.getOdataMetadataClockInclockOut, - }, - onboardee: { - createOnboardee: Onboardee.createOnboardee, - }, - onb2: { - getOnb2Process: Onb2.getOnb2Process, - }, - internal: { - updateInternalUsernameNewHiresAfter: - Internal.updateInternalUsernameNewHiresAfter, - }, - a: { - createAFeedbackRequest: A.createAFeedbackRequest, - }, - feedback: { - getFeedbackRecordsServiceAvailable: - Feedback.getFeedbackRecordsServiceAvailable, - }, - pending: { - getPendingFeedbackRequestsFeedback: - Pending.getPendingFeedbackRequestsFeedback, - }, - give: { - giveFeedbackOrRespondToAFeedbackRequest: - Give.giveFeedbackOrRespondToAFeedbackRequest, - }, - metadata: { - refreshMetadataContFeedbackService: - Metadata.refreshMetadataContFeedbackService, - }, - successor: { - createUpdateSuccessorNomination: Successor.createUpdateSuccessorNomination, - }, - nomination: { - deleteNominationPositionTalentPool: - Nomination.deleteNominationPositionTalentPool, - }, - talent: { - getTalentPool: Talent.getTalentPool, - }, - application: { - getApplicationInterview: Application.getApplicationInterview, - }, - interview: { - getInterviewOverallAssessment: Interview.getInterviewOverallAssessment, - }, - job: { - getJobApplication: Job.getJobApplication, - getJobRequisition: Job.getJobRequisition, - getJobReqScreeningQuestion: Job.getJobReqScreeningQuestion, - }, - candidates: { - listCandidates: Candidates.listCandidates, - }, - fo: { - getFoBusinessUnit: Fo.getFoBusinessUnit, - getFoCompany: Fo.getFoCompany, - getFoCostCenter: Fo.getFoCostCenter, - getFoDepartment: Fo.getFoDepartment, - getFoJobCode: Fo.getFoJobCode, - getFoJobFunction: Fo.getFoJobFunction, - getFoLocation: Fo.getFoLocation, - getFoPayGroup: Fo.getFoPayGroup, - }, - position: { - getPosition: Position.getPosition, - }, - custom: { - getCustomMdfObject: Custom.getCustomMdfObject, - }, - picklist: { - getPicklist: Picklist.getPicklist, - getPicklistOption: Picklist.getPicklistOption, - }, - current: { - getCurrentUser: Current.getCurrentUser, - }, - users: { - listUsers: Users.listUsers, - }, - per: { - getPerPersonById: Per.getPerPersonById, - listPerPerson: Per.listPerPerson, - getPerPersonal: Per.getPerPersonal, - }, - background: { - getBackgroundEducation: Background.getBackgroundEducation, - getBackgroundMobility: Background.getBackgroundMobility, - }, - emp: { - listEmpEmployment: Emp.listEmpEmployment, - getEmpEmploymentTermination: Emp.getEmpEmploymentTermination, - getEmpPayCompRecurring: Emp.getEmpPayCompRecurring, - getEmpPayCompNonRecurring: Emp.getEmpPayCompNonRecurring, - }, - work: { - getWorkOrder: Work.getWorkOrder, - }, - goal: { - getGoalPlanTemplate: Goal.getGoalPlanTemplate, - }, - goals: { - getGoalsByPlan: Goals.getGoalsByPlan, - }, - form: { - getFormContent: Form.getFormContent, - }, - learning: { - createLearningActivitiesBulk: Learning.createLearningActivitiesBulk, - }, - cdp: { - getCdpLearningMetadata: Cdp.getCdpLearningMetadata, - refreshCdpLearningMetadata: Cdp.refreshCdpLearningMetadata, - }, - employee: { - getEmployeeTime: Employee.getEmployeeTime, - getEmployeeTimesheet: Employee.getEmployeeTimesheet, - }, - temporary: { - getTemporaryTimeInformation: Temporary.getTemporaryTimeInformation, - }, - time: { - getTimeAccountSnapshot: Time.getTimeAccountSnapshot, - }, - query: { - queryAllAvailableClockClockOut: Query.queryAllAvailableClockClockOut, - queryClockClockOutGroupCodeTime: Query.queryClockClockOutGroupCodeTime, - }, -} as const; - -// SAP SuccessFactors webhook/event subscriptions are not implemented in this -// provider yet; this integration currently exposes REST/OData endpoints only. -const sapsuccessfactorsWebhooksNested = {} as const; - -export const sapsuccessfactorsEndpointSchemas = { - 'approve.approveCalibrationSession': { - input: SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession, - output: SapsuccessfactorsEndpointOutputSchemas.approveCalibrationSession, - }, - 'calibration.getCalibrationSessionById': { - input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSessionById, - output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessionById, - }, - 'calibration.getCalibrationSessions': { - input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSessions, - output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSessions, - }, - 'calibration.getCalibrationSubjectById': { - input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectById, - output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectById, - }, - 'calibration.getCalibrationSubjectRatings': { - input: SapsuccessfactorsEndpointInputSchemas.getCalibrationSubjectRatings, - output: SapsuccessfactorsEndpointOutputSchemas.getCalibrationSubjectRatings, - }, - 'calibration.updateCalibrationSubjectRatings': { - input: - SapsuccessfactorsEndpointInputSchemas.updateCalibrationSubjectRatings, - output: - SapsuccessfactorsEndpointOutputSchemas.updateCalibrationSubjectRatings, - }, - 'odata.getOdataMetadataCalibSessionService': { - input: - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataCalibSessionService, - output: - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataCalibSessionService, - }, - 'odata.getOdataMetadataOnboardingAddl': { - input: SapsuccessfactorsEndpointInputSchemas.getOdataMetadataOnboardingAddl, - output: - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataOnboardingAddl, - }, - 'odata.getOdataMetadataForNominationService': { - input: - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataForNominationService, - output: - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataForNominationService, - }, - 'odata.getOdataUserMetadata': { - input: SapsuccessfactorsEndpointInputSchemas.getOdataUserMetadata, - output: SapsuccessfactorsEndpointOutputSchemas.getOdataUserMetadata, - }, - 'odata.getOdataMetadataClockInclockOut': { - input: - SapsuccessfactorsEndpointInputSchemas.getOdataMetadataClockInclockOut, - output: - SapsuccessfactorsEndpointOutputSchemas.getOdataMetadataClockInclockOut, - }, - 'onboardee.createOnboardee': { - input: SapsuccessfactorsEndpointInputSchemas.createOnboardee, - output: SapsuccessfactorsEndpointOutputSchemas.createOnboardee, - }, - 'onb2.getOnb2Process': { - input: SapsuccessfactorsEndpointInputSchemas.getOnb2Process, - output: SapsuccessfactorsEndpointOutputSchemas.getOnb2Process, - }, - 'internal.updateInternalUsernameNewHiresAfter': { - input: - SapsuccessfactorsEndpointInputSchemas.updateInternalUsernameNewHiresAfter, - output: - SapsuccessfactorsEndpointOutputSchemas.updateInternalUsernameNewHiresAfter, - }, - 'a.createAFeedbackRequest': { - input: SapsuccessfactorsEndpointInputSchemas.createAFeedbackRequest, - output: SapsuccessfactorsEndpointOutputSchemas.createAFeedbackRequest, - }, - 'feedback.getFeedbackRecordsServiceAvailable': { - input: - SapsuccessfactorsEndpointInputSchemas.getFeedbackRecordsServiceAvailable, - output: - SapsuccessfactorsEndpointOutputSchemas.getFeedbackRecordsServiceAvailable, - }, - 'pending.getPendingFeedbackRequestsFeedback': { - input: - SapsuccessfactorsEndpointInputSchemas.getPendingFeedbackRequestsFeedback, - output: - SapsuccessfactorsEndpointOutputSchemas.getPendingFeedbackRequestsFeedback, - }, - 'give.giveFeedbackOrRespondToAFeedbackRequest': { - input: - SapsuccessfactorsEndpointInputSchemas.giveFeedbackOrRespondToAFeedbackRequest, - output: - SapsuccessfactorsEndpointOutputSchemas.giveFeedbackOrRespondToAFeedbackRequest, - }, - 'metadata.refreshMetadataContFeedbackService': { - input: - SapsuccessfactorsEndpointInputSchemas.refreshMetadataContFeedbackService, - output: - SapsuccessfactorsEndpointOutputSchemas.refreshMetadataContFeedbackService, - }, - 'successor.createUpdateSuccessorNomination': { - input: - SapsuccessfactorsEndpointInputSchemas.createUpdateSuccessorNomination, - output: - SapsuccessfactorsEndpointOutputSchemas.createUpdateSuccessorNomination, - }, - 'nomination.deleteNominationPositionTalentPool': { - input: - SapsuccessfactorsEndpointInputSchemas.deleteNominationPositionTalentPool, - output: - SapsuccessfactorsEndpointOutputSchemas.deleteNominationPositionTalentPool, - }, - 'talent.getTalentPool': { - input: SapsuccessfactorsEndpointInputSchemas.getTalentPool, - output: SapsuccessfactorsEndpointOutputSchemas.getTalentPool, - }, - 'application.getApplicationInterview': { - input: SapsuccessfactorsEndpointInputSchemas.getApplicationInterview, - output: SapsuccessfactorsEndpointOutputSchemas.getApplicationInterview, - }, - 'interview.getInterviewOverallAssessment': { - input: SapsuccessfactorsEndpointInputSchemas.getInterviewOverallAssessment, - output: - SapsuccessfactorsEndpointOutputSchemas.getInterviewOverallAssessment, - }, - 'job.getJobApplication': { - input: SapsuccessfactorsEndpointInputSchemas.getJobApplication, - output: SapsuccessfactorsEndpointOutputSchemas.getJobApplication, - }, - 'job.getJobRequisition': { - input: SapsuccessfactorsEndpointInputSchemas.getJobRequisition, - output: SapsuccessfactorsEndpointOutputSchemas.getJobRequisition, - }, - 'job.getJobReqScreeningQuestion': { - input: SapsuccessfactorsEndpointInputSchemas.getJobReqScreeningQuestion, - output: SapsuccessfactorsEndpointOutputSchemas.getJobReqScreeningQuestion, - }, - 'candidates.listCandidates': { - input: SapsuccessfactorsEndpointInputSchemas.listCandidates, - output: SapsuccessfactorsEndpointOutputSchemas.listCandidates, - }, - 'fo.getFoBusinessUnit': { - input: SapsuccessfactorsEndpointInputSchemas.getFoBusinessUnit, - output: SapsuccessfactorsEndpointOutputSchemas.getFoBusinessUnit, - }, - 'fo.getFoCompany': { - input: SapsuccessfactorsEndpointInputSchemas.getFoCompany, - output: SapsuccessfactorsEndpointOutputSchemas.getFoCompany, - }, - 'fo.getFoCostCenter': { - input: SapsuccessfactorsEndpointInputSchemas.getFoCostCenter, - output: SapsuccessfactorsEndpointOutputSchemas.getFoCostCenter, - }, - 'fo.getFoDepartment': { - input: SapsuccessfactorsEndpointInputSchemas.getFoDepartment, - output: SapsuccessfactorsEndpointOutputSchemas.getFoDepartment, - }, - 'fo.getFoJobCode': { - input: SapsuccessfactorsEndpointInputSchemas.getFoJobCode, - output: SapsuccessfactorsEndpointOutputSchemas.getFoJobCode, - }, - 'fo.getFoJobFunction': { - input: SapsuccessfactorsEndpointInputSchemas.getFoJobFunction, - output: SapsuccessfactorsEndpointOutputSchemas.getFoJobFunction, - }, - 'fo.getFoLocation': { - input: SapsuccessfactorsEndpointInputSchemas.getFoLocation, - output: SapsuccessfactorsEndpointOutputSchemas.getFoLocation, - }, - 'fo.getFoPayGroup': { - input: SapsuccessfactorsEndpointInputSchemas.getFoPayGroup, - output: SapsuccessfactorsEndpointOutputSchemas.getFoPayGroup, - }, - 'position.getPosition': { - input: SapsuccessfactorsEndpointInputSchemas.getPosition, - output: SapsuccessfactorsEndpointOutputSchemas.getPosition, - }, - 'custom.getCustomMdfObject': { - input: SapsuccessfactorsEndpointInputSchemas.getCustomMdfObject, - output: SapsuccessfactorsEndpointOutputSchemas.getCustomMdfObject, - }, - 'picklist.getPicklist': { - input: SapsuccessfactorsEndpointInputSchemas.getPicklist, - output: SapsuccessfactorsEndpointOutputSchemas.getPicklist, - }, - 'picklist.getPicklistOption': { - input: SapsuccessfactorsEndpointInputSchemas.getPicklistOption, - output: SapsuccessfactorsEndpointOutputSchemas.getPicklistOption, - }, - 'current.getCurrentUser': { - input: SapsuccessfactorsEndpointInputSchemas.getCurrentUser, - output: SapsuccessfactorsEndpointOutputSchemas.getCurrentUser, - }, - 'users.listUsers': { - input: SapsuccessfactorsEndpointInputSchemas.listUsers, - output: SapsuccessfactorsEndpointOutputSchemas.listUsers, - }, - 'per.getPerPersonById': { - input: SapsuccessfactorsEndpointInputSchemas.getPerPersonById, - output: SapsuccessfactorsEndpointOutputSchemas.getPerPersonById, - }, - 'per.listPerPerson': { - input: SapsuccessfactorsEndpointInputSchemas.listPerPerson, - output: SapsuccessfactorsEndpointOutputSchemas.listPerPerson, - }, - 'per.getPerPersonal': { - input: SapsuccessfactorsEndpointInputSchemas.getPerPersonal, - output: SapsuccessfactorsEndpointOutputSchemas.getPerPersonal, - }, - 'background.getBackgroundEducation': { - input: SapsuccessfactorsEndpointInputSchemas.getBackgroundEducation, - output: SapsuccessfactorsEndpointOutputSchemas.getBackgroundEducation, - }, - 'background.getBackgroundMobility': { - input: SapsuccessfactorsEndpointInputSchemas.getBackgroundMobility, - output: SapsuccessfactorsEndpointOutputSchemas.getBackgroundMobility, - }, - 'emp.listEmpEmployment': { - input: SapsuccessfactorsEndpointInputSchemas.listEmpEmployment, - output: SapsuccessfactorsEndpointOutputSchemas.listEmpEmployment, - }, - 'emp.getEmpEmploymentTermination': { - input: SapsuccessfactorsEndpointInputSchemas.getEmpEmploymentTermination, - output: SapsuccessfactorsEndpointOutputSchemas.getEmpEmploymentTermination, - }, - 'emp.getEmpPayCompRecurring': { - input: SapsuccessfactorsEndpointInputSchemas.getEmpPayCompRecurring, - output: SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompRecurring, - }, - 'emp.getEmpPayCompNonRecurring': { - input: SapsuccessfactorsEndpointInputSchemas.getEmpPayCompNonRecurring, - output: SapsuccessfactorsEndpointOutputSchemas.getEmpPayCompNonRecurring, - }, - 'work.getWorkOrder': { - input: SapsuccessfactorsEndpointInputSchemas.getWorkOrder, - output: SapsuccessfactorsEndpointOutputSchemas.getWorkOrder, - }, - 'goal.getGoalPlanTemplate': { - input: SapsuccessfactorsEndpointInputSchemas.getGoalPlanTemplate, - output: SapsuccessfactorsEndpointOutputSchemas.getGoalPlanTemplate, - }, - 'goals.getGoalsByPlan': { - input: SapsuccessfactorsEndpointInputSchemas.getGoalsByPlan, - output: SapsuccessfactorsEndpointOutputSchemas.getGoalsByPlan, - }, - 'form.getFormContent': { - input: SapsuccessfactorsEndpointInputSchemas.getFormContent, - output: SapsuccessfactorsEndpointOutputSchemas.getFormContent, - }, - 'learning.createLearningActivitiesBulk': { - input: SapsuccessfactorsEndpointInputSchemas.createLearningActivitiesBulk, - output: SapsuccessfactorsEndpointOutputSchemas.createLearningActivitiesBulk, - }, - 'cdp.getCdpLearningMetadata': { - input: SapsuccessfactorsEndpointInputSchemas.getCdpLearningMetadata, - output: SapsuccessfactorsEndpointOutputSchemas.getCdpLearningMetadata, - }, - 'cdp.refreshCdpLearningMetadata': { - input: SapsuccessfactorsEndpointInputSchemas.refreshCdpLearningMetadata, - output: SapsuccessfactorsEndpointOutputSchemas.refreshCdpLearningMetadata, - }, - 'employee.getEmployeeTime': { - input: SapsuccessfactorsEndpointInputSchemas.getEmployeeTime, - output: SapsuccessfactorsEndpointOutputSchemas.getEmployeeTime, - }, - 'employee.getEmployeeTimesheet': { - input: SapsuccessfactorsEndpointInputSchemas.getEmployeeTimesheet, - output: SapsuccessfactorsEndpointOutputSchemas.getEmployeeTimesheet, - }, - 'temporary.getTemporaryTimeInformation': { - input: SapsuccessfactorsEndpointInputSchemas.getTemporaryTimeInformation, - output: SapsuccessfactorsEndpointOutputSchemas.getTemporaryTimeInformation, - }, - 'time.getTimeAccountSnapshot': { - input: SapsuccessfactorsEndpointInputSchemas.getTimeAccountSnapshot, - output: SapsuccessfactorsEndpointOutputSchemas.getTimeAccountSnapshot, - }, - 'query.queryAllAvailableClockClockOut': { - input: SapsuccessfactorsEndpointInputSchemas.queryAllAvailableClockClockOut, - output: - SapsuccessfactorsEndpointOutputSchemas.queryAllAvailableClockClockOut, - }, - 'query.queryClockClockOutGroupCodeTime': { - input: - SapsuccessfactorsEndpointInputSchemas.queryClockClockOutGroupCodeTime, - output: - SapsuccessfactorsEndpointOutputSchemas.queryClockClockOutGroupCodeTime, - }, -} as const; +const sapsuccessfactorsEndpointMeta = Object.fromEntries( + sapRoutes.map((route) => [ + `${route.group}.${route.name}`, + { + riskLevel: route.riskLevel, + description: route.description, + ...('irreversible' in route && route.irreversible + ? { irreversible: true as const } + : {}), + }, + ]), +) as unknown as RequiredPluginEndpointMeta< + typeof sapsuccessfactorsEndpointsNested +>; -const sapsuccessfactorsEndpointMeta = { - 'approve.approveCalibrationSession': { - riskLevel: 'write', - description: 'Approve Calibration Session', - }, - 'calibration.getCalibrationSessionById': { - riskLevel: 'read', - description: 'Get Calibration Session By ID', - }, - 'calibration.getCalibrationSessions': { - riskLevel: 'read', - description: 'Get Calibration Sessions', - }, - 'calibration.getCalibrationSubjectById': { - riskLevel: 'read', - description: 'Get Calibration Subject By ID', - }, - 'calibration.getCalibrationSubjectRatings': { - riskLevel: 'read', - description: 'Get Calibration Subject Ratings', - }, - 'calibration.updateCalibrationSubjectRatings': { - riskLevel: 'write', - description: 'Update Calibration Subject Ratings', - }, - 'odata.getOdataMetadataCalibSessionService': { - riskLevel: 'read', - description: 'Get Calibration Session Metadata', - }, - 'odata.getOdataMetadataOnboardingAddl': { - riskLevel: 'read', - description: 'Get Onboarding Additional Services Metadata', - }, - 'odata.getOdataMetadataForNominationService': { - riskLevel: 'read', - description: 'Get Nomination Service Metadata', - }, - 'odata.getOdataUserMetadata': { - riskLevel: 'read', - description: 'Get User Entity Metadata', - }, - 'odata.getOdataMetadataClockInclockOut': { - riskLevel: 'read', - description: 'Get Clock In/Out Integration Metadata', - }, - 'onboardee.createOnboardee': { - riskLevel: 'write', - description: 'Create Onboardee', - }, - 'onb2.getOnb2Process': { - riskLevel: 'read', - description: 'Get Onboarding 2.0 Processes', - }, - 'internal.updateInternalUsernameNewHiresAfter': { - riskLevel: 'write', - description: 'Update Username Post Hiring', - }, - 'a.createAFeedbackRequest': { - riskLevel: 'write', - description: 'Create a Feedback Request', - }, - 'feedback.getFeedbackRecordsServiceAvailable': { - riskLevel: 'read', - description: 'Get Feedback Records', - }, - 'pending.getPendingFeedbackRequestsFeedback': { - riskLevel: 'read', - description: 'Get Pending Feedback Requests', - }, - 'give.giveFeedbackOrRespondToAFeedbackRequest': { - riskLevel: 'write', - description: 'Give Feedback or Respond to Feedback Request', - }, - 'metadata.refreshMetadataContFeedbackService': { - riskLevel: 'write', - description: 'Refresh Metadata for Continuous Feedback', - }, - 'successor.createUpdateSuccessorNomination': { - riskLevel: 'write', - description: 'Create or Update Successor Nomination', - }, - 'nomination.deleteNominationPositionTalentPool': { - riskLevel: 'destructive', - irreversible: true, - description: 'Delete Nomination', - }, - 'talent.getTalentPool': { - riskLevel: 'read', - description: 'Get Talent Pool', - }, - 'application.getApplicationInterview': { - riskLevel: 'read', - description: 'Get Application Interview', - }, - 'interview.getInterviewOverallAssessment': { - riskLevel: 'read', - description: 'Get Interview Overall Assessment', - }, - 'job.getJobApplication': { - riskLevel: 'read', - description: 'Get Job Application', - }, - 'job.getJobRequisition': { - riskLevel: 'read', - description: 'Get Job Requisition', - }, - 'job.getJobReqScreeningQuestion': { - riskLevel: 'read', - description: 'Get Job Requisition Screening Questions', - }, - 'candidates.listCandidates': { - riskLevel: 'read', - description: 'List Candidates', - }, - 'fo.getFoBusinessUnit': { - riskLevel: 'read', - description: 'Get FOBusinessUnit', - }, - 'fo.getFoCompany': { - riskLevel: 'read', - description: 'Get FOCompany Records', - }, - 'fo.getFoCostCenter': { - riskLevel: 'read', - description: 'Get Foundation Object Cost Centers', - }, - 'fo.getFoDepartment': { - riskLevel: 'read', - description: 'Get FODepartment Records', - }, - 'fo.getFoJobCode': { - riskLevel: 'read', - description: 'Get Foundation Object Job Codes', - }, - 'fo.getFoJobFunction': { - riskLevel: 'read', - description: 'Get Job Functions', - }, - 'fo.getFoLocation': { - riskLevel: 'read', - description: 'Get Foundation Object Location', - }, - 'fo.getFoPayGroup': { - riskLevel: 'read', - description: 'Get FOPayGroup', - }, - 'position.getPosition': { - riskLevel: 'read', - description: 'Get Position', - }, - 'custom.getCustomMdfObject': { - riskLevel: 'read', - description: 'Get Custom MDF Object', - }, - 'picklist.getPicklist': { - riskLevel: 'read', - description: 'Get Picklist', - }, - 'picklist.getPicklistOption': { - riskLevel: 'read', - description: 'Get Picklist Option', - }, - 'current.getCurrentUser': { - riskLevel: 'read', - description: 'Get Current User', - }, - 'users.listUsers': { - riskLevel: 'read', - description: 'List Users', - }, - 'per.getPerPersonById': { - riskLevel: 'read', - description: 'Get Person by ID', - }, - 'per.listPerPerson': { - riskLevel: 'read', - description: 'List Person Records', - }, - 'per.getPerPersonal': { - riskLevel: 'read', - description: 'Get Personal Information Records', - }, - 'background.getBackgroundEducation': { - riskLevel: 'read', - description: 'Get Background Education', - }, - 'background.getBackgroundMobility': { - riskLevel: 'read', - description: 'Get Background Mobility', - }, - 'emp.listEmpEmployment': { - riskLevel: 'read', - description: 'List Employee Employment Records', - }, - 'emp.getEmpEmploymentTermination': { - riskLevel: 'read', - description: 'Get Employee Employment Termination', - }, - 'emp.getEmpPayCompRecurring': { - riskLevel: 'read', - description: 'Get Recurring Pay Components', - }, - 'emp.getEmpPayCompNonRecurring': { - riskLevel: 'read', - description: 'Get Non-Recurring Pay Components', - }, - 'work.getWorkOrder': { - riskLevel: 'read', - description: 'Get Work Order', - }, - 'goal.getGoalPlanTemplate': { - riskLevel: 'read', - description: 'Get Goal Plan Template', - }, - 'goals.getGoalsByPlan': { - riskLevel: 'read', - description: 'Get Goals By Plan', - }, - 'form.getFormContent': { - riskLevel: 'read', - description: 'Get Form Content', - }, - 'learning.createLearningActivitiesBulk': { - riskLevel: 'write', - description: 'Create Learning Activities Bulk', - }, - 'cdp.getCdpLearningMetadata': { - riskLevel: 'read', - description: 'Get CDP Learning Metadata', - }, - 'cdp.refreshCdpLearningMetadata': { - riskLevel: 'write', - description: 'Refresh CDP Learning Metadata', - }, - 'employee.getEmployeeTime': { - riskLevel: 'read', - description: 'Get Employee Time', - }, - 'employee.getEmployeeTimesheet': { - riskLevel: 'read', - description: 'Get Employee Timesheet', - }, - 'temporary.getTemporaryTimeInformation': { - riskLevel: 'read', - description: 'Get Temporary Time Information', - }, - 'time.getTimeAccountSnapshot': { - riskLevel: 'read', - description: 'Get Time Account Snapshot', - }, - 'query.queryAllAvailableClockClockOut': { - riskLevel: 'read', - description: 'Query All Available Clock In/Clock Out Groups', - }, - 'query.queryClockClockOutGroupCodeTime': { - riskLevel: 'read', - description: 'Query Clock In/Clock Out Group By Code', - }, -} satisfies RequiredPluginEndpointMeta; +const defaultAuthType: AuthTypes = 'oauth_2'; export type BaseSapsuccessfactorsPlugin< T extends SapsuccessfactorsPluginOptions, @@ -865,8 +118,10 @@ export type BaseSapsuccessfactorsPlugin< 'sapsuccessfactors', typeof SapsuccessfactorsSchema, typeof sapsuccessfactorsEndpointsNested, - typeof sapsuccessfactorsWebhooksNested, - T + Record, + T, + typeof defaultAuthType, + typeof sapsuccessfactorsAuthConfig >; export type InternalSapsuccessfactorsPlugin = @@ -881,25 +136,60 @@ export function sapsuccessfactors< incomingOptions: SapsuccessfactorsPluginOptions & T = {} as SapsuccessfactorsPluginOptions & T, ): ExternalSapsuccessfactorsPlugin { - const options = { ...incomingOptions }; + const options = { + ...incomingOptions, + authType: incomingOptions.authType ?? defaultAuthType, + }; + const rawHost = options.host?.trim() || options.apiBaseUrl?.trim(); + const host = rawHost + ? normalizeSapsuccessfactorsHost(rawHost) + : SAP_SUCCESSFACTORS_DEFAULT_HOST; + options.host = host; + const oauthUrls = sapSuccessfactorsOAuthUrls(host); + return { id: 'sapsuccessfactors', + authConfig: sapsuccessfactorsAuthConfig, + oauthConfig: { + providerName: 'SAP SuccessFactors', + authUrl: oauthUrls.authUrl, + tokenUrl: oauthUrls.tokenUrl, + scopes: [], + tokenAuthMethod: 'body' as const, + requiresRegisteredRedirect: true, + }, schema: SapsuccessfactorsSchema, options, hooks: options.hooks, - webhookHooks: options.webhookHooks, + webhookHooks: undefined, endpoints: sapsuccessfactorsEndpointsNested, - webhooks: sapsuccessfactorsWebhooksNested, + webhooks: {} as const, endpointMeta: sapsuccessfactorsEndpointMeta, endpointSchemas: sapsuccessfactorsEndpointSchemas, - pluginWebhookMatcher: () => false, - keyBuilder: async (_ctx: SapsuccessfactorsKeyBuilderContext, source) => { + webhookSchemas: {} as const, + pluginWebhookMatcher: undefined, + errorHandlers: { + ...errorHandlers, + ...options.errorHandlers, + }, + keyBuilder: async (ctx: SapsuccessfactorsKeyBuilderContext, source) => { if (source === 'endpoint' && options.key) return options.key; - throw new Error('SAP SuccessFactors API key is required'); + if (source === 'endpoint' && ctx.authType === 'oauth_2') { + const res = await ctx.keys.get_access_token(); + if (!res) throw new AuthMissingError('sapsuccessfactors', 'oauth_2'); + return res; + } + if (source === 'endpoint' && ctx.authType === 'api_key') { + const res = await ctx.keys.get_api_key(); + if (!res) throw new AuthMissingError('sapsuccessfactors', 'api_key'); + return res; + } + throw new AuthMissingError('sapsuccessfactors', options.authType); }, } satisfies InternalSapsuccessfactorsPlugin; } +export { sapRoutes } from './endpoints/routes'; export type { SapsuccessfactorsEndpointInputs, SapsuccessfactorsEndpointOutputs, diff --git a/packages/sapsuccessfactors/jest.config.cjs b/packages/sapsuccessfactors/jest.config.cjs index 8c6218f64..5d4bfd6ec 100644 --- a/packages/sapsuccessfactors/jest.config.cjs +++ b/packages/sapsuccessfactors/jest.config.cjs @@ -2,54 +2,29 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: [''], - testMatch: [ - '**/*.test.ts', - '**/tests/**/*.test.ts', - '**/plugins/**/*.test.ts', - '**/setup/**/*.test.ts', - ], - collectCoverageFrom: [ - '**/*.ts', - '!**/*.d.ts', - '!**/node_modules/**', - '!**/dist/**', - '!jest.config.ts', - '!tests/**', - ], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + testMatch: ['**/*.test.ts'], transform: { - '^.+\\.yaml$': '/../corsair/jest-yaml-transform.cjs', '^.+\\.ts$': [ 'ts-jest', { useESM: true, - tsconfig: { - esModuleInterop: true, - allowSyntheticDefaultImports: true, - verbatimModuleSyntax: false, - module: 'ESNext', - moduleResolution: 'Bundler', - }, + tsconfig: 'tsconfig.test.json', }, ], '.*\\.js$': [ 'ts-jest', { useESM: true, - tsconfig: { - esModuleInterop: true, - allowSyntheticDefaultImports: true, - }, + tsconfig: 'tsconfig.test.json', }, ], }, moduleNameMapper: { - '^corsair/core$': '/../corsair/core.ts', '^corsair/http$': '/../corsair/http.ts', + '^corsair/core$': '/../corsair/core.ts', '^(\\.\\.?/.*)\\.js$': '$1', }, transformIgnorePatterns: ['node_modules/(?!.*uuid.*)'], extensionsToTreatAsEsm: ['.ts'], testTimeout: 30000, - verbose: true, }; diff --git a/packages/sapsuccessfactors/package.json b/packages/sapsuccessfactors/package.json index 458a24f4f..809f6f6af 100644 --- a/packages/sapsuccessfactors/package.json +++ b/packages/sapsuccessfactors/package.json @@ -1,7 +1,7 @@ { "name": "@corsair-dev/sapsuccessfactors", "version": "0.1.0", - "description": "Sapsuccessfactors plugin for Corsair", + "description": "SAP SuccessFactors plugin for Corsair", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index 9a4ce9275..5a731ecfa 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -1,75 +1,84 @@ +import { sapRoutes } from './endpoints/routes'; import { SapsuccessfactorsEndpointInputSchemas, SapsuccessfactorsEndpointOutputSchemas, } from './endpoints/types'; import { SapsuccessfactorsSchema } from './schema'; +import { SapsuccessfactorsUserEntity } from './schema/database'; -describe('Sapsuccessfactors schema and validation', () => { - it('declares a semver version', () => { - expect(SapsuccessfactorsSchema.version).toBeDefined(); +describe('sapsuccessfactors schemas', () => { + it('declares labeled User fields from the OData dictionary', () => { + const user = SapsuccessfactorsUserEntity.parse({ + userId: 'cgrant', + username: 'cgrant', + firstName: 'Carla', + lastName: 'Grant', + email: 'cgrant@example.com', + status: 't', + custom01: 'tenant-extra', + }); + expect(user.userId).toBe('cgrant'); + expect(SapsuccessfactorsSchema.entities.user).toBeDefined(); expect(SapsuccessfactorsSchema.version).toMatch(/^\d+\.\d+\.\d+$/); }); - it('declares comprehensive entity schemas', () => { - expect(typeof SapsuccessfactorsSchema.entities).toBe('object'); - expect(SapsuccessfactorsSchema.entities.user).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.person).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.personal).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.employment).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.calibrationSession).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.goalPlan).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.jobRequisition).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.candidate).toBeDefined(); - expect(SapsuccessfactorsSchema.entities.position).toBeDefined(); + it('covers every registered operation with input and output schemas', () => { + for (const route of sapRoutes) { + expect( + SapsuccessfactorsEndpointInputSchemas[ + route.name as keyof typeof SapsuccessfactorsEndpointInputSchemas + ], + ).toBeDefined(); + expect( + SapsuccessfactorsEndpointOutputSchemas[ + route.name as keyof typeof SapsuccessfactorsEndpointOutputSchemas + ], + ).toBeDefined(); + } + expect(sapRoutes).toHaveLength(64); }); - it('validates approveCalibrationSession input schema positive and negative cases', () => { - const valid = { session_id: 'session-123' }; + it('rejects invalid paging and missing keys', () => { expect( - SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.parse( - valid, - ), - ).toEqual(valid); + SapsuccessfactorsEndpointInputSchemas.listUsers.safeParse({ + top: 'nope', + }).success, + ).toBe(false); expect( SapsuccessfactorsEndpointInputSchemas.approveCalibrationSession.safeParse( {}, ).success, ).toBe(false); - }); - - it('validates getPersonById input schema positive and negative cases', () => { - const valid = { person_id_external: 'emp-456' }; - expect( - SapsuccessfactorsEndpointInputSchemas.getPerPersonById.parse(valid), - ).toEqual(valid); expect( SapsuccessfactorsEndpointInputSchemas.getPerPersonById.safeParse({}) .success, ).toBe(false); - }); - - it('validates listUsers input schema with pagination', () => { - const valid = { top: 10, skip: 0, filter: "status eq 'ACTIVE'" }; expect( - SapsuccessfactorsEndpointInputSchemas.listUsers.parse(valid), - ).toEqual(valid); - expect( - SapsuccessfactorsEndpointInputSchemas.listUsers.safeParse({ - top: 'invalid_number', + SapsuccessfactorsEndpointInputSchemas.getCustomMdfObject.safeParse({ + custom_object: 'User', }).success, ).toBe(false); + expect( + SapsuccessfactorsEndpointInputSchemas.createAFeedbackRequest.safeParse({}) + .success, + ).toBe(false); }); - it('validates standard response output schema', () => { - const validResponse = { - d: { - results: [{ id: '1', name: 'Test' }], - id: '1', - status: 'OK', - }, - }; + it('accepts OData v2, v4, and metadata payloads', () => { expect( - SapsuccessfactorsEndpointOutputSchemas.listUsers.parse(validResponse), + SapsuccessfactorsEndpointOutputSchemas.listUsers.parse({ + d: { results: [{ userId: 'cgrant' }] }, + }), + ).toBeDefined(); + expect( + SapsuccessfactorsEndpointOutputSchemas.getFeedbackRecordsServiceAvailable.parse( + { value: [{ id: '1' }] }, + ), + ).toBeDefined(); + expect( + SapsuccessfactorsEndpointOutputSchemas.getOdataUserMetadata.parse( + '', + ), ).toBeDefined(); }); }); diff --git a/packages/sapsuccessfactors/schema/database.ts b/packages/sapsuccessfactors/schema/database.ts index e94e9dd86..9dc00c3bf 100644 --- a/packages/sapsuccessfactors/schema/database.ts +++ b/packages/sapsuccessfactors/schema/database.ts @@ -1,199 +1,279 @@ import { z } from 'zod'; +/** + * SAP SuccessFactors OData entity shapes for Corsair DB cache (`ctx.db.*`). + * Field names follow the labeled properties in the OData API Data Dictionary + * (Admin Center → API Center → OData API Data Dictionary) and the HCM OData + * API Reference: User, PerPerson, PerPersonal, EmpEmployment, JobRequisition, + * Candidate, JobApplication, Position, FO*. + * + * Loose + catchall — tenants add custom fields; OData also returns `__metadata`. + */ + const S = z.string().nullable().optional(); const N = z.number().nullable().optional(); const B = z.boolean().nullable().optional(); +const Deferred = z + .object({ __deferred: z.object({ uri: z.string().optional() }).optional() }) + .catchall(z.unknown()) + .optional(); -/** - * SAP SuccessFactors User Entity - */ +const ODataMeta = z + .object({ + uri: z.string().optional(), + type: z.string().optional(), + }) + .catchall(z.unknown()) + .optional(); + +/** User — business key `userId`. OData: GET /odata/v2/User */ export const SapsuccessfactorsUserEntity = z .object({ + __metadata: ODataMeta, userId: z.string(), username: S, + defaultFullName: S, firstName: S, + mi: S, lastName: S, email: S, - title: S, + status: S, department: S, division: S, location: S, - status: S, + title: S, + managerId: S, + hrId: S, hireDate: S, lastModifiedDateTime: S, + lastModified: S, + timeZone: S, + country: S, + state: S, + city: S, + zipCode: S, + addressLine1: S, + businessPhone: S, + cellPhone: S, + empId: S, + totalTeamSize: N, + directReports: Deferred, + manager: Deferred, + hr: Deferred, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsUserEntity = z.infer< typeof SapsuccessfactorsUserEntity >; -/** - * SAP SuccessFactors PerPerson Entity - */ +/** PerPerson — Employee Central person; business key `personIdExternal`. */ export const SapsuccessfactorsPersonEntity = z .object({ + __metadata: ODataMeta, personIdExternal: z.string(), + personId: S, dateOfBirth: S, countryOfBirth: S, + regionOfBirth: S, placeOfBirth: S, - userId: S, + perPersonUuid: S, + lastModifiedDateTime: S, + personalInfoNav: Deferred, + employmentNav: Deferred, + emailNav: Deferred, + phoneNav: Deferred, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsPersonEntity = z.infer< typeof SapsuccessfactorsPersonEntity >; -/** - * SAP SuccessFactors PerPersonal Entity - */ +/** PerPersonal — effective-dated biographical info. */ export const SapsuccessfactorsPersonalEntity = z .object({ + __metadata: ODataMeta, personIdExternal: z.string(), startDate: S, endDate: S, firstName: S, lastName: S, + middleName: S, + formalName: S, + birthName: S, gender: S, maritalStatus: S, nationality: S, + preferredName: S, + salutation: S, + lastModifiedDateTime: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsPersonalEntity = z.infer< typeof SapsuccessfactorsPersonalEntity >; -/** - * SAP SuccessFactors EmpEmployment Entity - */ +/** EmpEmployment — employment assignment. */ export const SapsuccessfactorsEmploymentEntity = z .object({ + __metadata: ODataMeta, userId: z.string(), personIdExternal: S, startDate: S, endDate: S, - employmentStatus: S, + originalStartDate: S, + seniorityDate: S, + assignmentClass: S, + employmentType: S, + isContingentWorker: B, + lastModifiedDateTime: S, + jobInfoNav: Deferred, + compInfoNav: Deferred, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsEmploymentEntity = z.infer< typeof SapsuccessfactorsEmploymentEntity >; -/** - * SAP SuccessFactors CalibrationSession Entity - */ +/** CalibrationSession — CalSession.svc OData V4. */ export const SapsuccessfactorsCalibrationSessionEntity = z .object({ - sessionId: z.string(), + sessionId: z.string().optional(), sessionName: S, + sessionOwnerId: S, sessionType: S, status: S, startDate: S, endDate: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsCalibrationSessionEntity = z.infer< typeof SapsuccessfactorsCalibrationSessionEntity >; -/** - * SAP SuccessFactors GoalPlanTemplate Entity - */ +/** GoalPlanTemplate */ export const SapsuccessfactorsGoalPlanEntity = z .object({ - id: z.string(), + id: z.union([z.string(), z.number()]).optional(), name: S, - planType: S, + type: S, dueDate: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsGoalPlanEntity = z.infer< typeof SapsuccessfactorsGoalPlanEntity >; -/** - * SAP SuccessFactors Goal Entity - */ +/** Goal_ */ export const SapsuccessfactorsGoalEntity = z .object({ - id: z.string(), + id: z.union([z.string(), z.number()]).optional(), userId: S, name: S, + flag: S, state: S, + type: S, metric: S, done: N, start: S, due: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsGoalEntity = z.infer< typeof SapsuccessfactorsGoalEntity >; -/** - * SAP SuccessFactors JobRequisition Entity - */ +/** JobRequisition — business key `jobReqId`. */ export const SapsuccessfactorsJobRequisitionEntity = z .object({ - jobReqId: z.string(), + __metadata: ODataMeta, + jobReqId: z.union([z.string(), z.number()]), + internalStatus: S, jobTitle: S, + jobCode: S, department: S, division: S, location: S, - status: S, + country: S, + statusSetId: S, + appStatusSetId: S, + lastModifiedDateTime: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsJobRequisitionEntity = z.infer< typeof SapsuccessfactorsJobRequisitionEntity >; -/** - * SAP SuccessFactors Candidate Entity - */ +/** Candidate */ export const SapsuccessfactorsCandidateEntity = z .object({ - candidateId: z.string(), + __metadata: ODataMeta, + candidateId: z.union([z.string(), z.number()]), firstName: S, lastName: S, primaryEmail: S, + contactEmail: S, cellPhone: S, city: S, country: S, + currentTitle: S, + lastModifiedDateTime: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsCandidateEntity = z.infer< typeof SapsuccessfactorsCandidateEntity >; -/** - * SAP SuccessFactors JobApplication Entity - */ +/** JobApplication */ export const SapsuccessfactorsJobApplicationEntity = z .object({ - applicationId: z.string(), - jobReqId: S, - candidateId: S, - appStatusId: S, + __metadata: ODataMeta, + applicationId: z.union([z.string(), z.number()]), + jobReqId: z.union([z.string(), z.number()]).nullable().optional(), + candidateId: z.union([z.string(), z.number()]).nullable().optional(), + status: S, + appStatusSetId: S, applicationDate: S, + lastModifiedDateTime: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsJobApplicationEntity = z.infer< typeof SapsuccessfactorsJobApplicationEntity >; -/** - * SAP SuccessFactors Position Entity - */ +/** Position — Employee Central Position Management. */ export const SapsuccessfactorsPositionEntity = z .object({ + __metadata: ODataMeta, code: z.string(), - externalName: S, effectiveStartDate: S, + effectiveEndDate: S, effectiveStatus: S, + externalName_defaultValue: S, jobCode: S, department: S, + division: S, company: S, + location: S, + payGrade: S, + lastModifiedDateTime: S, }) - .passthrough(); + .catchall(z.unknown()); export type SapsuccessfactorsPositionEntity = z.infer< typeof SapsuccessfactorsPositionEntity >; + +/** FOCompany — foundation object. */ +export const SapsuccessfactorsCompanyEntity = z + .object({ + externalCode: z.string().optional(), + startDate: S, + name_defaultValue: S, + status: S, + country: S, + currency: S, + entityOID: S, + }) + .catchall(z.unknown()); +export type SapsuccessfactorsCompanyEntity = z.infer< + typeof SapsuccessfactorsCompanyEntity +>; diff --git a/packages/sapsuccessfactors/schema/index.ts b/packages/sapsuccessfactors/schema/index.ts index 98425dc22..ff7268d8c 100644 --- a/packages/sapsuccessfactors/schema/index.ts +++ b/packages/sapsuccessfactors/schema/index.ts @@ -1,6 +1,7 @@ import { SapsuccessfactorsCalibrationSessionEntity, SapsuccessfactorsCandidateEntity, + SapsuccessfactorsCompanyEntity, SapsuccessfactorsEmploymentEntity, SapsuccessfactorsGoalEntity, SapsuccessfactorsGoalPlanEntity, @@ -26,6 +27,7 @@ export const SapsuccessfactorsSchema = { candidate: SapsuccessfactorsCandidateEntity, jobApplication: SapsuccessfactorsJobApplicationEntity, position: SapsuccessfactorsPositionEntity, + company: SapsuccessfactorsCompanyEntity, }, } as const; diff --git a/packages/sapsuccessfactors/tsconfig.test.json b/packages/sapsuccessfactors/tsconfig.test.json new file mode 100644 index 000000000..99f74b817 --- /dev/null +++ b/packages/sapsuccessfactors/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", + "verbatimModuleSyntax": false, + "lib": ["es2022", "dom", "esnext"], + "types": ["node", "jest"] + } +} diff --git a/packages/sapsuccessfactors/webhooks/tenant-matcher.ts b/packages/sapsuccessfactors/webhooks/tenant-matcher.ts deleted file mode 100644 index 5e848edab..000000000 --- a/packages/sapsuccessfactors/webhooks/tenant-matcher.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { RawWebhookRequest, WebhookTenantMatch } from 'corsair/core'; - -// SAP SuccessFactors REST/OData plugin does not expose inbound webhooks in this integration. -export function matchSapsuccessfactorsTenantWebhook( - _request: RawWebhookRequest, -): WebhookTenantMatch | null { - return null; -} From fedd6ac9910b44c9abcc14a7491a45c15ad91415 Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:15:43 +0530 Subject: [PATCH 12/18] fix(sapsuccessfactors): restore registration after main merge --- packages/corsair/core/constants.ts | 3 +++ pnpm-lock.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/corsair/core/constants.ts b/packages/corsair/core/constants.ts index b0323ec3c..e50a1cae5 100644 --- a/packages/corsair/core/constants.ts +++ b/packages/corsair/core/constants.ts @@ -204,6 +204,7 @@ export const BaseProviders = [ 'resend', 'retailed', 'salesforce', + 'sapsuccessfactors', 'scrapegraphai', 'securitytrails', 'sendgrid', @@ -451,6 +452,7 @@ export const ProviderDisplayNames = { resend: 'Resend', retailed: 'Retailed', salesforce: 'Salesforce', + sapsuccessfactors: 'SAP SuccessFactors', scrapegraphai: 'ScrapeGraphAI', securitytrails: 'SecurityTrails', sendgrid: 'SendGrid', @@ -704,6 +706,7 @@ export type AllProviders = | 'resend' | 'retailed' | 'salesforce' + | 'sapsuccessfactors' | 'scrapegraphai' | 'securitytrails' | 'sendgrid' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45ec32231..22e4d9be8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5241,6 +5241,30 @@ importers: specifier: 4.4.3 version: 4.4.3 + packages/sapsuccessfactors: + devDependencies: + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + corsair: + specifier: workspace:* + version: link:../corsair + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + ts-jest: + specifier: ^29.4.9 + version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@30.4.1)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.27.0)(jest-util@30.4.1)(jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(typescript@5.9.3) + tsup: + specifier: ^8.0.1 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.4)(typescript@5.9.3)(yaml@2.9.0) + typescript: + specifier: 'catalog:' + version: 5.9.3 + zod: + specifier: 4.4.3 + version: 4.4.3 + packages/scrapegraphai: devDependencies: '@types/jest': From 25ebb5efb857b581fabfa428cb52dc13cd58405d Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:16:45 +0530 Subject: [PATCH 13/18] Update packages/sapsuccessfactors/endpoints/types.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- packages/sapsuccessfactors/endpoints/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts index 08a2dc2c2..8d7a264dd 100644 --- a/packages/sapsuccessfactors/endpoints/types.ts +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -23,6 +23,7 @@ export const SapResponseSchema = z.union([ z.string(), z.record(z.string(), z.unknown()), z.null(), + z.undefined(), ]); const Body = z.record(z.string(), z.unknown()).optional(); From dd1ff2ed95b1b8c84d6fe059bb8210445e63533a Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:17:45 +0530 Subject: [PATCH 14/18] fix: drop leftover docs from unrelated-history merge --- docs/getting-started/introduction.mdx | 74 -------- docs/getting-started/quick-start.mdx | 239 -------------------------- docs/mcp-adapters/openai-agents.mdx | 36 ---- 3 files changed, 349 deletions(-) delete mode 100644 docs/getting-started/introduction.mdx delete mode 100644 docs/getting-started/quick-start.mdx delete mode 100644 docs/mcp-adapters/openai-agents.mdx diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx deleted file mode 100644 index cd777581d..000000000 --- a/docs/getting-started/introduction.mdx +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Introduction -description: The go-to integration layer for AI agents and apps. Connect anything, anywhere, in seconds. -mode: "wide" ---- - -Corsair is the fastest way to add any integration to your app or agent. It handles the authentication and data-sync plumbing every service needs, so you write only the part that's unique to your use case — not the wiring you've already built a hundred times. - -Every integration is a plugin. Install the ones you need, hand Corsair a database and an encryption key, and each service becomes a typed client in your code — plus one set of tools your agent can call across all of them. One pattern, whether you have a single integration or fifty. [Corsair Hub](/hub/overview) handles the hosted OAuth and approval surfaces so you never build them. - -Install Corsair and the plugins you need: - - -```bash npm -npm install corsair @corsair-dev/slack @corsair-dev/github @corsair-dev/gmail @corsair-dev/linear -``` -```bash yarn -yarn add corsair @corsair-dev/slack @corsair-dev/github @corsair-dev/gmail @corsair-dev/linear -``` -```bash pnpm -pnpm install corsair @corsair-dev/slack @corsair-dev/github @corsair-dev/gmail @corsair-dev/linear -``` -```bash bun -bun add corsair @corsair-dev/slack @corsair-dev/github @corsair-dev/gmail @corsair-dev/linear -``` - - -Each service ships as its own `@corsair-dev/*` package. Find the one you need — and its exact install id — in the [**Plugins**](/guides/plugins) catalog. - -```ts corsair.ts -import { createCorsair } from 'corsair'; -import { github } from '@corsair-dev/github'; -import { gmail } from '@corsair-dev/gmail'; -import { linear } from '@corsair-dev/linear'; -import { slack } from '@corsair-dev/slack'; - -export const corsair = createCorsair({ - plugins: [slack(), github(), gmail(), linear()], - database: db, - kek: process.env.CORSAIR_KEK!, -}); -``` - -Connect it to your agent and start prompting: - -``` -Invite Jim to next Thursday's sales call. Tell him over Slack too so he -can accept it. Let me know when he does. -``` - -One prompt, four integrations, and Corsair handles the rest. - -Corsair runs in your own app and stores credentials in your own database. **[Corsair Hub](/hub/overview) is the recommended way to run it** — connect your app, or your users' apps, without hosting the OAuth connect, approval, and webhook surfaces yourself. Hub relays those surfaces and still stores none of your credentials; tokens stay encrypted in your database. Prefer to host those surfaces yourself? That path stays [fully supported](/hub/manual-vs-hub). - - -In a hurry? Hand it to your coding agent. [Set up with your agent](/getting-started/set-up-with-your-agent) is one prompt that wires Corsair Hub end to end — install, route, keys, and a real connected integration. - - -## Get started - - - - Install and run your first integration with Hub in minutes. - - - Paste one prompt and let your coding agent wire Corsair Hub end to end. - - - The hosted relay for connect and approvals — and why it stores no credentials. - - - Slack, Linear, Gmail, GitHub, HubSpot, Stripe, and hundreds more. - - diff --git a/docs/getting-started/quick-start.mdx b/docs/getting-started/quick-start.mdx deleted file mode 100644 index 6d84df192..000000000 --- a/docs/getting-started/quick-start.mdx +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: Quick Start -description: A working integration in five steps, powered by Hub. ---- - -import { GenerateKEK } from '/snippets/generate-kek.mdx'; -import { WindowsMigrationNote } from '/snippets/windows-shell.mdx'; -import MountHandlerNextjs from '/snippets/mount-handler-nextjs.mdx'; - -The quickest path to a working integration is **Hub**. It hosts the OAuth connect, approval, and webhook surfaces, so there are no connect pages, callback routes, or per-environment redirect URIs to build. Credentials are still encrypted and stored in your own database — Hub stores none. - - -Want to host those surfaces yourself instead? Every step below is the same; swap the `hub` block for `manual`. See [Manual or Hub](/hub/manual-vs-hub). - - - - - - -## Install - - -```bash npm -npm install corsair @corsair-dev/github -``` -```bash yarn -yarn add corsair @corsair-dev/github -``` -```bash pnpm -pnpm install corsair @corsair-dev/github -``` -```bash bun -bun add corsair @corsair-dev/github -``` - - - -`@corsair-dev/github` is one plugin. Every service is its own `@corsair-dev/*` package — find the one you need, and its exact install id, in the [**Plugins**](/guides/plugins) catalog. - - - - - - -## Set your environment - -Create a project in the [Hub dashboard](https://hub.corsair.dev/dashboard) and copy the **development** API key and signing secret. Then generate a KEK — Corsair encrypts every stored credential with it: - - - -```bash .env -CORSAIR_KEK=your-generated-kek -CORSAIR_DEV_API_KEY=ck_dev_... -CORSAIR_DEV_SIGNING_SECRET=... -APP_URL=http://localhost:3000 -``` - - -Keep your KEK safe. Lose it and you lose access to every stored credential. Treat it like a root password. - - - - - - -## Create the database - -Corsair stores data in four tables. SQLite is the fastest way to start: - - -```bash npm -npm install better-sqlite3 -``` -```bash yarn -yarn add better-sqlite3 -``` -```bash pnpm -pnpm install better-sqlite3 -``` -```bash bun -bun add better-sqlite3 -``` - - - - -```sql migration.sql -CREATE TABLE IF NOT EXISTS corsair_integrations ( - id TEXT PRIMARY KEY, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - name TEXT NOT NULL, - config TEXT NOT NULL DEFAULT '{}', - dek TEXT NULL -); - -CREATE TABLE IF NOT EXISTS corsair_accounts ( - id TEXT PRIMARY KEY, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - tenant_id TEXT NOT NULL, - integration_id TEXT NOT NULL, - config TEXT NOT NULL DEFAULT '{}', - dek TEXT NULL, - FOREIGN KEY (integration_id) REFERENCES corsair_integrations(id) -); - -CREATE TABLE IF NOT EXISTS corsair_entities ( - id TEXT PRIMARY KEY, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - account_id TEXT NOT NULL, - entity_id TEXT NOT NULL, - entity_type TEXT NOT NULL, - version TEXT NOT NULL, - data TEXT NOT NULL DEFAULT '{}', - FOREIGN KEY (account_id) REFERENCES corsair_accounts(id) -); - -CREATE TABLE IF NOT EXISTS corsair_events ( - id TEXT PRIMARY KEY, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - account_id TEXT NOT NULL, - event_type TEXT NOT NULL, - payload TEXT NOT NULL DEFAULT '{}', - status TEXT, - FOREIGN KEY (account_id) REFERENCES corsair_accounts(id) -); -``` - - - - - - -```bash -sqlite3 corsair.db < migration.sql -``` - - - - - - -```powershell -Get-Content migration.sql | sqlite3 corsair.db -``` - - - - -Using Postgres, Drizzle, or Prisma instead? See [Database](/concepts/database) for each option. - - - - - -## Configure Corsair - -Wire your database, KEK, and Hub keys together in `src/server/corsair.ts`: - -```ts src/server/corsair.ts -import 'dotenv/config'; -import Database from 'better-sqlite3'; -import { createCorsair } from 'corsair'; -import { github } from '@corsair-dev/github'; - -const db = new Database('corsair.db'); - -export const corsair = createCorsair({ - plugins: [github({ authType: 'managed' })], - database: db, - kek: process.env.CORSAIR_KEK!, - hub: { - projectApiKey: process.env.CORSAIR_DEV_API_KEY!, - signingSecret: process.env.CORSAIR_DEV_SIGNING_SECRET!, - }, -}); -``` - -Mount the handler once — it serves Hub delivery and the management API. In development, Hub auto-detects your localhost delivery URL: - - - -Add more plugins later — `slack()`, `linear()`, `gmail()` — by appending to the array. - - - - - -## Connect and call - -Mint a connect link and send the user to it. Hub hosts the connect page and delivers the result back to your app — no connect page or OAuth callback to build: - -```ts connect.ts -const { connectUrl } = await corsair.manage.connect.createLink({ - plugin: 'github', - tenantId: 'acme', -}); -// redirect the user's browser to connectUrl -``` - -Once connected, call any endpoint. Responses are also cached in your database for instant reads: - -```ts usage.ts -const repos = await corsair.github.api.repositories.list({}); -``` - -Want an agent to call endpoints on its own? See [MCP Adapters](/mcp-adapters/mcp-adapters). - - - - - ---- - -## What's next - - - - Give an agent the four Corsair tools and let it discover and call any endpoint. - - - How the relay works, and why Hub stores none of your credentials. - - - Development vs production keys — switch to production when you deploy. - - - Building a product? Flip one flag and every user gets their own data and credentials. - - - Postgres, Drizzle, Prisma, and the four tables Corsair uses. - - - Seed credentials and provision tenants from the `corsair` CLI or `setupCorsair`. - - diff --git a/docs/mcp-adapters/openai-agents.mdx b/docs/mcp-adapters/openai-agents.mdx deleted file mode 100644 index cb4341767..000000000 --- a/docs/mcp-adapters/openai-agents.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: OpenAI Agents -description: Connect Corsair to the OpenAI Agents SDK. ---- - -Use `OpenAIAgentsProvider` to connect Corsair to the [OpenAI Agents SDK](https://github.com/openai/openai-agents-js). - -## Install - -```bash -npm install @openai/agents -``` - -## Usage - -```ts agent.ts -import { OpenAIAgentsProvider } from '@corsair-dev/mcp'; -import { Agent, run, tool } from '@openai/agents'; -import { corsair } from './corsair'; - -const provider = new OpenAIAgentsProvider(); -const tools = provider.build({ corsair, tool }); - -const agent = new Agent({ - name: 'corsair-agent', - model: 'gpt-4.1', - instructions: - 'You have access to Corsair tools. Use list_operations to discover available APIs, get_schema to understand required arguments, and run_script to execute them. When referencing resources (like channels), always use their ID, not their name.', - tools, -}); - -const result = await run(agent, 'Setup corsair, then list all Slack channels.'); -console.log(result.finalOutput); -``` - -`OpenAIAgentsProvider.build()` is async — it dynamically imports `@openai/agents` as an optional peer dependency. Pass the `tool` function from `@openai/agents` so the provider can wrap each Corsair tool in the correct format. From 073030407aa8b4ec5fc9459e60fe26d9a5b4da85 Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:20:46 +0530 Subject: [PATCH 15/18] fix(sapsuccessfactors): satisfy RequiredPluginEndpointMeta --- packages/sapsuccessfactors/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sapsuccessfactors/index.ts b/packages/sapsuccessfactors/index.ts index 5d44c12e3..4dfbfca58 100644 --- a/packages/sapsuccessfactors/index.ts +++ b/packages/sapsuccessfactors/index.ts @@ -108,7 +108,7 @@ const sapsuccessfactorsEndpointMeta = Object.fromEntries( ]), ) as unknown as RequiredPluginEndpointMeta< typeof sapsuccessfactorsEndpointsNested ->; +> satisfies RequiredPluginEndpointMeta; const defaultAuthType: AuthTypes = 'oauth_2'; From d18687772a0872300423967351c6fbfc7c8ebf71 Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:24:55 +0530 Subject: [PATCH 16/18] fix(sapsuccessfactors): enforce OData collection vs entity outputs --- packages/sapsuccessfactors/api.test.ts | 10 ++++ packages/sapsuccessfactors/endpoints/types.ts | 58 ++++++++++++++----- packages/sapsuccessfactors/schema.test.ts | 16 +++++ 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 3ded273a2..554a51431 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -179,6 +179,16 @@ describe('SAP SuccessFactors plugin', () => { expect(opts).toEqual(expect.objectContaining({ url: '/odata/v2/User' })); }); + it('rejects non-numeric paging before the HTTP call', async () => { + await expect(run('listUsers', { top: 'nope' })).rejects.toThrow(); + expect(mockedRequest).not.toHaveBeenCalled(); + }); + + it('rejects a response that is not an OData envelope', async () => { + mockedRequest.mockResolvedValueOnce({ garbage: true } as never); + await expect(run('listUsers', { top: 1 })).rejects.toThrow(); + }); + it('rejects User as a custom MDF entity', async () => { await expect( run('getCustomMdfObject', { custom_object: 'User' }), diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts index 8d7a264dd..fd760ff11 100644 --- a/packages/sapsuccessfactors/endpoints/types.ts +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -1,4 +1,6 @@ import { z } from 'zod'; +import type { SapRoute, SapRouteName } from './routes'; +import { sapRoutes } from './routes'; const odataQuery = { filter: z.string().optional(), @@ -12,20 +14,53 @@ const odataQuery = { const ODataQuery = z.object(odataQuery); const Empty = z.object({}).optional(); -/** OData V2 `{ d }` and V4 `{ value }` plus metadata XML/JSON. */ -export const SapResponseSchema = z.union([ +const ODataRecord = z.record(z.string(), z.unknown()); + +/** OData V2 `{ d: { results } }` or V4 `{ value }`. */ +export const SapCollectionSchema = z.union([ z .object({ - d: z.unknown().optional(), - value: z.array(z.unknown()).optional(), + d: z.object({ results: z.array(ODataRecord) }).passthrough(), }) .passthrough(), - z.string(), - z.record(z.string(), z.unknown()), - z.null(), + z.object({ value: z.array(ODataRecord) }).passthrough(), +]); + +/** OData V2 `{ d: entity }` or V4 entity with `@odata.context`. */ +export const SapEntitySchema = z.union([ + z.object({ d: ODataRecord }).passthrough(), + z + .object({ '@odata.context': z.string().min(1) }) + .passthrough() + .refine((v) => !Array.isArray(v.value), { + message: 'V4 entity must not be a collection', + }), +]); + +export const SapMetadataSchema = z.union([ + z.string().refine((s) => s.includes(' [ - key, - SapResponseSchema, - ]), -) as { - [K in keyof typeof SapsuccessfactorsEndpointInputSchemas]: typeof SapResponseSchema; -}; + sapRoutes.map((route) => [route.name, outputSchemaFor(route)]), +) as { [K in SapRouteName]: ReturnType }; export type SapsuccessfactorsEndpointOutputs = { [K in keyof typeof SapsuccessfactorsEndpointOutputSchemas]: z.infer< diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index 5a731ecfa..11b268fe4 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -80,5 +80,21 @@ describe('sapsuccessfactors schemas', () => { '', ), ).toBeDefined(); + expect( + SapsuccessfactorsEndpointOutputSchemas.listUsers.safeParse(null).success, + ).toBe(false); + expect( + SapsuccessfactorsEndpointOutputSchemas.listUsers.safeParse('oops') + .success, + ).toBe(false); + expect( + SapsuccessfactorsEndpointOutputSchemas.listUsers.safeParse({ foo: 1 }) + .success, + ).toBe(false); + expect( + SapsuccessfactorsEndpointOutputSchemas.getPerPersonById.safeParse({ + value: [{ id: '1' }], + }).success, + ).toBe(false); }); }); From 7b394ef33230bddde40f37f0e9ec677c57e7bb8c Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:30:18 +0530 Subject: [PATCH 17/18] fix(sapsuccessfactors): accept 204 empty bodies on writes --- packages/sapsuccessfactors/api.test.ts | 10 ++++++++++ packages/sapsuccessfactors/endpoints/types.ts | 7 +++---- packages/sapsuccessfactors/schema.test.ts | 9 +++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 554a51431..4b6b1ebe9 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -189,6 +189,16 @@ describe('SAP SuccessFactors plugin', () => { await expect(run('listUsers', { top: 1 })).rejects.toThrow(); }); + it('treats 204 writes as success', async () => { + mockedRequest.mockResolvedValueOnce(undefined as never); + await expect( + run('updateCalibrationSubjectRatings', { + subject_id: 'sub1', + body: { rating: 3 }, + }), + ).resolves.toBeUndefined(); + }); + it('rejects User as a custom MDF entity', async () => { await expect( run('getCustomMdfObject', { custom_object: 'User' }), diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts index fd760ff11..5d2bef2ac 100644 --- a/packages/sapsuccessfactors/endpoints/types.ts +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -45,8 +45,8 @@ export const SapMetadataSchema = z.union([ SapEntitySchema, ]); -/** DELETE may be 204 empty. */ -export const SapDeleteSchema = z.union([ +/** POST/PATCH/DELETE may be 204 empty. */ +export const SapWriteSchema = z.union([ z.undefined(), z.null(), z.object({}).strict(), @@ -55,8 +55,7 @@ export const SapDeleteSchema = z.union([ function outputSchemaFor(route: SapRoute) { if (route.path.includes('$metadata')) return SapMetadataSchema; - if (route.method === 'DELETE') return SapDeleteSchema; - if (route.method !== 'GET') return SapEntitySchema; + if (route.method !== 'GET') return SapWriteSchema; if (route.path.includes('({')) return SapEntitySchema; return SapCollectionSchema; } diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index 11b268fe4..d86fb36e4 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -96,5 +96,14 @@ describe('sapsuccessfactors schemas', () => { value: [{ id: '1' }], }).success, ).toBe(false); + expect( + SapsuccessfactorsEndpointOutputSchemas.updateCalibrationSubjectRatings.safeParse( + undefined, + ).success, + ).toBe(true); + expect( + SapsuccessfactorsEndpointOutputSchemas.listUsers.safeParse(undefined) + .success, + ).toBe(false); }); }); From 421be65389b9aa6a903f11579282ef02ff5c7c82 Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Thu, 3 Sep 2026 00:40:12 +0530 Subject: [PATCH 18/18] fix(sapsuccessfactors): per-operation OData output shapes --- packages/sapsuccessfactors/api.test.ts | 46 ++++- packages/sapsuccessfactors/endpoints/types.ts | 191 ++++++++++++++---- packages/sapsuccessfactors/schema.test.ts | 7 +- 3 files changed, 200 insertions(+), 44 deletions(-) diff --git a/packages/sapsuccessfactors/api.test.ts b/packages/sapsuccessfactors/api.test.ts index 4b6b1ebe9..15947aabb 100644 --- a/packages/sapsuccessfactors/api.test.ts +++ b/packages/sapsuccessfactors/api.test.ts @@ -9,7 +9,25 @@ import { sapsuccessfactors } from './index'; jest.mock('corsair/http', () => ({ request: jest.fn().mockResolvedValue({ - d: { results: [{ userId: 'cgrant' }] }, + d: { + results: [ + { + userId: 'cgrant', + personIdExternal: 'p1', + jobReqId: 1, + candidateId: 1, + applicationId: 1, + code: 'POS-1', + sessionId: 's1', + subjectId: 'sub1', + externalCode: '1000', + id: '1', + nominationTargetId: 'nt-1', + picklistId: 'pk1', + formContentId: 'fc1', + }, + ], + }, }), ApiError: class ApiError extends Error { constructor( @@ -212,6 +230,7 @@ describe('SAP SuccessFactors plugin', () => { }); it('deletes NominationTarget with userId and isPoolNomination', async () => { + mockedRequest.mockResolvedValueOnce(undefined as never); await run('deleteNominationPositionTalentPool', { nominationTargetId: 'nt-1', userId: 'cgrant', @@ -271,6 +290,31 @@ describe('SAP SuccessFactors plugin', () => { const input = fixtures[name]; expect(input).toBeDefined(); SapsuccessfactorsEndpointInputSchemas[name].parse(input); + const route = getSapRoute(name); + const record = { + userId: 'cgrant', + personIdExternal: 'p1', + jobReqId: 1, + candidateId: 1, + applicationId: 1, + code: 'CICO1', + sessionId: 's1', + subjectId: 'sub1', + externalCode: '1000', + id: '1', + nominationTargetId: 'nt-1', + picklistId: 'pk1', + formContentId: 'fc1', + }; + mockedRequest.mockResolvedValueOnce( + (route.path.includes('$metadata') + ? '' + : route.method === 'GET' && route.path.includes('({') + ? { d: record } + : route.method === 'GET' + ? { d: { results: [record] } } + : { d: record }) as never, + ); await run(name, input); expect(lastCall().method).toBe(method); expect(lastCall().url).toMatch(/^\//); diff --git a/packages/sapsuccessfactors/endpoints/types.ts b/packages/sapsuccessfactors/endpoints/types.ts index 5d2bef2ac..a722d3e13 100644 --- a/packages/sapsuccessfactors/endpoints/types.ts +++ b/packages/sapsuccessfactors/endpoints/types.ts @@ -1,6 +1,15 @@ import { z } from 'zod'; -import type { SapRoute, SapRouteName } from './routes'; -import { sapRoutes } from './routes'; +import { + SapsuccessfactorsCandidateEntity, + SapsuccessfactorsEmploymentEntity, + SapsuccessfactorsJobApplicationEntity, + SapsuccessfactorsJobRequisitionEntity, + SapsuccessfactorsPersonalEntity, + SapsuccessfactorsPersonEntity, + SapsuccessfactorsPositionEntity, + SapsuccessfactorsUserEntity, +} from '../schema/database'; +import type { SapRouteName } from './routes'; const odataQuery = { filter: z.string().optional(), @@ -14,52 +23,87 @@ const odataQuery = { const ODataQuery = z.object(odataQuery); const Empty = z.object({}).optional(); -const ODataRecord = z.record(z.string(), z.unknown()); +const rec = (shape: z.ZodRawShape) => z.object(shape).catchall(z.unknown()); +const Id = z.union([z.string(), z.number()]); -/** OData V2 `{ d: { results } }` or V4 `{ value }`. */ -export const SapCollectionSchema = z.union([ - z - .object({ - d: z.object({ results: z.array(ODataRecord) }).passthrough(), - }) - .passthrough(), - z.object({ value: z.array(ODataRecord) }).passthrough(), -]); +const CalibrationSessionOut = rec({ sessionId: z.string() }); +const CalibrationSubjectOut = rec({ subjectId: z.string() }); +const Onb2ProcessOut = rec({ userId: z.string() }); +const FeedbackOut = rec({ id: Id }); +const FeedbackRequestOut = rec({ id: Id }); +const NominationOut = rec({ nominationTargetId: z.string() }); +const TalentPoolOut = rec({ id: Id }); +const ApplicationInterviewOut = rec({ applicationId: Id }); +const InterviewAssessmentOut = rec({ applicationId: Id }); +const ScreeningQuestionOut = rec({ jobReqId: Id }); +const FoBusinessUnitOut = rec({ externalCode: z.string() }); +const FoCompanyOut = rec({ externalCode: z.string() }); +const FoCostCenterOut = rec({ externalCode: z.string() }); +const FoDepartmentOut = rec({ externalCode: z.string() }); +const FoJobCodeOut = rec({ externalCode: z.string() }); +const FoJobFunctionOut = rec({ externalCode: z.string() }); +const FoLocationOut = rec({ externalCode: z.string() }); +const FoPayGroupOut = rec({ externalCode: z.string() }); +const MdfOut = rec({ externalCode: z.string().optional() }); +const PicklistOut = rec({ picklistId: Id }); +const PicklistOptionOut = rec({ id: Id }); +const GoalPlanOut = rec({ id: Id }); +const GoalOut = rec({ id: Id }); +const BackgroundEducationOut = rec({ userId: z.string() }); +const BackgroundMobilityOut = rec({ userId: z.string() }); +const EmploymentTerminationOut = rec({ userId: z.string() }); +const PayCompOut = rec({ userId: z.string() }); +const WorkOrderOut = rec({ userId: z.string() }); +const FormContentOut = rec({ formContentId: Id }); +const LearningActivityOut = rec({ userId: z.string() }); +const ActionOut = rec({ message: z.string().optional() }); +const EmployeeTimeOut = rec({ userId: z.string() }); +const EmployeeTimeSheetOut = rec({ userId: z.string() }); +const TemporaryTimeOut = rec({ userId: z.string() }); +const TimeAccountSnapshotOut = rec({ userId: z.string() }); +const ClockInClockOutGroupOut = rec({ code: z.string() }); -/** OData V2 `{ d: entity }` or V4 entity with `@odata.context`. */ -export const SapEntitySchema = z.union([ - z.object({ d: ODataRecord }).passthrough(), - z - .object({ '@odata.context': z.string().min(1) }) - .passthrough() - .refine((v) => !Array.isArray(v.value), { - message: 'V4 entity must not be a collection', - }), -]); +function collectionOf(item: T) { + return z.union([ + z + .object({ + d: z.object({ results: z.array(item) }).passthrough(), + }) + .passthrough(), + z.object({ value: z.array(item) }).passthrough(), + ]); +} + +function entityOf(item: T) { + return z.union([ + z.object({ d: item }).passthrough(), + z + .object({ '@odata.context': z.string().min(1) }) + .passthrough() + .and(item) + .refine((v) => !Array.isArray((v as { value?: unknown }).value), { + message: 'V4 entity must not be a collection', + }), + ]); +} + +function writeOf(item: T) { + return z.union([ + z.undefined(), + z.null(), + z.object({}).strict(), + entityOf(item), + ]); +} export const SapMetadataSchema = z.union([ z.string().refine((s) => s.includes('; }; -export const SapsuccessfactorsEndpointOutputSchemas = Object.fromEntries( - sapRoutes.map((route) => [route.name, outputSchemaFor(route)]), -) as { [K in SapRouteName]: ReturnType }; +export const SapsuccessfactorsEndpointOutputSchemas = { + approveCalibrationSession: writeOf(CalibrationSessionOut), + getCalibrationSessionById: entityOf(CalibrationSessionOut), + getCalibrationSessions: collectionOf(CalibrationSessionOut), + getOdataMetadataCalibSessionService: SapMetadataSchema, + getCalibrationSubjectById: entityOf(CalibrationSubjectOut), + getCalibrationSubjectRatings: collectionOf(CalibrationSubjectOut), + updateCalibrationSubjectRatings: writeOf(CalibrationSubjectOut), + createOnboardee: writeOf(SapsuccessfactorsUserEntity), + getOnb2Process: collectionOf(Onb2ProcessOut), + getOdataMetadataOnboardingAddl: SapMetadataSchema, + updateInternalUsernameNewHiresAfter: writeOf(SapsuccessfactorsUserEntity), + createAFeedbackRequest: writeOf(FeedbackRequestOut), + getFeedbackRecordsServiceAvailable: collectionOf(FeedbackOut), + getPendingFeedbackRequestsFeedback: collectionOf(FeedbackRequestOut), + giveFeedbackOrRespondToAFeedbackRequest: writeOf(FeedbackOut), + refreshMetadataContFeedbackService: writeOf(ActionOut), + createUpdateSuccessorNomination: writeOf(NominationOut), + deleteNominationPositionTalentPool: writeOf(NominationOut), + getOdataMetadataForNominationService: SapMetadataSchema, + getTalentPool: collectionOf(TalentPoolOut), + getApplicationInterview: collectionOf(ApplicationInterviewOut), + getInterviewOverallAssessment: collectionOf(InterviewAssessmentOut), + getJobApplication: collectionOf(SapsuccessfactorsJobApplicationEntity), + getJobRequisition: collectionOf(SapsuccessfactorsJobRequisitionEntity), + getJobReqScreeningQuestion: collectionOf(ScreeningQuestionOut), + listCandidates: collectionOf(SapsuccessfactorsCandidateEntity), + getFoBusinessUnit: collectionOf(FoBusinessUnitOut), + getFoCompany: collectionOf(FoCompanyOut), + getFoCostCenter: collectionOf(FoCostCenterOut), + getFoDepartment: collectionOf(FoDepartmentOut), + getFoJobCode: collectionOf(FoJobCodeOut), + getFoJobFunction: collectionOf(FoJobFunctionOut), + getFoLocation: collectionOf(FoLocationOut), + getFoPayGroup: collectionOf(FoPayGroupOut), + getPosition: collectionOf(SapsuccessfactorsPositionEntity), + getCustomMdfObject: collectionOf(MdfOut), + getPicklist: collectionOf(PicklistOut), + getPicklistOption: collectionOf(PicklistOptionOut), + getCurrentUser: collectionOf(SapsuccessfactorsUserEntity), + getOdataUserMetadata: SapMetadataSchema, + listUsers: collectionOf(SapsuccessfactorsUserEntity), + getPerPersonById: entityOf(SapsuccessfactorsPersonEntity), + listPerPerson: collectionOf(SapsuccessfactorsPersonEntity), + getPerPersonal: collectionOf(SapsuccessfactorsPersonalEntity), + getBackgroundEducation: collectionOf(BackgroundEducationOut), + getBackgroundMobility: collectionOf(BackgroundMobilityOut), + listEmpEmployment: collectionOf(SapsuccessfactorsEmploymentEntity), + getEmpEmploymentTermination: collectionOf(EmploymentTerminationOut), + getWorkOrder: collectionOf(WorkOrderOut), + getEmpPayCompRecurring: collectionOf(PayCompOut), + getEmpPayCompNonRecurring: collectionOf(PayCompOut), + getGoalPlanTemplate: collectionOf(GoalPlanOut), + getGoalsByPlan: collectionOf(GoalOut), + getFormContent: collectionOf(FormContentOut), + createLearningActivitiesBulk: writeOf(LearningActivityOut), + getCdpLearningMetadata: SapMetadataSchema, + refreshCdpLearningMetadata: writeOf(ActionOut), + getEmployeeTime: collectionOf(EmployeeTimeOut), + getEmployeeTimesheet: collectionOf(EmployeeTimeSheetOut), + getTemporaryTimeInformation: collectionOf(TemporaryTimeOut), + getTimeAccountSnapshot: collectionOf(TimeAccountSnapshotOut), + getOdataMetadataClockInclockOut: SapMetadataSchema, + queryAllAvailableClockClockOut: collectionOf(ClockInClockOutGroupOut), + queryClockClockOutGroupCodeTime: entityOf(ClockInClockOutGroupOut), +} as const satisfies Record; export type SapsuccessfactorsEndpointOutputs = { [K in keyof typeof SapsuccessfactorsEndpointOutputSchemas]: z.infer< diff --git a/packages/sapsuccessfactors/schema.test.ts b/packages/sapsuccessfactors/schema.test.ts index d86fb36e4..2840c2617 100644 --- a/packages/sapsuccessfactors/schema.test.ts +++ b/packages/sapsuccessfactors/schema.test.ts @@ -91,9 +91,14 @@ describe('sapsuccessfactors schemas', () => { SapsuccessfactorsEndpointOutputSchemas.listUsers.safeParse({ foo: 1 }) .success, ).toBe(false); + expect( + SapsuccessfactorsEndpointOutputSchemas.listUsers.safeParse({ + d: { results: [{ jobReqId: 1 }] }, + }).success, + ).toBe(false); expect( SapsuccessfactorsEndpointOutputSchemas.getPerPersonById.safeParse({ - value: [{ id: '1' }], + d: { userId: 'cgrant' }, }).success, ).toBe(false); expect(