# rr-rome-client A minimal Node.js wrapper for Reynolds & Reynolds (Rome/RIH) STAR `ProcessMessage` over SOAP, with WS-Security UsernameToken. ## Contents - Overview - Install - Supported Node Version - Peer / External Dependencies - Quick Start - Configuration & Environment Variables - Client API Methods - RRResult Structure - Types & IntelliSense - Errors & Validation - Retry Strategy - Debug / Dump Flags - Live Test Runner (`scripts/run-live.mjs`) - Bundling & Upload Helper (`scripts/bundle-for-upload.mjs`) - XML Templates & XSDs - Logging - Build & Development - Design Notes / Non-goals - License ## Overview `rr-rome-client` builds and sends STAR XML payloads (Customer, Service Vehicle, Combined Search, Advisors, Parts, BSM Repair Orders) inside a SOAP envelope using the STAR Transport `ProcessMessage` operation. It applies a WS-Security UsernameToken header and provides: - High-level `RRClient` methods for each supported Rome operation. - JSDoc typedefs for all payloads and response shapes (usable in JS and TS). - Structured parsing of response status blocks and operation-specific data. - Automatic BODId & CreationDateTime generation, with ability to override. - Basic input validation and error classification. - Exponential backoff + jitter for retryable transport/vendor lock scenarios. ## Install ```bash npm i rr-rome-client ``` (You may also need peer dependencies; see below.) ## Supported Node Version `package.json` declares `"engines": { "node": ">=22.0.0" }`. The build targets modern Node 22 features (native ESM, improved performance). Earlier Node versions are not officially supported/tested. ## Peer / External Dependencies Rollup externalizes runtime libraries (they are listed in `dependencies` but not bundled). Ensure these are available in your environment: Required: - axios – HTTP transport - fast-xml-parser – STAR XML parsing - mustache – XML templating - uuid – BODId generation Optional (only if you use env loader helpers or live scripts): - dotenv - dotenv-expand Install (versions per `package.json`): ```bash npm install axios@^1.7.7 fast-xml-parser@^4.5.0 mustache@^4.2.0 uuid@^9.0.1 # Optional env loader npm install dotenv@^17.2.3 dotenv-expand@^12.0.3 ``` TypeScript users (optional): ```bash npm install -D @types/node ``` Browser bundling is not officially supported; you would need polyfills for core modules if attempting. ## Quick Start ```js import { RRClient } from 'rr-rome-client'; import { loadEnv } from 'rr-rome-client/src/util/config.js'; // optional helper // Load routing & credentials from environment variables (see below) const { baseUrl, username, password, routing } = loadEnv(); const client = new RRClient({ baseUrl, username, password }); // Minimal createRepairOrder example const result = await client.createRepairOrder({ customerNo: '12345', departmentType: 'S', // DeptType vin: '1ABCDEF2GHIJ34567', outsdRoNo: 'EXT-RO-99' }, { routing }); if (result.success) { console.log('RO status:', result.data); // {status, date, time, outsdRoNo, dmsRoNo, errorMessage} } else { console.error('Failure:', result.statusBlocks?.transaction); } ``` ## Configuration & Environment Variables You can manually provide configuration or use the helper `loadEnv(env)` in `src/util/config.js`. Recognized environment variables: - RR_BASE_URL – SOAP endpoint URL (required) - RR_USERNAME – WS-Security UsernameToken username (required) - RR_PASSWORD – WS-Security UsernameToken password (required) - RR_DEALER_NUMBER – DealerNumber for Destination (required per call) - RR_STORE_NUMBER – StoreNumber (optional) - RR_AREANUMBER – AreaNumber (optional) Example `.env`: ``` RR_BASE_URL=https://rome.example.com/soap RR_USERNAME=integratorUser RR_PASSWORD=superSecret RR_DEALER_NUMBER=1234 RR_STORE_NUMBER=01 RR_AREANUMBER=1 ``` `loadEnv()` returns `{ baseUrl, username, password, routing: { dealerNumber, storeNumber, areaNumber } }`. Per-call options object shape (`CallOptions`): ```js { routing: { dealerNumber: '1234', storeNumber?: '01', areaNumber?: '1' }, envelope?: { bodId?, creationDateTime?, sender?: { component?, task?, referenceId? } } } ``` If omitted, `RRClient` auto-generates `bodId` (UUID) and `creationDateTime` when sending. ## Client API Methods Each method returns a `Promise>` where `T` is operation-specific data (or array). `success` is true for vendor SUCCESS and NO_MATCH results; FAIL triggers an `RRVendorStatusError` exception before a `RRResult` is returned. All methods require `opts.routing.dealerNumber`. ### combinedSearch(payload: CombinedSearchQuery, opts) Search customer + service vehicle combinations by exactly one criterion: `phone | license | vin | name | nameRecId | stkNo` plus optional `make`, `model`, `year`, `maxResults` (capped at 50). Minimal: ```js const res = await client.combinedSearch({ kind: 'vin', vin: '12345' }, { routing }); res.data; // Array ``` Errors: Throws `RRValidationError` if missing or multiple criteria. ### insertCustomer(payload: InsertCustomerPayload, opts) Insert a customer record. Required: `lastName` (or `customerName`). If individual (`ibFlag='I'` or inferred by presence of `firstName`), then `firstName` required. ```js const res = await client.insertCustomer({ firstName: 'Jane', lastName: 'Doe', phones:[{number:'5551234567'}] }, { routing }); res.data; // { dmsRecKey, status, statusCode } ``` ### updateCustomer(payload: UpdateCustomerPayload, opts) Update existing customer by `nameRecId` plus required `ibFlag`. Other fields optional. ```js const res = await client.updateCustomer({ nameRecId:'998877', ibFlag:'I', lastName:'Doe' }, { routing }); ``` ### insertServiceVehicle(payload: InsertServiceVehiclePayload, opts) Insert a service vehicle linked to a customer. Required: `vin`, `vehicleServInfo.customerNo`. ```js const res = await client.insertServiceVehicle({ vin:'1HGCM82633A004352', vehicleServInfo:{ customerNo:'12345' } }, { routing }); res.data; // { status, statusCode } ``` ### getAdvisors(payload: GetAdvisorsParams, opts) Fetch advisors for a department. Department values normalized: S/P/B or long names. ```js const res = await client.getAdvisors({ department:'SERVICE' }, { routing }); res.data; // AdvisorRow[] ``` ### createRepairOrder(payload: CreateRepairOrderPayload, opts) Required: `customerNo`, `departmentType`, `vin`, `outsdRoNo`. Advisor optional. Complex nested labor/parts/misc blocks supported via payload. ```js const res = await client.createRepairOrder({ customerNo:'12345', departmentType:'S', vin:'1ABCDEF2GHIJ34567', outsdRoNo:'EXT-RO-99' }, { routing }); res.data; // { status, date, time, outsdRoNo, dmsRoNo, errorMessage } ``` ### updateRepairOrder(payload: UpdateRepairOrderPayload, opts) Required: `finalUpdate ('Y'|'N')`, `outsdRoNo`. May include `roNo` and nested sections. ```js const res = await client.updateRepairOrder({ finalUpdate:'N', outsdRoNo:'EXT-RO-99' }, { routing }); ``` ### getParts(payload: GetPartsParams, opts) Required: `roNumber` (internal ERA RO number). ```js const res = await client.getParts({ roNumber:'938275' }, { routing }); res.data; // PartRow[] ``` ## Payload Schema Reference Comprehensive field-level summary sourced from `src/types.js`, operation builders, and validation logic. Types reflect accepted JS types (string|number where applicable). Constraints list enumerations, inference rules, and validation notes. Required = must be supplied by caller (or inferred automatically). Optional fields omitted become absent in generated XML. ### CombinedSearchQuery Only one criterion permitted; `maxResults` capped at 50. | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | kind | 'phone'|'license'|'vin'|'name'|'nameRecId'|'stkNo' | Yes | Determines which single criterion block is emitted | | phone | string\|number\|{phone:string} | Conditionally (if kind==='phone') | Value mapped to `` | | license | string\|number\|{license:string} | Conditionally (kind==='license') | Value mapped to `` | | vin | string\|number\|{vin:string} | Conditionally (kind==='vin') | Value mapped to `` (partial VIN allowed) | | name | {fname,lname,mname} or {name} | Conditionally (kind==='name') | Either FullName triple or LName only; must supply all three for FullName | | nameRecId | string\|number\|{custId:string}|{nameRecId:string} | Conditionally (kind==='nameRecId') | Emits `` | | stkNo | string\|number\|{stkNo:string} | Conditionally (kind==='stkNo') | Emits `` | | maxResults | number | No | Capped at 50 (default 50) -> `MaxRecs` attribute | | make | string | No | Defaults 'ANY' -> `VehData MakePfx` | | model | string\|number | No | Defaults 'ANY' -> `VehData Model` | | year | string\|number | No | Defaults 'ANY' -> `VehData Year` | ### InsertCustomerPayload `ibFlag` inferred as 'I' if `firstName` present, else 'B'. Business requires `lastName` / `customerName`; individual requires `firstName` + last name. | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | ibFlag | 'I'|'B' | Auto / For update must supply | Inferred if omitted (firstName present => 'I') | | customerType | 'R'|'W'|'I'|'Retail'|'Wholesale'|'Internal' | No | Normalized to 'R','W','I'; must be one of listed | | createdBy | string | No | Optional CreatedBy attribute | | customerName | string | Conditional | Alias for `lastName` when business | | lastName | string | Yes (unless `customerName` provided) | Required base name; sanitized to A-Z0-9 space | | firstName | string | Required when ibFlag='I' | Sanitized; required for individuals | | midName | string | No | Sanitized | | salut | string | No | Sanitized | | suffix | string | No | Sanitized | | addresses | CustomerAddress[] | No | Each entry requires `line1`; Type defaults 'P' | | phones | CustomerPhone[] | No | Each entry requires `number`; Type defaults 'H' | | emails | CustomerEmail[] | No | First entry used -> `` | | personal.gender | 'M'|'F'|'U' | No | Optional | | personal.otherName | string | No | Sanitized alnum/space | | personal.anniversaryDate | string | No | Included if non-empty | | personal.employerName | string | No | Sanitized | | personal.employerPhone | string | No | Raw string | | personal.occupation | string | No | Sanitized | | personal.optOut | string | No | Pass-through | | personal.optOutUse | string | No | Pass-through | | personal.birthDates[].type | 'P'|'S' | No | Defaults 'P'; entry must have `date` | | personal.birthDates[].date | string | Conditional | Included only if non-empty | | personal.ssns[].type | 'P'|'S' | No | Defaults 'P'; entry must have `ssn` | | personal.ssns[].ssn | string | Conditional | Included only if non-empty | | personal.driver.type | 'P'|'S' | No | Defaults 'P' | | personal.driver.licenseNumber | string | Conditional | Required to emit DriverInfo | | personal.driver.licenseState | string | No | Optional | | personal.driver.licenseExpDate | string | No | Optional | | personal.children[].name | string | No | Sanitized; optional list | | dms.taxExemptNum | string | No | Optional | | dms.salesTerritory | string | No | Optional | | dms.deliveryRoute | string | No | Optional | | dms.salesmanNum | string | No | Optional | | dms.lastContactMethod | string | No | Optional | | dms.followups[].type | string | Conditional | Must have both type & value | | dms.followups[].value | string | Conditional | Must have both type & value | ### UpdateCustomerPayload Extends InsertCustomerPayload plus: | Field | Type | Required | Constraints | |-------|------|----------|-------------| | nameRecId | string\|number | Yes | Required identifier | | ibFlag | 'I'|'B' | Yes | Must be explicitly provided on update | ### InsertServiceVehiclePayload | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | vin | string | Yes | Must be provided | | modelDesc | string | No | Optional attribute | | carline | string | No | Optional attribute | | extClrDesc | string | No | Optional attribute | | intClrDesc | string | No | Optional attribute | | trimDesc | string | No | Optional attribute | | bodyStyle | string | No | Optional attribute | | engineDesc | string | No | Optional attribute | | transDesc | string | No | Optional attribute | | year | string\|number | No | Emits `` element if present | | odometer | string\|number | No | Emits `` | | odometerUnits | string | No | Emits `` | | vehicleDetail.licNo | string | No | Emits `` | | vehicleServInfo.customerNo | string\|number | Yes | Required; becomes `CustomerNo` attribute | | vehicleServInfo.salesmanNo | string\|number | No | Optional element | | vehicleServInfo.inServiceDate | string\|number | No | Optional element | | vehicleServInfo.mileage | string\|number | No | Optional element | | vehicleServInfo.teamCode | string | No | Optional element | | vehicleServInfo.vehExtWarranty.contractNumber | string | Conditional | Included if any warranty field present | | vehicleServInfo.vehExtWarranty.expirationDate | string | Conditional | "" excluded | | vehicleServInfo.vehExtWarranty.expirationMileage | string\|number | Conditional | "" excluded | | vehicleServInfo.advisor.contactInfo.nameRecId | string\|number | No | Advisor block included only if provided | ### CreateRepairOrderPayload | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | customerNo | string\|number | Yes | CustNo | | departmentType | string\|number | Yes | DeptType | | vin | string | Yes | Vin | | outsdRoNo | string\|number | Yes | External RO identifier | | advisorNo | string\|number | No | AdvNo | | tagNo | string\|number | No | TagNo | | mileageIn | string\|number | No | MileageIn | | roComment | string | No | `` | | estimate.parts | string\|number | No | EstPartsAmt | | estimate.labor | string\|number | No | EstLaborAmt | | estimate.total | string\|number | No | EstTotalAmt | | tax.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration validated | | tax.taxCode | string | No | TaxCode | | tax.txblGrossAmt | string\|number | No | TxblGrossAmt | | tax.grossTaxAmt | string\|number | No | GrossTaxAmt | | rolabor.ops[].opCode | string | No | Optional | | rolabor.ops[].jobNo | string\|number | No | Optional | | rolabor.ops[].custPayTypeFlag | string | No | Freeform (not validated) | | rolabor.ops[].warrPayTypeFlag | string | No | Freeform | | rolabor.ops[].intrPayTypeFlag | string | No | Freeform | | rolabor.ops[].custTxblNtxblFlag | 'T'|'N' | No | Enumerated & validated | | rolabor.ops[].warrTxblNtxblFlag | 'T'|'N' | No | Enumerated & validated | | rolabor.ops[].intrTxblNtxblFlag | 'T'|'N' | No | Enumerated & validated | | rolabor.ops[].vlrCode | string | No | Optional | | rolabor.ops[].bill.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration | | rolabor.ops[].bill.jobTotalHrs | string\|number | No | Optional | | rolabor.ops[].bill.billTime | string\|number | No | Optional | | rolabor.ops[].bill.billRate | string\|number | No | Optional | | rolabor.ops[].ccc.cause | string | No | Optional | | rolabor.ops[].ccc.complaint | string | No | Optional | | rolabor.ops[].ccc.correction | string | No | Optional | | rolabor.ops[].amount.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration | | rolabor.ops[].amount.amtType | string | No | Optional | | rolabor.ops[].amount.custPrice | string\|number | No | Optional | | rolabor.ops[].amount.totalAmt | string\|number | No | Optional | | ropart.jobs[].opCode | string | No | Optional | | ropart.jobs[].jobNo | string\|number | No | Optional | | ropart.jobs[].lines[].partNo | string | No | Optional | | ropart.jobs[].lines[].partNoDesc | string | No | Optional | | ropart.jobs[].lines[].partQty | string\|number | No | Emits QtyOrd | | ropart.jobs[].lines[].sale | string\|number | No | Sale | | ropart.jobs[].lines[].cost | string\|number | No | Cost | | ropart.jobs[].lines[].addDeleteFlag | string | No | AddDeleteFlag | | rogg.ops[].lines[].breakOut | string | No | Optional | | rogg.ops[].lines[].itemType | 'G'|'P'|'S'|'F' | No | Enumerated & validated | | rogg.ops[].lines[].itemDesc | string | No | Optional | | rogg.ops[].lines[].custQty | string\|number | No | Optional | | rogg.ops[].lines[].warrQty | string\|number | No | Optional | | rogg.ops[].lines[].intrQty | string\|number | No | Optional | | rogg.ops[].lines[].custPayTypeFlag | string | No | Optional | | rogg.ops[].lines[].warrPayTypeFlag | string | No | Optional | | rogg.ops[].lines[].intrPayTypeFlag | string | No | Optional | | rogg.ops[].lines[].custTxblNtxblFlag | 'T'|'N' | No | Enumerated | | rogg.ops[].lines[].warrTxblNtxblFlag | 'T'|'N' | No | Enumerated | | rogg.ops[].lines[].intrTxblNtxblFlag | 'T'|'N' | No | Enumerated | | rogg.ops[].lines[].amount.payType | 'All'|'Cust'|'Intr'|'Warr' | No | Enumeration | | rogg.ops[].lines[].amount.amtType | string | No | Optional | | rogg.ops[].lines[].amount.custPrice | string\|number | No | Optional | | rogg.ops[].lines[].amount.dlrCost | string\|number | No | Optional | | romisc.ops[].lines[].miscCode | string | No | Optional | | romisc.ops[].lines[].custPayTypeFlag | string | No | Optional | | romisc.ops[].lines[].warrPayTypeFlag | string | No | Optional | | romisc.ops[].lines[].intrPayTypeFlag | string | No | Optional | | romisc.ops[].lines[].custTxblNtxblFlag | 'T'|'N' | No | Enumerated | | romisc.ops[].lines[].warrTxblNtxblFlag | 'T'|'N' | No | Enumerated | | romisc.ops[].lines[].intrTxblNtxblFlag | 'T'|'N' | No | Enumerated | | romisc.ops[].lines[].codeAmt | string\|number | No | Optional | ### UpdateRepairOrderPayload Adds required `finalUpdate` and supports most Create fields + mileageOut. | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | finalUpdate | 'Y'|'N' | Yes | Must be 'Y' or 'N' (validated) | | outsdRoNo | string\|number | Yes | External RO identifier (validation requires present) | | roNo | string\|number | No | Optional internal RoNo | | mileageOut | string\|number | No | MileageOut | | (other fields) | see CreateRepairOrderPayload | Conditional | Same validations apply | ### GetAdvisorsParams | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | department | 'S'|'P'|'B'|'SERVICE'|'PARTS'|'BODY'|'BODYSHOP'|'BODY SHOP' | Yes | Normalized to S/P/B | | advisorNumber | string\|number | No | Optional filter | | maxResults | number | Ignored | Not in XSD (builder ignores) | ### GetPartsParams | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | roNumber | string\|number | Yes | Required; becomes `` | ### Common Routing & Envelope Fields | Field | Type | Required | Constraints / Notes | |-------|------|----------|----------------------| | routing.dealerNumber | string | Yes | Required for all requests (Destination) | | routing.storeNumber | string | No | Optional Destination StoreNumber | | routing.areaNumber | string | No | Optional Destination AreaNumber | | envelope.bodId | string | Auto | UUID generated if omitted | | envelope.creationDateTime | Date\|string | Auto | Uses current time; formatted without milliseconds | | envelope.sender.component | string | No | Defaults 'Rome' if omitted | | envelope.sender.task | string | No | Op-specific defaults (e.g. 'CU','SV','BSMRO','CVC','RCT') | | envelope.sender.referenceId | string | No | Op-specific defaults ('Insert','Update','Query') | ### AdvisorRow (response convenience) | Field | Type | Notes | |-------|------|-------| | advisorId | string\|number\|undefined | AdvisorNumber attribute | | firstName | string\|undefined | FirstName attribute | | lastName | string\|undefined | LastName attribute | | department | 'S'|'P'|'B'|undefined | Normalized department passed through | ### PartRow (response convenience) | Field | Type | Notes | |-------|------|-------| | partNumber | string\|undefined | PartNumber | | partDescription | string\|undefined | PartDescription | | quantityOrdered | string\|number\|undefined | QuantityOrdered | | quantityShipped | string\|number\|undefined | QuantityShipped | | price | string\|number\|undefined | Price | | cost | string\|number\|undefined | Cost | | processedFlag | string\|undefined | ProcessedFlag | | addOrDelete | string\|undefined | AddOrDelete | ### CombinedSearch Response Shapes Hierarchical breakdown of the parsed array returned under `RRResult.data` for `combinedSearch` (each entry is a `CombinedSearchBlock`). Attribute-centric; absent fields are simply omitted. #### CombinedSearchBlock | Field | Type | Notes | |-------|------|-------| | NameContactId | CombinedSearchNameContactId | Customer name/contact composite | | ServVehicle | CombinedSearchServVehicle[] | Zero or more service vehicle blocks | | Message | CombinedSearchMessage[] | Optional informational messages | #### NameContactId | Field | Type | Notes | |-------|------|-------| | NameId | CombinedSearchNameId | Includes identifiers & individual/business name choice | | Address | Object[] | Each has address-related attributes (lines, city, state, zip, etc.) | | ContactOptions | Object[] | Arbitrary contact option attributes as present | | Phone | Object[] | Each phone entry carries attributes like Type, Num, Ext | | Email | Object[] | Each email entry carries attributes (MailTo, etc.) | #### NameId | Field | Type | Notes | |-------|------|-------| | NameRecId | string\|number | Identifier used for subsequent operations | | IBFlag | 'I'|'B' | Individual or Business flag | | IndName | Object | Present for individual: attributes like FName, LName, MName | | BusName | Object | Present for business: attributes like Name (business name) | #### ServVehicle (CombinedSearchServVehicle) | Field | Type | Notes | |-------|------|-------| | Vehicle | CombinedSearchVehicle | VIN + descriptive attributes | | VehicleServInfo | CombinedSearchVehicleServInfo | Service info attributes & nested warranty/advisor/comments | #### Vehicle (CombinedSearchVehicle) | Attribute | Type | Notes | |-----------|------|-------| | Vin | string | Vehicle identification number (may be partial in search results) | | VehicleMake | string | Make code / description | | VehicleYr | string\|number | Year | | MdlNo | string | Model code | | ModelDesc | string | Model description | | Carline | string | Carline description | | ExtClrDesc | string | Exterior color | | IntClrDesc | string | Interior color | | MakeName | string | Full make name | | VehicleDetail.LicNo | string | License plate (if present) | #### VehicleServInfo (CombinedSearchVehicleServInfo) | Attribute | Type | Notes | |-----------|------|-------| | CustomerNo | string\|number | Linked customer number | | SalesmanNo | string\|number | Optional salesman number | | InServiceDate | string\|number | Date vehicle placed in service | | Mileage | string\|number | Current mileage | | TeamCode | string | Team/department code | | VehExtWarranty.ContractNumber | string | Extended warranty contract number | | VehExtWarranty.ExpirationDate | string | Warranty expiration date | | VehExtWarranty.ExpirationMileage | string\|number | Warranty mileage limit | | Advisor.ContactInfo.NameRecId | string\|number | Advisor reference (if present) | | VehServComments[] | string[] | Freeform service comments (array of raw text) | #### Message (CombinedSearchMessage) | Field | Type | Notes | |-------|------|-------| | MessageNo | string\|number | Optional message number (if provided) | | Text | string | Message text content | ### CustomerResponseData Returned as `result.data` for `insertCustomer` / `updateCustomer`. | Field | Type | Notes | |-------|------|-------| | dmsRecKey | string\|undefined | DMS record key identifier (if provided in TransStatus) | | status | string\|undefined | Vendor status string | | statusCode | string\|undefined | Vendor status code | ### ServiceVehicleResponseData Returned as `result.data` for `insertServiceVehicle`. | Field | Type | Notes | |-------|------|-------| | status | string\|undefined | GenTransStatus Status | | statusCode | string\|undefined | GenTransStatus StatusCode | ### RepairOrderData Returned as `result.data` for `createRepairOrder` / `updateRepairOrder` (parsed from RoRecordStatus). | Field | Type | Notes | |-------|------|-------| | status | string\|undefined | RoRecordStatus Status | | date | string\|undefined | Date attribute/text | | time | string\|undefined | Time attribute/text | | outsdRoNo | string\|undefined | External RO number (OutsdRoNo) | | dmsRoNo | string\|undefined | Internal DMS RO number (DMSRoNo) | | errorMessage | string\|undefined | ErrorMessage if provided | ## RRResult Structure Each successful call resolves to: ```ts interface RRResult { success: boolean; // SUCCESS or NO_MATCH data?: T; // op-specific parsed convenience data parsed: any; // entire parsed STAR payload root xml: { request: string; response: string }; // raw SOAP envelopes statusBlocks?: { transaction?: {status,statusCode,message}; roRecord?: {status,date,time,outsdRoNo,dmsRoNo,errorMessage} }; applicationArea?: any; // raw ApplicationArea node } ``` Use `statusBlocks.transaction` for generic status; `data` for normalized op output. `Convenience data` refers to the distilled, operation-specific subset placed on `RRResult.data` by a dedicated `postParse` function (e.g., extracting only RoRecordStatus identifiers or customer DMS keys). It is intentionally smaller and flatter than `RRResult.parsed`, which contains the entire parsed STAR payload tree. Use `data` for common identifiers/status checks; fall back to `parsed` when you need full raw XML-derived detail. ## Types & IntelliSense Rich JSDoc typedefs ship with the package. - ESM: `import { RRClient } from 'rr-rome-client';` optionally import `'rr-rome-client/types'` to prompt editor indexing. - CJS: `const { RRClient } = require('rr-rome-client'); require('rr-rome-client/types');` Selected typedef categories (see `src/types.js`): - Routing / Envelope / CallOptions - Customer / Service Vehicle / Repair Order payload blocks - Combined Search structures - Advisor and Parts row shapes - `RRResult` generic helper The build also emits a TypeScript declaration bundle (`dist/types/index.d.ts`) generated via `tsconfig.types.json` (processing only `src/types.js`). ## Errors & Validation Three custom error classes (`src/errors.js`): - `RRTransportError` – Non-2xx HTTP status or network failure; `meta.status` / `meta.body` may be attached. - `RRVendorStatusError` – Vendor FAIL status (non-success & not NO_MATCH). Includes `meta.status` (raw status object) and full response XML. `retryable` may be set (currently determined by vendor message lock wording or explicit flag). - `RRValidationError` – Input validation failures (missing required fields, invalid enumeration values, etc.). Enumerated validations (examples): - Customer: `ibFlag`, required `firstName` if individual, allowed `customerType` values. - Service Vehicle: mandatory `vin` and `vehicleServInfo.customerNo`. - Repair Orders: required header fields; enumerations for tax pay type (`All|Cust|Intr|Warr`), taxable flags (`T|N`), item types (`G|P|S|F`). Error Handling Example: ```js try { await client.createRepairOrder(/* ... */); } catch (e) { if (e instanceof RRVendorStatusError) { console.error('Vendor fail', e.meta.status); } else if (e instanceof RRValidationError) { console.error('Bad input', e.message); } else if (e instanceof RRTransportError) { console.error('HTTP/Network', e.message, e.meta.status); } else { console.error('Unexpected', e); } } ``` ## Retry Strategy `withBackoff(fn, {max, logger})` (used internally) retries on: - Transport/network errors (`RRTransportError`). - Vendor status errors indicating record lock/in use (message matching `/lock|in use|record.*busy/i`) or explicit `retryable` flag. Backoff: exponential starting at 400ms, capped at 10s, plus up to 250ms jitter. Configure via `new RRClient({ retries: { max: 5 }, ... })`. ## Debug / Dump Flags Set these environment variables to inspect request/response internals: - `RR_DEBUG` – Enables debug logging (`defaultLogger.debug`). - `RR_DUMP_ENVELOPE=1` – Prints outgoing SOAP envelope. - `RR_DUMP_XML=1` – Prints full response XML. - `RR_DUMP_STATUS=1` – Logs parsed status blocks. - `RR_DUMP_APPLICATION=1` – Logs raw ApplicationArea block. - `RR_DUMP_DATA=1` – Logs `result.data` (postParse convenience output). ## Live Test Runner (`scripts/run-live.mjs`) Provides curated integration tests against a live Rome system using Vitest. Loads `.env` then `.env.local` (override). Usage: ``` node scripts/run-live.mjs [flags] [-- ...vitestArgs] ``` Available tests (see script for full list & flags): - `combinedSearch` – VIN or phone search. - `insertCustomer`, `updateCustomer` – Customer record operations (use `--write` to enable live writes). - `insertServiceVehicle` – Add vehicle (requires or discovers customer). - `getAdvisors` – Advisor listing by department. - `createRepairOrder`, `updateRepairOrder` – BSM RO operations. - `getParts` – Retrieve RO parts lines. - `all` – Run all sequentially. Common flags: - `--dump` → `RR_DUMP_ENVELOPE` - `--write` → `RR_LIVE_WRITES` (enables writes) - Operation-specific flags map to `RR_TEST_*` env variables (see script comments for details). - Arbitrary env: `--set=KEY=VALUE`. Example: ``` node scripts/run-live.mjs createRepairOrder --write --dump --customerNo=1134485 --vin=1ABCDEF2GHIJ34567 --ro=BSM123 ``` ## Bundling & Upload Helper (`scripts/bundle-for-upload.mjs`) Creates text bundles of project files for transport/support purposes. - Includes key directories (`src/`, `test/`, `schemas/`) and specific root files. - Excludes large/irrelevant directories (`node_modules`, `dist`, etc.). - Options: `--max-bytes`, `--pattern=globish`, `--with-env` (include `.env` – caution), `--list-only`. Generates `bundles/bundle--N.txt` with file boundary markers. ## XML Templates & XSDs Templates reside in `src/templates/templateMap.js` using Mustache. Each operation builder renders a STAR root element plus `ApplicationArea`. XSD files (under `schemas/`) accompany operations: - Customer Insert/Update: `rey_RomeCustomerInsertReq.xsd`, `rey_RomeCustomerUpdateReq.xsd` - Service Vehicle Insert: `rey_RomeServVehicleInsertReq.xsd` - Combined Search: `rey_RomeCustServVehCombReq.xsd` - Advisors: `rey_RomeGetAdvisorsReq.xsd` - Parts: `rey_RomeGetPartsReq.xsd` - Repair Orders Create/Update: `rey_RomeCreateBSMRepairOrderReq.xsd`, `rey_RomeUpdateBSMRepairOrderReq.xsd` Response XSDs also present for repair orders and others. Builders attach an `xsdFilename` hint and `elementName`; no runtime XSD validation is performed (the project does not contain a validation module beyond these references). ## Logging Default logger (`src/logger.js`): logs to console via `info`, `warn`, `error`; `debug` gated by `RR_DEBUG`. Provide a custom logger with matching method names in `RRClientConfig`: ```js const logger = { info:()=>{}, warn:()=>{}, error:console.error, debug:()=>{} }; const client = new RRClient({ baseUrl, username, password, logger }); ``` ## Build & Development Scripts: - `npm run build` – Rollup builds `dist/index.cjs` & `dist/index.mjs`, copies JSDoc types (`scripts/postbuild-copy-types.mjs`), emits `.d.ts` (via `tsc -p tsconfig.types.json`). - `npm test` – Runs unit tests (`vitest` with `vitest.config.unit.mjs`). - `npm run bundle` – Invoke bundle creation (see above). - `npm run live:` – Convenience commands mapping to `scripts/run-live.mjs` (e.g. `npm run live:getParts`). Rollup configuration (`rollup.config.mjs`): - Externalizes Node core modules and listed dependencies. - Applies terser minification (2 passes, hoisting) for compact output. - Generates both CJS and ESM entrypoints. Tree-shaking is enabled (`treeshake.moduleSideEffects = false`). `sideEffects: false` in `package.json` allows downstream bundlers to drop unused exports. ## Design Notes / Non-goals - WS-Security: Implements only UsernameToken with password type Text or Digest (configurable via `wssePasswordType`). No nonce or timestamp included. - Idempotency: `bodId` generated per request unless provided; `ensureBodAndDates` centralizes creation. - Parsing: Focused on extracting status and op-specific identifiers; raw parsed STAR tree still accessible via `result.parsed`. - Status Handling: Treats `NO_MATCH` (codes like 2 or 213) as non-error with `success=true` so callers can differentiate empty results from failures. - Validation: Enforces only practical minimum & enumerations; does not attempt full schema compliance. - No automatic `fetch` transport fallback yet (axios chosen for reliability). You can replace `postSoap` if providing a compatible function returning raw response text and error semantics. - No built-in XSD validator; XSDs present for reference only.