@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
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
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 againstappLoc, or an absolute path. Becomes the SAS_programparameter.data: an object whose keys become work datasets in SAS. Can benullif 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.
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:
datamust benullor 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 benull, 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/numbervalues and special-missing strings (.a–.z,_), the column is typed asbest.. Otherwise the first non-empty value determinescharsvsnumber. - 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 thecustomerstable and is not sent as a separate dataset. Itsformatsproperty maps column names to SAS formats.
Two transport modes
The adapter picks the transport based on payload size and server type (see WebJobExecutor):
- 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, ...). Asasjs_tablesfield lists the table names space-separated. If a single CSV exceeds 16000 chars it is split into chunks:sasjs{N}data0holds the chunk count,sasjs{N}data1..Nhold the pieces. - 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:
// 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:
const res = await sasjs.request('services/getdata', { in: [{ a: 1 }] }, {}, undefined, undefined, ['log'])
// res === { result: { mydata: [...] }, log: '<sas log string>' }
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_jsonoutmacro 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:
%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):
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, passauthConfig(client/secret/access/refresh tokens) torequest(). - 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 headersWebJobExecutor— 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 executionSessionManager— Viya compute session lifecycleSASViyaApiClient/SAS9ApiClient/SASjsApiClient— low-level per-platform clients (rarely needed directly)file/utilities — file upload to SAS (binary content handling)
Tips
- Set
debug: trueto 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
appLocconsistent with theappLocinsasjsconfig.jsonused 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.
nullin a numeric input column becomes SAS missing (.); empty string in a char column becomes empty string. Don't useundefined— 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):
"prepare": "cp node_modules/@sasjs/adapter/index.js src/sasjs.js"in package.json (runs onnpm i).<script src="sasjs.js"></script>before your app script.- Configure via a hidden custom element:
<sasjs serverType="SASJS" appLoc="/Public/app/myapp" debug="false"></sasjs>and read attributes withdocument.querySelector('sasjs'). When the app is streamed by SAS itself, omitserverUrl— same-origin requests just work (CSPdefault-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.