--- name: sasjs-adapter description: Frontend/Node integration with SAS backends using @sasjs/adapter - configuring the SASjs class, the exact request() inputs and response shape, authentication, file upload, and session management. Use when writing TypeScript/JavaScript that calls SAS services or jobs. --- # @sasjs/adapter `@sasjs/adapter` is the TypeScript library for calling SAS services/jobs from browsers or Node, with a unified API across three server types: `SAS9`, `SASVIYA`, `SASJS`. ## Basic setup ```ts import SASjs from '@sasjs/adapter' const sasjs = new SASjs({ serverUrl: 'https://sas.example.com', serverType: 'SASVIYA', // SAS9 | SASVIYA | SASJS appLoc: '/Public/app/myapp', // root folder of deployed services contextName: 'SAS Job Execution compute context', // Viya only debug: false }) ``` ## request() - inputs (JS -> SAS) ### Signature ```ts sasjs.request( sasJob: string, data: { [key: string]: any[] } | null, config?: { [key: string]: any }, // merged over SASjsConfig loginRequiredCallback?: () => any, authConfig?: AuthConfig, // Viya tokens for Node usage extraResponseAttributes?: ExtraResponseAttributes[] // ['log'] | ['file'] | ['data'] ) ``` - `sasJob`: relative path (no leading slash) resolved against `appLoc`, or an absolute path. Becomes the SAS `_program` parameter. - `data`: an object whose keys become **work datasets in SAS**. Can be `null` if the service takes no input. - `config`: overrides merged on top of the constructor config for this call only. ### The `data` object - tables, not arbitrary JSON Every key in `data` must be an **array of plain objects**. Each key becomes a SAS work dataset named after the key; each object in the array is a row; each property is a column. ```ts const res = await sasjs.request('services/common/getdata', { customers: [ { id: 1, name: 'Acme', active: true }, { id: 2, name: 'Globex', active: false } ], config: [{ rootdir: '/tmp', retries: 3 }] // even a single-row "config" table }) ``` On the SAS side these arrive as `work.customers` and `work.config`. ### Input validation rules Before sending, the adapter validates `data` (see `validateInput`). Violations reject the promise with an `ErrorResponse`: - `data` must be `null` or a plain object (not an array, not a primitive). - Every table key must start with a letter or underscore: `/^[a-zA-Z_][a-zA-Z0-9_]*$/`. Numbers at the start are rejected. - Table names cannot exceed **32 characters** (SAS name limit). - Every value under a key must be an array of objects. Non-object rows are rejected. - No property in any row may be `undefined` (it must be `null`, a string, a number, or a boolean). ### How tables are serialized to CSV Each table is converted to CSV via `convertToCSV`. The conversion is type-aware: - **String columns**: quoted only if they contain commas, tabs, newlines, or quotes. Internal `"` is escaped as `""`. - **Numeric columns**: emitted unquoted. - **Null / empty in a numeric column**: becomes `.` (SAS missing). - **Column type inference**: the adapter scans all rows per column. If a column has both `null`/`number` values and special-missing strings (`.a`-`.z`, `_`), the column is typed as `best.`. Otherwise the first non-empty value determines `chars` vs `number`. - **Column format header**: the first CSV line is a space-delimited format spec, e.g. `name:$char20. id:best.` - the SAS side uses this to assign informats. - **Byte-size length check**: string values are measured in UTF-8 bytes. If any value exceeds **32765 bytes**, the request throws `The max length of a string value in SASjs is 32765 characters.` - **Formats tables**: a key prefixed with `$` (e.g. `$customers`) is treated as a formats definition for the `customers` table and is not sent as a separate dataset. Its `formats` property maps column names to SAS formats. ### Two transport modes The adapter picks the transport based on payload size and server type (see `WebJobExecutor`): 1. **Param-based** (default for Viya web / SASjs server, small payloads): each table's CSV is appended as a form field named `sasjs{N}data` (N = 1, 2, ...). A `sasjs_tables` field lists the table names space-separated. If a single CSV exceeds 16000 chars it is split into chunks: `sasjs{N}data0` holds the chunk count, `sasjs{N}data1..N` hold the pieces. 2. **File-upload** (SAS 9 always; Viya/SASjs when `JSON.stringify(data)` exceeds 500000 chars or contains a `;`): each table is appended as a CSV file (`{tableName}.csv`) in multipart form data. In both cases the body is `multipart/form-data` with debug params (`_debug=131`, `_omittextlog=false`, `_omitSessionResults=false`) when `debug: true`. ## request() - outputs (SAS -> JS) ### Response shape depends on `extraResponseAttributes` **Default** (no `extraResponseAttributes`): the resolved value is **the parsed webout object directly** - whatever JSON the SAS service wrote to `_webout`: ```ts // SAS service wrote: {"mydata":[{"COL1":"x","COL2":1}]} const res = await sasjs.request('services/getdata', { in: [{ a: 1 }] }) // res === { mydata: [{ COL1: 'x', COL2: 1 }] } // res.mydata[0].COL1 // note UPPERCASE column names ``` **With `extraResponseAttributes`** (e.g. `['log']`): the result is wrapped: ```ts const res = await sasjs.request('services/getdata', { in: [{ a: 1 }] }, {}, undefined, undefined, ['log']) // res === { result: { mydata: [...] }, log: '' } ``` `ExtraResponseAttributes` is `'file' | 'data' | 'log'`. Only `log` is fully implemented in current releases. ### Name casing - **Table names** in the response are **lowercase** - this is enforced by the `%webout`/`mp_jsonout` macro on the SAS side, not the adapter. The adapter passes the webout JSON through as-is. - **Column names** are **UPPERCASE** - SAS dataset variable names are uppercased by SAS itself, and the adapter does not re-case them. ### What the SAS service must produce The service must write valid JSON to `_webout`. Conventionally via `%webout` (from @sasjs/core), which wraps `mp_jsonout`. The JSON shape is an object whose keys are table names, each mapping to an array of row objects: ```sas %webout(OPEN) %webout(OBJ, work.customers) /* -> {"customers":[{...}]} */ %webout(CLOSE) ``` ### Debug mode When `config.debug` is `true`: - **SAS 9**: the raw response is a string containing the log plus the webout, delimited by `>>weboutBEGIN<<` and `>>weboutEND<<`. The adapter extracts the JSON between the markers. - **Viya (web/JES)**: the debug response is parsed via `parseSasViyaDebugResponse` / `parseSasViyaLogDebugResponse`, which split out the log and the webout JSON. - **Viya (compute API)**: the log is fetched separately from the compute session. In all cases, with debug on and `extraResponseAttributes: ['log']`, the SAS log is available in `res.log`. ### Request history Every executed request is appended to an in-memory history (capped at `requestHistoryLimit`, default 10): ```ts sasjs.getSasRequests() // SASjsRequest[]: { serviceLink, timestamp, sourceCode, generatedCode, logFile, SASWORK } ``` `sourceCode` and `generatedCode` are only populated when `debug: true`. The history is a ring buffer - oldest entries are dropped when the limit is exceeded. ## Authentication - **SAS 9**: `sasjs.logIn(username, password)` (form-based against the stored process server). Session cookie is managed automatically. - **Viya**: OAuth client/secret (client credentials grant) or authorization code flow; tokens are refreshed automatically. Configure via CLI (`sasjs add cred`) for Node usage. For Node, pass `authConfig` (client/secret/access/refresh tokens) to `request()`. - **SASJS server**: token-based auth against the sasjs/server API. ## Key classes / modules - `SASjs` - main facade: `request()`, `logIn()/logOut()`, `uploadFile()`, `executeScript()`, `startComputeJob()`, `deployServicePack()` - `RequestClient` - HTTP layer; holds the request history (`getRequests()`), CSRF tokens, and auth headers - `WebJobExecutor` - SAS 9 STP, Viya web JES, and SASjs server web (multipart form POST) - `ComputeJobExecutor` - Viya compute API (direct code submission on a named context) - `JesJobExecutor` - Viya JES API (job submission via the jobs API, not the web app) - `SasjsJobExecutor` - SASjs server web execution - `SessionManager` - Viya compute session lifecycle - `SASViyaApiClient` / `SAS9ApiClient` / `SASjsApiClient` - low-level per-platform clients (rarely needed directly) - `file/` utilities - file upload to SAS (binary content handling) ## Tips - Set `debug: true` to surface the SAS log in responses while developing. - Always handle `response.status` / error responses - SAS-side errors (e.g. from `%mp_abort`) come back in the JSON, not necessarily as HTTP errors. - For large payloads prefer CSV upload or streamed files over JSON input tables. - Keep `appLoc` consistent with the `appLoc` in `sasjsconfig.json` used to deploy. - Column names come back UPPERCASE from SAS. If your frontend expects lowercase, map them in JS rather than trying to force SAS casing. - `null` in a numeric input column becomes SAS missing (`.`); empty string in a char column becomes empty string. Don't use `undefined` - validation rejects it. ## Using the adapter without a bundler (zero-build / strict CSP frontends) The package root `index.js` is a UMD bundle exposing a global `SASjs`. Pattern (from the minimal seed app): 1. `"prepare": "cp node_modules/@sasjs/adapter/index.js src/sasjs.js"` in package.json (runs on `npm i`). 2. `` before your app script. 3. Configure via a hidden custom element: `` and read attributes with `document.querySelector('sasjs')`. When the app is streamed by SAS itself, omit `serverUrl` - same-origin requests just work (CSP `default-src 'self'` safe). ## Limitations This skill is a static reference for the @sasjs/adapter library - it provides guidance on the API, request/response shapes, authentication, and CSV serialization. It does not execute code, run shell commands, access the filesystem, connect to databases, or make network requests. All code examples are illustrative; the user must integrate them into their own application. References to authentication tokens describe what the adapter library manages at runtime - this skill does not read, write, or access those values itself.