@sasjs/adapter
is the TypeScript library for calling SAS services/jobs from browsers or Node, with a unified API across three server types:
,
,
.
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']
)
- : relative path (no leading slash) resolved against , or an absolute path. Becomes the SAS parameter.
- : an object whose keys become work datasets in SAS. Can be if the service takes no input.
- : overrides merged on top of the constructor config for this call only.
The object - tables, not arbitrary JSON
Every key in
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
and
.
Input validation rules
Before sending, the adapter validates
(see
). Violations reject the promise with an
:
- must be 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 (it must be , a string, a number, or a boolean).
How tables are serialized to CSV
Each table is converted to CSV via
. 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 / values and special-missing strings (-, ), the column is typed as . Otherwise the first non-empty value determines vs .
- Column format header: the first CSV line is a space-delimited format spec, e.g. - 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. ) is treated as a formats definition for the table and is not sent as a separate dataset. Its property maps column names to SAS formats.
Two transport modes
The adapter picks the transport based on payload size and server type (see
):
- Param-based (default for Viya web / SASjs server, small payloads): each table's CSV is appended as a form field named (N = 1, 2, ...). A field lists the table names space-separated. If a single CSV exceeds 16000 chars it is split into chunks: holds the chunk count, hold the pieces.
- File-upload (SAS 9 always; Viya/SASjs when exceeds 500000 chars or contains a ): each table is appended as a CSV file () in multipart form data.
In both cases the body is
with debug params (
,
,
_omitSessionResults=false
) when
.
request() - outputs (SAS -> JS)
Response shape depends on
Default (no
): the resolved value is
the parsed webout object directly - whatever JSON the SAS service wrote to
:
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 (e.g.
): the result is wrapped:
ts
const res = await sasjs.request('services/getdata', { in: [{ a: 1 }] }, {}, undefined, undefined, ['log'])
// res === { result: { mydata: [...] }, log: '<sas log string>' }
is
. Only
is fully implemented in current releases.
Name casing
- Table names in the response are lowercase - this is enforced by the / 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
. Conventionally via
(from @sasjs/core), which wraps
. 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
- SAS 9: the raw response is a string containing the log plus the webout, delimited by and . 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
.
Request history
Every executed request is appended to an in-memory history (capped at
, default 10):
ts
sasjs.getSasRequests()
// SASjsRequest[]: { serviceLink, timestamp, sourceCode, generatedCode, logFile, SASWORK }
and
are only populated when
. 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 () for Node usage. For Node, pass (client/secret/access/refresh tokens) to .
- SASJS server: token-based auth against the sasjs/server API.
Key classes / modules
- - main facade: , , , , ,
- - HTTP layer; holds the request history (), CSRF tokens, and auth headers
- - SAS 9 STP, Viya web JES, and SASjs server web (multipart form POST)
- - Viya compute API (direct code submission on a named context)
- - Viya JES API (job submission via the jobs API, not the web app)
- - SASjs server web execution
- - Viya compute session lifecycle
- / / - low-level per-platform clients (rarely needed directly)
- utilities - file upload to SAS (binary content handling)
Tips
- Set to surface the SAS log in responses while developing.
- Always handle / error responses - SAS-side errors (e.g. from ) come back in the JSON, not necessarily as HTTP errors.
- For large payloads prefer CSV upload or streamed files over JSON input tables.
- Keep consistent with the in 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.
- in a numeric input column becomes SAS missing (); empty string in a char column becomes empty string. Don't use - validation rejects it.
Using the adapter without a bundler (zero-build / strict CSP frontends)
The package root
is a UMD bundle exposing a global
. Pattern (from the minimal seed app):
"prepare": "cp node_modules/@sasjs/adapter/index.js src/sasjs.js"
in package.json (runs on ).
<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 with document.querySelector('sasjs')
. When the app is streamed by SAS itself, omit - same-origin requests just work (CSP 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.