About sasjs-adapter
A TypeScript library for calling SAS services and jobs from browsers or Node.js through a unified API for SAS9, SASVIYA, and SASJS. It is for developers integrating SAS execution into JavaScript applications.
Key features
- 01Unified calls across three SAS server types
- 02Serialize input tables as CSV
- 03Support parameter and file-upload transports
- 04Manage authentication and sessions
- 05Keep request history and retrieve logs
Use cases
- 01Send tables from a browser to a SAS job
- 02Submit jobs to Viya from Node.js
- 03Debug requests and inspect SAS logs
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ sasjs/ skills --skill sasjs-adapterAfter installing, type/sasjs-adapterin Claude Code to confirm it loaded.
Details
From the project README@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.