1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 09:24:35 +00:00

Compare commits

...

105 Commits

Author SHA1 Message Date
Allan Bowe
dad99557a7 Merge pull request #788 from sasjs/quick-fix
fix: throw error as it is when its an instance of JobExecutionError
2023-03-06 10:15:36 +00:00
c7cc2e5fa4 fix: throw error as it is in sas9RequestClient when its an instance of JobExecutionError 2023-03-06 14:53:42 +05:00
Allan Bowe
2bd7544051 Merge pull request #787 from sasjs/issue-786
fix: removing .do from loginUrl mechanism
2023-02-21 18:56:12 +00:00
1fb972d88a chore: improved url match 2023-02-21 16:10:14 +01:00
64f8f8c893 fix: removing .do from loginUrl mechanism 2023-02-21 16:04:50 +01:00
Sabir Hassan
ddb4a51c55 Merge pull request #785 from sasjs/issue-783
Sasjs executor not re-sending waiting requests
2023-02-15 21:45:16 +05:00
921d6ef364 fix: sasjs executor not re-sending waiting requests 2023-02-15 17:26:23 +01:00
Allan Bowe
105675d46a Merge pull request #784 from sasjs/medjedovicm-issue-template
Create issue_template.md
2023-02-15 16:18:27 +00:00
Allan Bowe
e4addba762 Update issue_template.md 2023-02-15 16:17:02 +00:00
8203e918fd Create issue_template.md 2023-02-15 17:15:24 +01:00
Allan Bowe
2210e43880 chore: update README
Thanks @saramartinelli1992

https://github.com/sasjs/adapter/issues/781#issuecomment-1420943632
2023-02-07 15:20:10 +00:00
Allan Bowe
b04df0bc6d Merge pull request #779 from sasjs/issue-771
fix: bump axios version
2023-02-01 10:23:29 +00:00
98e851b4d8 chore: npm audit fix 2023-01-31 23:08:56 +05:00
84306bea3d fix: bump axios version to 0.27.2 2023-01-31 23:05:54 +05:00
89d32262f8 chore: form-data provides its own type definitions, so you do not need @types/form-data installed 2023-01-31 15:49:40 +05:00
257010f57d chore: axios provides its own type definitions, so you don't need @types/axios installed 2023-01-31 15:48:08 +05:00
eb9991015b chore: fix npm install 2023-01-31 15:45:49 +05:00
Allan Bowe
9d17e87a09 Merge pull request #778 from sasjs/issue-777
fix: improve error message when sasjs runner is not found
2023-01-30 19:12:48 +00:00
55f309e998 chore: quick fix 2023-01-30 23:50:40 +05:00
3d9b40398c fix: improve error message when sasjs runner is not found 2023-01-30 23:22:43 +05:00
Allan Bowe
e0badae973 fix: bumping semantic release package 2023-01-26 13:51:22 +00:00
Allan Bowe
524c561390 Merge pull request #775 from sasjs/import-node-env
fix: createReadStream import where needed
2023-01-26 13:46:39 +00:00
e7ceac1b78 fix: createReadStream import where needed
It's node specific util and othervise adapter was failing on browser environment
2023-01-09 13:07:51 +01:00
Allan Bowe
72ddd424a5 Merge pull request #772 from sasjs/add-deployZipFile
feat: add a new method deployZipFile in sasjsAPiClient class
2022-12-06 11:25:48 +00:00
85f771d1ed feat: add a new method deployZipFile in sasjsAPiClient class 2022-12-06 14:51:34 +05:00
Allan Bowe
1a781c3a56 chore: adding matrix room link in action 2022-10-24 21:25:52 +00:00
Allan Bowe
296d4efdfb Merge pull request #768 from sasjs/update-sasjs-tests-deps
fix: vulnerabilities in sasjs-tests
2022-10-24 21:48:22 +01:00
8df09d01de fix: bump deps versions
@sasjs/test-framework
node-sass
2022-10-25 01:41:09 +05:00
Allan Bowe
2d4a9d6dee chore: updating matrix room id for matrixbot 2022-10-24 10:24:46 +00:00
Allan Bowe
38c30f6342 Merge pull request #767 from sasjs/removevpn
fix: removing old vpn files
2022-10-24 11:12:01 +01:00
Allan Bowe
dd72304bc7 fix: removing old vpn files
BREAKING CHANGE: The _previous_ commit introduced some breaking changes, due to method re-organisation, namely:
* merged executableScriptSAS9, executableScriptSASViya and executableScriptSASjs methods to executableScript
* removed deployToSASjs and executeJobSASjs from the main SASjs moodule, as we can use the SASjsApiClient directly for these operations
2022-10-24 10:10:39 +00:00
Allan Bowe
296a543b2d Merge pull request #766 from sasjs/breaking-change
fix!: move methods from main sasjs class to respective api client class
2022-10-20 14:02:06 +01:00
70b31dcb8f fix: export SasjsRequestClient from main index.ts 2022-10-18 19:10:46 +05:00
b0c2a81989 fix: remove deployToSasjs and executeJobSasjs from main sasjs module 2022-10-18 19:09:56 +05:00
53e167b17d fix: merge executeScriptSAS9, executeScriptSASViya and executeScriptSASjs into single executeScript
BREAKING CHANGE
2022-10-17 23:14:51 +05:00
Allan Bowe
5159318d0d chore: final edit to matrix 2022-10-07 12:35:09 +00:00
Allan Bowe
6842ee13e4 chore: matrix curl syntax 2022-10-07 12:29:13 +00:00
Allan Bowe
2ce0395a2e chore: new gh-pages action 2022-10-07 12:24:58 +00:00
Allan Bowe
63440ddfd2 chore: matrix message fix 2022-10-07 12:20:37 +00:00
Allan Bowe
eb6729a9c7 chore: fix error if folder does not exist 2022-10-07 12:13:40 +00:00
Allan Bowe
9cd9dc83f3 chore: putting docs back in docs folder 2022-10-07 12:06:47 +00:00
Allan Bowe
7608887a0e chore: move to gh-pages 2022-10-07 12:00:55 +00:00
Allan Bowe
b453717fbc chore: removing docs folder 2022-10-07 11:56:35 +00:00
Allan Bowe
ecc896e52a Merge pull request #765 from sasjs/matrix
chore: moving slack message to matrix
2022-10-07 12:53:01 +01:00
Allan Bowe
e881e21f80 chore: moving slack message to matrix 2022-10-07 11:51:44 +00:00
Allan Bowe
44d6cfd544 Merge pull request #764 from sasjs/fix-workflow
chore: fix workflow syntax
2022-10-07 12:36:28 +01:00
b0c9ca3f9f chore: fix workflow syntax 2022-10-07 16:19:00 +05:00
Allan Bowe
e6f7781648 Merge pull request #763 from sasjs/move-docs-generation-to-pipeline
chore: added github workflow for generating docs
2022-10-07 12:13:45 +01:00
b07770eb7b chore: added github workflow for generating docs 2022-10-07 16:05:55 +05:00
Allan Bowe
c498783d76 Merge pull request #762 from sasjs/remove-node-version-check
fix: remove the invocation of checkNodeVersion script
2022-09-28 16:16:03 +01:00
7b3b1d7d7d fix: remove the invocation of checkNodeVersion script 2022-09-28 20:11:16 +05:00
Allan Bowe
60d3ff2d39 Merge pull request #759 from sasjs/sas9-extract-username
fix: sas9 extractUserName improvement
2022-09-28 10:52:49 +01:00
Saad Jutt
5217198ef6 Merge pull request #761 from sasjs/introduced-user-long-name
Introduced user long name
2022-09-26 17:25:09 +05:00
Saad Jutt
3a16c83697 test: AuthManager specs are updated/removed 2022-09-25 17:30:07 +05:00
Saad Jutt
e65b19b85e test: updated and renamed to extractUserLongNameSas9 2022-09-25 17:09:33 +05:00
Saad Jutt
a1c09ec802 fix(auth): login and check session returns username + user full name 2022-09-25 01:11:22 +05:00
2cbba38af5 chore: sas9 username parsing bring back 2022-09-22 16:58:39 +02:00
61fdf5573b chore: specs fixing, regex improve 2022-09-22 16:26:45 +02:00
5259cf3942 chore: added back docs 2022-09-22 16:02:07 +02:00
5f17f77018 chore: gitignore 2022-09-22 16:00:01 +02:00
84b46d02c9 chore: improved algorithm for detecting log off word 2022-09-22 15:56:48 +02:00
4ec57d93dd fix: removed shortening 2022-09-22 13:14:56 +02:00
8e73f543c3 chore(git): Merge branch 'sas9-extract-username' of github.com:sasjs/adapter into sas9-extract-username 2022-09-22 13:08:31 +02:00
a45edbc066 chore(git): Merge branch 'master' into sas9-extract-username 2022-09-22 12:55:18 +02:00
9258817887 chore: docs removed 2022-09-22 12:54:34 +02:00
1f35e01f22 chore: docs removed 2022-09-22 12:38:59 +02:00
59af907202 chore: typedoc 2022-09-22 12:35:28 +02:00
cd00c483c5 chore: docs 2022-09-22 01:16:10 +02:00
a01eb948b5 fix: sas9 extractUserName improvement 2022-09-22 01:05:53 +02:00
Allan Bowe
d746d21dee Merge pull request #756 from sasjs/execute-job
fix: executeJob method fixed for SASjsApiClient
2022-09-20 19:04:52 +01:00
d7330617df chore: update docs 2022-09-20 22:02:15 +05:00
b72cee099c chore: bump dependencies and fix vulnerabilities 2022-09-20 22:00:51 +05:00
9871ce4fb3 fix: executeJob method fixed for SASjsApiClient 2022-09-20 02:04:44 +05:00
Allan Bowe
16185eba4d Merge pull request #753 from sasjs/all-contributors/add-rudvfaden
docs: add rudvfaden as a contributor for userTesting, doc
2022-08-30 16:35:29 +01:00
Allan Bowe
eae14df683 Merge pull request #752 from rudvfaden/label
Correted label in read.me to dslabel
2022-08-30 16:32:04 +01:00
allcontributors[bot]
d989f74112 docs: update .all-contributorsrc [skip ci] 2022-08-30 15:30:12 +00:00
allcontributors[bot]
64d72ea0c2 docs: update README.md [skip ci] 2022-08-30 15:30:11 +00:00
Rud Faden
325202c8e1 Correted label in read.me to dslabel 2022-08-30 17:16:52 +02:00
Allan Bowe
e9422898b6 Merge pull request #751 from sasjs/quick-fix
fix: should not assign error to parsedSasjsServeLog
2022-08-28 22:42:07 +01:00
2b7281e70c fix: should not assign error to parsedSasjsServeLog 2022-08-29 02:34:13 +05:00
Allan Bowe
c1e3a9f5bb Merge pull request #745 from sasjs/matrix
chore: moving github docs to .github folder and migrating discord hook to matrix
2022-08-25 12:44:12 +01:00
Sabir Hassan
d249295b49 Merge pull request #749 from sasjs/update-sasjs-request-client
fix: update parse response method of sasjsRequestClient
2022-08-25 00:19:33 +05:00
010fd063df fix: update parse response method of sasjsRequestClient 2022-08-25 00:06:10 +05:00
Sabir Hassan
c6bbf1ff34 Merge pull request #746 from sasjs/handle-updated-sasjs-response
fix: handled updated sasjs response
2022-08-24 21:03:17 +05:00
Allan Bowe
f1df27fdf1 Merge pull request #748 from sasjs/issue-741
fix: error when multiple redirections happen while debug is on
2022-08-24 14:44:45 +01:00
eb739a83a4 chore: fix 2022-08-24 15:16:47 +02:00
d8b686dd7e fix: error when multiple redirections happen while debug is on 2022-08-24 15:12:13 +02:00
3d8eb762d0 chore: quick fix 2022-08-23 15:56:13 +05:00
c551cd0311 fix: created sasjsJobExecutor class and overrided parseResponse for sasjsRequestClient 2022-08-23 15:47:50 +05:00
4a319f1aef fix: handled updated sasjs response 2022-08-19 16:10:05 +05:00
Allan Bowe
a0b8316d7c Update README.md 2022-08-12 15:48:38 +01:00
munja
3b53d5b3ae chore: fix body 2022-08-07 14:52:42 +01:00
munja
dfebab5abc chore: fix body 2022-08-07 14:52:08 +01:00
munja
8d5ee0d6e1 chore: json payload 2022-08-07 14:16:36 +01:00
munja
1ed7a11fc3 chore: testing json payload 2022-08-07 14:03:59 +01:00
munja
abe95f5432 chore: moving github docs to .github folder and migrating discord hook to matrix 2022-08-07 13:47:50 +01:00
Allan Bowe
92be5a2dca Merge pull request #744 from sasjs/sasjs-server-csrf-cookie
fix(server): csrf cookie is created explicitly
2022-08-04 02:03:06 +01:00
Saad Jutt
f58f2eba97 chore: error needs to be more specific 2022-08-04 05:59:39 +05:00
Saad Jutt
e37bb182c3 fix(server): csrf cookie is created explicitly 2022-08-04 05:04:43 +05:00
Allan Bowe
504777603c Merge pull request #743 from sasjs/issue-722
fix: improve input validations
2022-07-28 13:03:03 +01:00
706cbe5513 chore: add unit tests for validateInput 2022-07-28 14:23:00 +05:00
88eadd27aa chore: moved utils specs to spec folder 2022-07-28 14:22:16 +05:00
4ed9f87434 fix: moved validateInput method to separate file and added some additional validation 2022-07-28 14:20:41 +05:00
Allan Bowe
f0f80a1c1f Merge pull request #742 from sasjs/issue-721
fix: add additional check for string type before converting data to lower case
2022-07-27 20:42:48 +01:00
d0d8d58945 fix: add additional check for string type before converting data to lower case 2022-07-27 18:30:47 +05:00
88 changed files with 2723 additions and 6055 deletions

View File

@@ -96,6 +96,16 @@
"test",
"review"
]
},
{
"login": "rudvfaden",
"name": "Rud Faden",
"avatar_url": "https://avatars.githubusercontent.com/u/2445577?v=4",
"profile": "http://rudvfaden.github.io/",
"contributions": [
"userTesting",
"doc"
]
}
],
"contributorsPerLine": 7,

12
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,12 @@
## Expected behaviour
*Describe what should be happening*
## Current behaviour
*Describe what is actually happening*
## Environment info
**Client tech stack**: *Angular, React, Vue, VanillaJS, NodeJS etc.*
**Server type**: SASJS|SASVIYA|SAS9
**Login mechanism**: Default|Redirected
**Debug**: true|false
**Use Compute Api (relevant only on VIYA)**: true|false

View File

@@ -1,30 +0,0 @@
cipher AES-256-CBC
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1

View File

@@ -24,16 +24,16 @@ jobs:
- name: Check npm audit
run: npm audit --production --audit-level=low
- name: Install Dependencies
run: npm ci
- name: Check code style
run: npm run lint
- name: Run unit tests
run: npm test
- name: Build Package
run: npm run package:lib
env:
@@ -41,35 +41,10 @@ jobs:
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
with:
key: ${{ secrets.DCGITLAB_KEY }}
known_hosts: 'placeholder'
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3=16~beta+focal
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Deploy sasjs-tests
run: |
npm install -g replace-in-files-cli
@@ -88,7 +63,7 @@ jobs:
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"${{ secrets.SASJS_TEST_URL_VIYA }}",' ./cypress.json
replace-in-files --regex='"username".*' --replacement='"username":"${{ secrets.SASJS_USERNAME }}",' ./cypress.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./cypress.json
sh ./sasjs-cypress-run.sh ${{ secrets.DISCORD_WEBHOOK }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
sh ./sasjs-tests/sasjs-cypress-run.sh ${{ secrets.MATRIX_TOKEN }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
# For some reason if coverage report action is run before other commands, those commands can't access the directories and files on which they depend on
- name: Generate coverage report

44
.github/workflows/generateDocs.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Generate docs and Push to docs Branch
on:
push:
branches:
- master
jobs:
generate_and_push_docs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [lts/fermium]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install Dependencies
run: npm ci
- name: Ensure docs folder exists
run: |
rm -rf docs || true # avoid error if docs folder does not exist
mkdir docs
- name: Generate Docs
run: npm run typedoc
- name: Push generated docs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GH_TOKEN }}
publish_branch: gh-pages
publish_dir: ./docs
cname: adapter.sasjs.io

View File

@@ -34,9 +34,10 @@ jobs:
run: npm run build
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
uses: cycjimmy/semantic-release-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Send Slack message
run: curl -X POST --data-urlencode "payload={\"channel\":\"#sasjs\", \"username\":\"GitHub CI\", \"text\":\"New version of @sasjs/adapter has been released! \n Please deploy and run `dctests` with new adapter to make sure everything is still in place.\", \"icon_emoji\":\":rocket:\"}" ${{ secrets.SLACK_WEBHOOK }}
- name: Send Matrix message
run: curl -XPOST -d "{\"msgtype\":\"m.text\", \"body\":\"New version of @sasjs/adapter has been released! \n Please deploy and run 'dctests' with new adapter to make sure everything is still in place.\"}" https://matrix.4gl.io/_matrix/client/r0/rooms/!jRebyiGmHZlpfDwYXN:4gl.io/send/m.room.message?access_token=${{ secrets.MATRIX_TOKEN }}

4
.gitignore vendored
View File

@@ -1,8 +1,10 @@
node_modules
build
docs
.env
/coverage
.DS_Store
.DS_Store

View File

@@ -20,7 +20,7 @@ SASjs is a open-source framework for building Web Apps on SAS® platforms. You c
1 - `npm install @sasjs/adapter` - for use in a nodeJS project (recommended)
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@3/index.min.js) and use a copy of the latest JS file
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@4/index.min.js) and use a copy of the latest JS file
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
@@ -125,9 +125,9 @@ sasJs.request("/path/to/my/service", dataObject)
})
```
We supply the path to the SAS service, and a data object.
We supply the path to the SAS service, and a data object.
If the path starts with a `/` then it should be a full path to the service. If there is no leading `/` then it is relative to the `appLoc`.
If the path starts with a `/` then it should be a full path to the service. If there is no leading `/` then it is relative to the `appLoc`.
The data object can be null (for services with no input), or can contain one or more "tables" in the following format:
@@ -159,7 +159,7 @@ The SAS type (char/numeric) of the values is determined according to a set of ru
* If the values are numeric, the SAS type is numeric
* If the values are all string, the SAS type is character
* If the values contain a single character (a-Z + underscore + .) AND a numeric, then the SAS type is numeric (with special missing values).
* If the values contain a single character (a-Z + underscore + .) AND a numeric, then the SAS type is numeric (with special missing values).
* `null` is set to either '.' or '' depending on the assigned or derived type per the above rules. If entire column is `null` then the type will be numeric.
The following table illustrates the formats applied to columns under various scenarios:
@@ -237,7 +237,8 @@ run;
%webout(OBJ,a) /* Rows in table `a` are objects (easy to use) */
%webout(ARR,b) /* Rows in table `b` are arrays (compact) */
%webout(OBJ,c,fmt=N) /* Table `c` is sent unformatted (raw) */
%webout(OBJ,c,label=d) /* Rename as `d` on JS side */
%webout(OBJ,c,dslabel=d) /* Rename table as `d` in output JSON */
%webout(OBJ,c,dslabel=e, maxobs=10) /* send only 10 rows back */
%webout(CLOSE) /* Close the JSON and add default variables */
```
@@ -331,7 +332,7 @@ If you find this library useful, help us grow our star graph!
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -349,6 +350,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center"><a href="http://rudvfaden.github.io/"><img src="https://avatars.githubusercontent.com/u/2445577?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rud Faden</b></sub></a><br /><a href="#userTesting-rudvfaden" title="User Testing">📓</a> <a href="https://github.com/sasjs/adapter/commits?author=rudvfaden" title="Documentation">📖</a></td>
</tr>
</table>
<!-- markdownlint-restore -->

View File

@@ -1 +0,0 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

View File

@@ -1 +0,0 @@
adapter.sasjs.io

View File

@@ -1,99 +0,0 @@
:root {
--light-hl-0: #000000;
--dark-hl-0: #D4D4D4;
--light-hl-1: #0000FF;
--dark-hl-1: #569CD6;
--light-hl-2: #001080;
--dark-hl-2: #9CDCFE;
--light-hl-3: #795E26;
--dark-hl-3: #DCDCAA;
--light-hl-4: #A31515;
--dark-hl-4: #CE9178;
--light-hl-5: #AF00DB;
--dark-hl-5: #C586C0;
--light-hl-6: #0070C1;
--dark-hl-6: #4FC1FF;
--light-hl-7: #008000;
--dark-hl-7: #6A9955;
--light-hl-8: #098658;
--dark-hl-8: #B5CEA8;
--light-hl-9: #000000;
--dark-hl-9: #C8C8C8;
--light-hl-10: #CD3131;
--dark-hl-10: #F44747;
--light-code-background: #F5F5F5;
--dark-code-background: #1E1E1E;
}
@media (prefers-color-scheme: light) { :root {
--hl-0: var(--light-hl-0);
--hl-1: var(--light-hl-1);
--hl-2: var(--light-hl-2);
--hl-3: var(--light-hl-3);
--hl-4: var(--light-hl-4);
--hl-5: var(--light-hl-5);
--hl-6: var(--light-hl-6);
--hl-7: var(--light-hl-7);
--hl-8: var(--light-hl-8);
--hl-9: var(--light-hl-9);
--hl-10: var(--light-hl-10);
--code-background: var(--light-code-background);
} }
@media (prefers-color-scheme: dark) { :root {
--hl-0: var(--dark-hl-0);
--hl-1: var(--dark-hl-1);
--hl-2: var(--dark-hl-2);
--hl-3: var(--dark-hl-3);
--hl-4: var(--dark-hl-4);
--hl-5: var(--dark-hl-5);
--hl-6: var(--dark-hl-6);
--hl-7: var(--dark-hl-7);
--hl-8: var(--dark-hl-8);
--hl-9: var(--dark-hl-9);
--hl-10: var(--dark-hl-10);
--code-background: var(--dark-code-background);
} }
body.light {
--hl-0: var(--light-hl-0);
--hl-1: var(--light-hl-1);
--hl-2: var(--light-hl-2);
--hl-3: var(--light-hl-3);
--hl-4: var(--light-hl-4);
--hl-5: var(--light-hl-5);
--hl-6: var(--light-hl-6);
--hl-7: var(--light-hl-7);
--hl-8: var(--light-hl-8);
--hl-9: var(--light-hl-9);
--hl-10: var(--light-hl-10);
--code-background: var(--light-code-background);
}
body.dark {
--hl-0: var(--dark-hl-0);
--hl-1: var(--dark-hl-1);
--hl-2: var(--dark-hl-2);
--hl-3: var(--dark-hl-3);
--hl-4: var(--dark-hl-4);
--hl-5: var(--dark-hl-5);
--hl-6: var(--dark-hl-6);
--hl-7: var(--dark-hl-7);
--hl-8: var(--dark-hl-8);
--hl-9: var(--dark-hl-9);
--hl-10: var(--dark-hl-10);
--code-background: var(--dark-code-background);
}
.hl-0 { color: var(--hl-0); }
.hl-1 { color: var(--hl-1); }
.hl-2 { color: var(--hl-2); }
.hl-3 { color: var(--hl-3); }
.hl-4 { color: var(--hl-4); }
.hl-5 { color: var(--hl-5); }
.hl-6 { color: var(--hl-6); }
.hl-7 { color: var(--hl-7); }
.hl-8 { color: var(--hl-8); }
.hl-9 { color: var(--hl-9); }
.hl-10 { color: var(--hl-10); }
pre, code { background: var(--code-background); }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,48 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>SASjsConfig | @sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@sasjs/adapter</a></li><li><a href="../modules/types.html">types</a></li><li><a href="types.SASjsConfig.html">SASjsConfig</a></li></ul><h1>Class SASjsConfig</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel tsd-comment"><div class="tsd-comment tsd-typography"><div class="lead">
<p>Specifies the configuration for the SASjs instance - eg where and how to
connect to SAS.</p>
</div></div></section><section class="tsd-panel tsd-hierarchy"><h3>Hierarchy</h3><ul class="tsd-hierarchy"><li><span class="target">SASjsConfig</span></li></ul></section><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Constructors</h3><ul class="tsd-index-list"><li class="tsd-kind-constructor tsd-parent-kind-class"><a href="types.SASjsConfig.html#constructor" class="tsd-kind-icon">constructor</a></li></ul></section><section class="tsd-index-section "><h3>Properties</h3><ul class="tsd-index-list"><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#appLoc" class="tsd-kind-icon">app<wbr/>Loc</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#contextName" class="tsd-kind-icon">context<wbr/>Name</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#debug" class="tsd-kind-icon">debug</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#httpsAgentOptions" class="tsd-kind-icon">https<wbr/>Agent<wbr/>Options</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#loginMechanism" class="tsd-kind-icon">login<wbr/>Mechanism</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#pathSAS9" class="tsd-kind-icon">pathSAS9</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#pathSASJS" class="tsd-kind-icon">pathSASJS</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#pathSASViya" class="tsd-kind-icon">pathSASViya</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#requestHistoryLimit" class="tsd-kind-icon">request<wbr/>History<wbr/>Limit</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#serverType" class="tsd-kind-icon">server<wbr/>Type</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#serverUrl" class="tsd-kind-icon">server<wbr/>Url</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#useComputeApi" class="tsd-kind-icon">use<wbr/>Compute<wbr/>Api</a></li></ul></section></div></section></section><section class="tsd-panel-group tsd-member-group "><h2>Constructors</h2><section class="tsd-panel tsd-member tsd-kind-constructor tsd-parent-kind-class"><a id="constructor" class="tsd-anchor"></a><h3 class="tsd-anchor-link">constructor<a href="#constructor" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><ul class="tsd-signatures tsd-kind-constructor tsd-parent-kind-class"><li class="tsd-signature tsd-kind-icon">new SASjs<wbr/>Config<span class="tsd-signature-symbol">(</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><a href="types.SASjsConfig.html" class="tsd-signature-type" data-tsd-kind="Class">SASjsConfig</a></li></ul><ul class="tsd-descriptions"><li class="tsd-description"><h4 class="tsd-returns-title">Returns <a href="types.SASjsConfig.html" class="tsd-signature-type" data-tsd-kind="Class">SASjsConfig</a></h4></li></ul></section></section><section class="tsd-panel-group tsd-member-group "><h2>Properties</h2><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="appLoc" class="tsd-anchor"></a><h3 class="tsd-anchor-link">app<wbr/>Loc<a href="#appLoc" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">app<wbr/>Loc<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &#39;&#39;</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L39">types/SASjsConfig.ts:39</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>The appLoc is the parent folder under which the SAS services (STPs or Job
Execution Services) are stored. We recommend that each app is stored in
a dedicated parent folder (the appLoc) and the services are grouped inside
subfolders within the appLoc - allowing functionality to be restricted
according to those groups at backend.
When using appLoc, the paths provided in the <code>request</code> function should be
<em>without</em> a leading slash (/).</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="contextName" class="tsd-anchor"></a><h3 class="tsd-anchor-link">context<wbr/>Name<a href="#contextName" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">context<wbr/>Name<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &#39;&#39;</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L52">types/SASjsConfig.ts:52</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>The name of the compute context to use when calling the Viya services directly.
Example value: &#39;SAS Job Execution compute context&#39;</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="debug" class="tsd-anchor"></a><h3 class="tsd-anchor-link">debug<a href="#debug" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">debug<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol"> = true</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L47">types/SASjsConfig.ts:47</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>Set to <code>true</code> to enable additional debugging.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="httpsAgentOptions" class="tsd-anchor"></a><h3 class="tsd-anchor-link"><span class="tsd-flag ts-flagOptional">Optional</span> https<wbr/>Agent<wbr/>Options<a href="#httpsAgentOptions" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">https<wbr/>Agent<wbr/>Options<span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">AgentOptions</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L67">types/SASjsConfig.ts:67</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>Optional setting to configure HTTPS Agent.
By providing <code>key</code>, <code>cert</code>, <code>ca</code> to connect with server
Other options can be set <code>rejectUnauthorized</code> and <code>requestCert</code></p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="loginMechanism" class="tsd-anchor"></a><h3 class="tsd-anchor-link">login<wbr/>Mechanism<a href="#loginMechanism" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">login<wbr/>Mechanism<span class="tsd-signature-symbol">:</span> <a href="../enums/types.LoginMechanism.html" class="tsd-signature-type" data-tsd-kind="Enumeration">LoginMechanism</a><span class="tsd-signature-symbol"> = LoginMechanism.Default</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L71">types/SASjsConfig.ts:71</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>Supported login mechanisms are - Redirected and Default</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="pathSAS9" class="tsd-anchor"></a><h3 class="tsd-anchor-link">pathSAS9<a href="#pathSAS9" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">pathSAS9<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &#39;&#39;</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L24">types/SASjsConfig.ts:24</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>The location of the Stored Process Web Application. By default the adapter
will use &#39;/SASStoredProcess/do&#39; on SAS 9.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="pathSASJS" class="tsd-anchor"></a><h3 class="tsd-anchor-link">pathSASJS<a href="#pathSASJS" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">pathSASJS<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &#39;&#39;</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L19">types/SASjsConfig.ts:19</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>The location of the STP Process Web Application. By default the adapter
will use &#39;/SASjsApi/stp/execute&#39; on SAS JS.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="pathSASViya" class="tsd-anchor"></a><h3 class="tsd-anchor-link">pathSASViya<a href="#pathSASViya" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">pathSASViya<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &#39;&#39;</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L29">types/SASjsConfig.ts:29</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>The location of the Job Execution Web Application. By default the adapter
will use &#39;/SASJobExecution&#39; on SAS Viya.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="requestHistoryLimit" class="tsd-anchor"></a><h3 class="tsd-anchor-link"><span class="tsd-flag ts-flagOptional">Optional</span> request<wbr/>History<wbr/>Limit<a href="#requestHistoryLimit" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">request<wbr/>History<wbr/>Limit<span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">number</span><span class="tsd-signature-symbol"> = 10</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L76">types/SASjsConfig.ts:76</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>Optional setting to configure request history limit. Increasing this limit
may affect browser performance, especially with debug (logs) enabled.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="serverType" class="tsd-anchor"></a><h3 class="tsd-anchor-link">server<wbr/>Type<a href="#serverType" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">server<wbr/>Type<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">ServerType</span><span class="tsd-signature-symbol"> = null</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L43">types/SASjsConfig.ts:43</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>Can be <code>SAS9</code> or <code>SASVIYA</code>.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="serverUrl" class="tsd-anchor"></a><h3 class="tsd-anchor-link">server<wbr/>Url<a href="#serverUrl" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">server<wbr/>Url<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> = &#39;&#39;</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L14">types/SASjsConfig.ts:14</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>The location (including http protocol and port) of the SAS Server.
Can be omitted, eg if serving directly from the SAS Web Server or being
streamed.</p>
</div></div></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="useComputeApi" class="tsd-anchor"></a><h3 class="tsd-anchor-link">use<wbr/>Compute<wbr/>Api<a href="#useComputeApi" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">use<wbr/>Compute<wbr/>Api<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol"> = null</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/SASjsConfig.ts#L61">types/SASjsConfig.ts:61</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>If it&#39;s <code>false</code> adapter will use the JES API as connection approach. To enhance VIYA
performance, set to <code>true</code> and provide a <code>contextName</code> on which to run
the code. When running on a named context, the code executes under the
user identity. When running as a Job Execution service, the code runs
under the identity in the JES context. If <code>useComputeApi</code> is <code>null</code> or <code>undefined</code>, the service will run as a Job, except
triggered using the APIs instead of the Job Execution Web Service broker.</p>
</div></div></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="../modules/SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="../modules/SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="../modules/SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="../modules/SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class="current tsd-kind-module"><a href="../modules/types.html">types</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="current tsd-kind-class tsd-parent-kind-module"><a href="types.SASjsConfig.html" class="tsd-kind-icon">SASjs<wbr/>Config</a><ul><li class="tsd-kind-constructor tsd-parent-kind-class"><a href="types.SASjsConfig.html#constructor" class="tsd-kind-icon">constructor</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#appLoc" class="tsd-kind-icon">app<wbr/>Loc</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#contextName" class="tsd-kind-icon">context<wbr/>Name</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#debug" class="tsd-kind-icon">debug</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#httpsAgentOptions" class="tsd-kind-icon">https<wbr/>Agent<wbr/>Options</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#loginMechanism" class="tsd-kind-icon">login<wbr/>Mechanism</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#pathSAS9" class="tsd-kind-icon">pathSAS9</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#pathSASJS" class="tsd-kind-icon">pathSASJS</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#pathSASViya" class="tsd-kind-icon">pathSASViya</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#requestHistoryLimit" class="tsd-kind-icon">request<wbr/>History<wbr/>Limit</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#serverType" class="tsd-kind-icon">server<wbr/>Type</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#serverUrl" class="tsd-kind-icon">server<wbr/>Url</a></li><li class="tsd-kind-property tsd-parent-kind-class"><a href="types.SASjsConfig.html#useComputeApi" class="tsd-kind-icon">use<wbr/>Compute<wbr/>Api</a></li></ul></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li><li class="tsd-kind-constructor tsd-parent-kind-class"><span class="tsd-kind-icon">Constructor</span></li><li class="tsd-kind-property tsd-parent-kind-class"><span class="tsd-kind-icon">Property</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,292 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>@sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="assets/style.css"/><link rel="stylesheet" href="assets/highlight.css"/><script async src="assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base="."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><h1> @sasjs/adapter</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><div class="tsd-panel tsd-typography">
<a href="#sasjsadapter" id="sasjsadapter" style="color: inherit; text-decoration: none;">
<h1>@sasjs/adapter</h1>
</a>
<p><a href="http://npmjs.org/package/@sasjs/adapter"><img src="https://img.shields.io/npm/v/@sasjs/adapter.svg" alt="npm package"></a>
<a href="https://github.com/sasjs/adapter/blob/main/.github/workflows/build.yml"><img src="https://github.com/sasjs/adapter/actions/workflows/build.yml/badge.svg" alt="Github Workflow"></a>
<a href="https://github.com/sasjs/adapter/blob/main/package.json"><img src="https://david-dm.org/sasjs/adapter.svg" alt="Dependency Status"></a>
<a href=""><img src="https://img.shields.io/npm/dt/@sasjs/adapter" alt="npm"></a>
<img src="https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/adapter" alt="Snyk Vulnerabilities for npm package">
<a href="/LICENSE"><img src="https://img.shields.io/apm/l/atomic-design-ui.svg" alt="License"></a>
<img src="https://img.shields.io/github/languages/top/sasjs/adapter" alt="GitHub top language">
<img src="https://img.shields.io/github/issues/sasjs/adapter" alt="GitHub issues">
<a href="https://gitpod.io/#https://github.com/sasjs/adapter"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-908a85?logo=gitpod" alt="Gitpod ready-to-code"></a></p>
<p>SASjs is a open-source framework for building Web Apps on SAS® platforms. You can use as much or as little of it as you like. This repository contains the JS adapter, the part that handles the to/from SAS communication on the client side. There are 3 ways to install it:</p>
<p>1 - <code>npm install @sasjs/adapter</code> - for use in a nodeJS project (recommended)</p>
<p>2 - <a href="https://cdn.jsdelivr.net/npm/@sasjs/adapter@3/index.min.js">Download</a> and use a copy of the latest JS file</p>
<p>3 - Reference directly from the CDN - in which case click <a href="https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection">here</a> and select &quot;SRI&quot; to get the script tag with the integrity hash.</p>
<p>If you are short on time and just need to build an app quickly, then check out <a href="https://vimeo.com/393161794">this video</a> and the <a href="https://github.com/sasjs/react-seed-app">react-seed-app</a> which provides some boilerplate.</p>
<p>For more information on building web apps with SAS, check out <a href="https://sasjs.io">sasjs.io</a></p>
<a href="#none-of-this-makes-sense-how-do-i-build-an-app-with-it" id="none-of-this-makes-sense-how-do-i-build-an-app-with-it" style="color: inherit; text-decoration: none;">
<h2>None of this makes sense. How do I build an app with it?</h2>
</a>
<p>Ok ok. Deploy this <a href="https://raw.githubusercontent.com/sasjs/adapter/master/example.html">example.html</a> file to your web server, and update <code>servertype</code> to <code>SAS9</code> or <code>SASVIYA</code> depending on your backend.</p>
<p>The backend part can be deployed as follows:</p>
<pre><code class="language-sas"><span class="hl-0">%let appLoc=/Public/app/readme; /* Metadata or Viya Folder per SASjs config */</span><br/><span class="hl-0">filename mc url &quot;https://raw.githubusercontent.com/sasjs/core/main/all.sas&quot;;</span><br/><span class="hl-0">%inc mc; /* compile macros (can also be downloaded &amp; compiled seperately) */</span><br/><span class="hl-0">filename ft15f001 temp;</span><br/><span class="hl-0">parmcards4;</span><br/><span class="hl-0"> %webout(FETCH) /* receive all data as SAS datasets */</span><br/><span class="hl-0"> proc sql;</span><br/><span class="hl-0"> create table areas as select make,mean(invoice) as avprice</span><br/><span class="hl-0"> from sashelp.cars</span><br/><span class="hl-0"> where type in (select type from work.fromjs)</span><br/><span class="hl-0"> group by 1;</span><br/><span class="hl-0"> %webout(OPEN)</span><br/><span class="hl-0"> %webout(OBJ,areas)</span><br/><span class="hl-0"> %webout(CLOSE)</span><br/><span class="hl-0">;;;;</span><br/><span class="hl-0">%mp_createwebservice(path=&amp;appLoc/common,name=getdata)</span>
</code></pre>
<p>You now have a simple web app with a backend service!</p>
<a href="#detailed-overview" id="detailed-overview" style="color: inherit; text-decoration: none;">
<h2>Detailed Overview</h2>
</a>
<p>The SASjs adapter is a JS library and a set of SAS Macros that handle the communication between the frontend app and backend SAS services.</p>
<p>There are three parts to consider:</p>
<ol>
<li>JS request / response</li>
<li>SAS inputs / outputs</li>
<li>Configuration</li>
</ol>
<a href="#js-request--response" id="js-request--response" style="color: inherit; text-decoration: none;">
<h3>JS Request / Response</h3>
</a>
<p>To install the library you can simply run <code>npm i @sasjs/adapter</code> or include a <code>&lt;script&gt;</code> tag with a reference to our <a href="https://www.jsdelivr.com/package/npm/@sasjs/adapter">CDN</a>.</p>
<p>Full technical documentation is available <a href="https://adapter.sasjs.io">here</a>. The main parts are:</p>
<a href="#instantiation" id="instantiation" style="color: inherit; text-decoration: none;">
<h3>Instantiation</h3>
</a>
<p>The following code will instantiate an instance of the adapter:</p>
<pre><code class="language-javascript"><span class="hl-1">let</span><span class="hl-0"> </span><span class="hl-2">sasJs</span><span class="hl-0"> = </span><span class="hl-1">new</span><span class="hl-0"> </span><span class="hl-2">SASjs</span><span class="hl-0">.</span><span class="hl-3">default</span><span class="hl-0">(</span><br/><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-2">appLoc:</span><span class="hl-0"> </span><span class="hl-4">&quot;/Your/SAS/Folder&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-2">serverType:</span><span class="hl-4">&quot;SAS9&quot;</span><br/><span class="hl-0"> }</span><br/><span class="hl-0">);</span>
</code></pre>
<p>If you&#39;ve installed it via NPM, you can import it as a default import like so:</p>
<pre><code class="language-js"><span class="hl-0"> </span><span class="hl-5">import</span><span class="hl-0"> </span><span class="hl-2">SASjs</span><span class="hl-0"> </span><span class="hl-5">from</span><span class="hl-0"> </span><span class="hl-4">&#39;@sasjs/adapter&#39;</span><span class="hl-0">;</span>
</code></pre>
<p>You can then instantiate it with:</p>
<pre><code class="language-js"><span class="hl-1">const</span><span class="hl-0"> </span><span class="hl-6">sasJs</span><span class="hl-0"> = </span><span class="hl-1">new</span><span class="hl-0"> </span><span class="hl-3">SASjs</span><span class="hl-0">({your </span><span class="hl-2">config</span><span class="hl-0">})</span>
</code></pre>
<p>More on the config later.</p>
<a href="#sas-logon" id="sas-logon" style="color: inherit; text-decoration: none;">
<h3>SAS Logon</h3>
</a>
<p>All authentication from the adapter is done against SASLogon. There are two approaches that can be taken, which are configured using the <code>LoginMechanism</code> attribute of the sasJs config object (above):</p>
<ul>
<li><code>LoginMechanism:&#39;Redirected&#39;</code> - this approach enables authentication through a SASLogon window, supporting complex authentication flows (such as 2FA) and avoids the need to handle passwords in the application itself. The styling of the window can be modified using CSS.</li>
<li><code>LoginMechanism:&#39;Default&#39;</code> - this approach requires that the username and password are captured, and used within the <code>.login()</code> method. This can be helpful for development, or automated testing.</li>
</ul>
<p>Sample code for logging in with the <code>Default</code> approach:</p>
<pre><code class="language-javascript"><span class="hl-2">sasJs</span><span class="hl-0">.</span><span class="hl-3">logIn</span><span class="hl-0">(</span><span class="hl-4">&#39;USERNAME&#39;</span><span class="hl-0">,</span><span class="hl-4">&#39;PASSWORD&#39;</span><br/><span class="hl-0"> ).</span><span class="hl-3">then</span><span class="hl-0">((</span><span class="hl-2">response</span><span class="hl-0">) </span><span class="hl-1">=&gt;</span><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-5">if</span><span class="hl-0"> (</span><span class="hl-2">response</span><span class="hl-0">.</span><span class="hl-2">isLoggedIn</span><span class="hl-0"> === </span><span class="hl-1">true</span><span class="hl-0">) {</span><br/><span class="hl-0"> </span><span class="hl-2">console</span><span class="hl-0">.</span><span class="hl-3">log</span><span class="hl-0">(</span><span class="hl-4">&#39;do stuff&#39;</span><span class="hl-0">)</span><br/><span class="hl-0"> } </span><span class="hl-5">else</span><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-2">console</span><span class="hl-0">.</span><span class="hl-3">log</span><span class="hl-0">(</span><span class="hl-4">&#39;do other stuff&#39;</span><span class="hl-0">)</span><br/><span class="hl-0"> }</span><br/><span class="hl-0">}</span>
</code></pre>
<p>More examples of using authentication, and more, can be found in the <a href="https://github.com/search?q=topic%3Asasjs-app+org%3Asasjs+fork%3Atrue">SASjs Seed Apps</a> on github.</p>
<a href="#request--response" id="request--response" style="color: inherit; text-decoration: none;">
<h3>Request / Response</h3>
</a>
<p>A simple request can be sent to SAS in the following fashion:</p>
<pre><code class="language-javascript"><span class="hl-2">sasJs</span><span class="hl-0">.</span><span class="hl-3">request</span><span class="hl-0">(</span><span class="hl-4">&quot;/path/to/my/service&quot;</span><span class="hl-0">, </span><span class="hl-2">dataObject</span><span class="hl-0">)</span><br/><span class="hl-0"> .</span><span class="hl-3">then</span><span class="hl-0">((</span><span class="hl-2">response</span><span class="hl-0">) </span><span class="hl-1">=&gt;</span><span class="hl-0"> {</span><br/><span class="hl-0"> </span><span class="hl-7">// all tables are in the response object, eg:</span><br/><span class="hl-0"> </span><span class="hl-2">console</span><span class="hl-0">.</span><span class="hl-3">log</span><span class="hl-0">(</span><span class="hl-2">response</span><span class="hl-0">.</span><span class="hl-2">tablewith2cols1row</span><span class="hl-0">[</span><span class="hl-8">0</span><span class="hl-0">].</span><span class="hl-6">COL1</span><span class="hl-0">.</span><span class="hl-2">value</span><span class="hl-0">)</span><br/><span class="hl-0"> })</span>
</code></pre>
<p>We supply the path to the SAS service, and a data object. The data object can be null (for services with no input), or can contain one or more tables in the following format:</p>
<pre><code class="language-javascript"><span class="hl-1">let</span><span class="hl-0"> </span><span class="hl-2">dataObject</span><span class="hl-0">={</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;tablewith2cols1row&quot;</span><span class="hl-2">:</span><span class="hl-0"> [{</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;col1&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;val1&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;col2&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-8">42</span><br/><span class="hl-0"> }],</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;tablewith1col2rows&quot;</span><span class="hl-2">:</span><span class="hl-0"> [{</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;col&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;row1&quot;</span><br/><span class="hl-0"> }, {</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;col&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;row2&quot;</span><br/><span class="hl-0"> }]</span><br/><span class="hl-0">};</span>
</code></pre>
<p>There are optional parameters such as a config object and a callback login function.</p>
<p>The response object will contain returned tables and columns. Table names are always lowercase, and column names uppercase.</p>
<p>The adapter will also cache the logs (if debug enabled) and even the work tables. For performance, it is best to keep debug mode off.</p>
<a href="#variable-types" id="variable-types" style="color: inherit; text-decoration: none;">
<h3>Variable Types</h3>
</a>
<p>The SAS type (char/numeric) of the values is determined according to a set of rules:</p>
<ul>
<li>If the values are numeric, the SAS type is numeric</li>
<li>If the values are all string, the SAS type is character</li>
<li>If the values contain a single character (a-Z + underscore) AND a numeric, then the SAS type is numeric (with special missing values). </li>
<li><code>null</code> is set to either &#39;.&#39; or &#39;&#39; depending on the assigned or derived type per the above rules. If entire column is <code>null</code> then the type will be numeric.</li>
</ul>
<p>The following table illustrates the formats applied to columns under various scenarios:</p>
<table>
<thead>
<tr>
<th>JS Values</th>
<th>SAS Format</th>
</tr>
</thead>
<tbody><tr>
<td>&#39;a&#39;, &#39;a&#39;</td>
<td>$char1.</td>
</tr>
<tr>
<td>0, &#39;_&#39;</td>
<td>best.</td>
</tr>
<tr>
<td>&#39;Z&#39;, 0</td>
<td>best.</td>
</tr>
<tr>
<td>&#39;a&#39;, &#39;aaa&#39;</td>
<td>$char3.</td>
</tr>
<tr>
<td>null, &#39;a&#39;, &#39;aaa&#39;</td>
<td>$char3.</td>
</tr>
<tr>
<td>null, &#39;a&#39;, 0</td>
<td>best.</td>
</tr>
<tr>
<td>null, null</td>
<td>best.</td>
</tr>
<tr>
<td>null, &#39;&#39;</td>
<td>$char1.</td>
</tr>
<tr>
<td>null, &#39;a&#39;</td>
<td>$char1.</td>
</tr>
<tr>
<td>&#39;a&#39;</td>
<td>$char1.</td>
</tr>
<tr>
<td>&#39;a&#39;, null</td>
<td>$char1.</td>
</tr>
<tr>
<td>&#39;a&#39;, null, 0</td>
<td>best.</td>
</tr>
</tbody></table>
<p>Validation is also performed on the values. The following combinations will throw errors:</p>
<table>
<thead>
<tr>
<th>JS Values</th>
<th>SAS Format</th>
</tr>
</thead>
<tbody><tr>
<td>null, &#39;aaaa&#39;, 0</td>
<td>Error: mixed types. &#39;aaaa&#39; is not a special missing value.</td>
</tr>
<tr>
<td>0, &#39;a&#39;, &#39;!&#39;</td>
<td>Error: mixed types. &#39;!&#39; is not a special missing value</td>
</tr>
<tr>
<td>1.1, &#39;.&#39;, 0</td>
<td>Error: mixed types. For regular nulls, use <code>null</code></td>
</tr>
</tbody></table>
<a href="#variable-format-override" id="variable-format-override" style="color: inherit; text-decoration: none;">
<h3>Variable Format Override</h3>
</a>
<p>The auto-detect functionality above is thwarted in the following scenarios:</p>
<ul>
<li>A character column containing only <code>null</code> values (is considered numeric)</li>
<li>A numeric column containing only special missing values (is considered character)</li>
</ul>
<p>To cater for these scenarios, an optional array of formats can be passed along with the data to ensure that SAS will read them in correctly.</p>
<p>To understand these formats, it should be noted that the JSON data is NOT passed directly (as JSON) to SAS. It is first converted into CSV, and the header row is actually an <code>infile</code> statement in disguise. It looks a bit like this:</p>
<pre><code class="language-csv">CHARVAR1:$char4. CHARVAR2:$char1. NUMVAR:best.
LOAD,,0
ABCD,X,.
</code></pre>
<p>To provide overrides to this header row, the tables object can be constructed as follows (with a leading &#39;$&#39; in the table name):</p>
<pre><code class="language-javascript"><span class="hl-1">let</span><span class="hl-0"> </span><span class="hl-2">specialData</span><span class="hl-0">={</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;tablewith2cols2rows&quot;</span><span class="hl-2">:</span><span class="hl-0"> [</span><br/><span class="hl-0"> {</span><span class="hl-4">&quot;col1&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;val1&quot;</span><span class="hl-0">,</span><span class="hl-4">&quot;specialMissingsCol&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;A&quot;</span><span class="hl-0">},</span><br/><span class="hl-0"> {</span><span class="hl-4">&quot;col1&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;val2&quot;</span><span class="hl-0">,</span><span class="hl-4">&quot;specialMissingsCol&quot;</span><span class="hl-2">:</span><span class="hl-0"> </span><span class="hl-4">&quot;_&quot;</span><span class="hl-0">}</span><br/><span class="hl-0"> ],</span><br/><span class="hl-0"> </span><span class="hl-4">&quot;$tablewith2cols2rows&quot;</span><span class="hl-2">:</span><span class="hl-0">{</span><span class="hl-4">&quot;formats&quot;</span><span class="hl-2">:</span><span class="hl-0">{</span><span class="hl-4">&quot;specialMissingsCol&quot;</span><span class="hl-2">:</span><span class="hl-4">&quot;best.&quot;</span><span class="hl-0">}</span><br/><span class="hl-0"> }</span><br/><span class="hl-0">};</span>
</code></pre>
<p>It is not necessary to provide formats for ALL the columns, only the ones that need to be overridden.</p>
<a href="#sas-inputs--outputs" id="sas-inputs--outputs" style="color: inherit; text-decoration: none;">
<h2>SAS Inputs / Outputs</h2>
</a>
<p>The SAS side is handled by a number of macros in the <a href="https://github.com/sasjs/core">macro core</a> library.</p>
<p>The following snippet shows the process of SAS tables arriving / leaving:</p>
<pre><code class="language-sas"><span class="hl-7">/* fetch all input tables sent from frontend - they arrive as work tables */</span><br/><span class="hl-0">%webout(FETCH)</span><br/><br/><span class="hl-7">/* some sas code */</span><br/><span class="hl-0">data a b c;</span><br/><span class="hl-0"> set from js;</span><br/><span class="hl-0">run;</span><br/><br/><span class="hl-0">%webout(OPEN) /* Open the JSON to be returned */</span><br/><span class="hl-0">%webout(OBJ,a) /* Rows in table `a` are objects (easy to use) */</span><br/><span class="hl-0">%webout(ARR,b) /* Rows in table `b` are arrays (compact) */</span><br/><span class="hl-0">%webout(OBJ,c,fmt=N) /* Table `c` is sent unformatted (raw) */</span><br/><span class="hl-0">%webout(OBJ,c,label=d) /* Rename as `d` on JS side */</span><br/><span class="hl-0">%webout(CLOSE) /* Close the JSON and add default variables */</span>
</code></pre>
<p>By default, special SAS numeric missings (_a-Z) are converted to <code>null</code> in the JSON. If you&#39;d like to preserve these, use the <code>missing=STRING</code> option as follows:</p>
<pre><code class="language-sas"><span class="hl-0">%webout(OBJ,a,missing=STRING)</span>
</code></pre>
<p>In this case, special missings (such as <code>.a</code>, <code>.b</code>) are converted to javascript string values (<code>&#39;A&#39;, &#39;B&#39;</code>).</p>
<p>Where an entire column is made up of special missing numerics, there would be no way to distinguish it from a single-character column by looking at the values. To cater for this scenario, it is possible to export the variable types (and other attributes such as label and format) by adding a <code>showmeta</code> param to the <code>webout()</code> macro as follows:</p>
<pre><code class="language-sas"><span class="hl-0">%webout(OBJ,a,missing=STRING,showmeta=YES)</span>
</code></pre>
<a href="#configuration" id="configuration" style="color: inherit; text-decoration: none;">
<h2>Configuration</h2>
</a>
<p>Configuration on the client side involves passing an object on startup, which can also be passed with each request. Technical documentation on the SASjsConfig class is available <a href="https://adapter.sasjs.io/classes/types.sasjsconfig.html">here</a>. The main config items are:</p>
<ul>
<li><code>appLoc</code> - this is the folder (eg in metadata or SAS Drive) under which the SAS services are created.</li>
<li><code>serverType</code> - either <code>SAS9</code>, <code>SASVIYA</code> or <code>SASJS</code>. The <code>SASJS</code> server type is for use with <a href="https://github.com/sasjs/server">sasjs/server</a>.</li>
<li><code>serverUrl</code> - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.</li>
<li><code>debug</code> - if <code>true</code> then SAS Logs and extra debug information is returned.</li>
<li><code>LoginMechanism</code> - either <code>Default</code> or <code>Redirected</code>. See <a href="#sas-logon">SAS Logon</a> section.</li>
<li><code>useComputeApi</code> - Only relevant when the serverType is <code>SASVIYA</code>. If <code>true</code> the <a href="#using-the-compute-api">Compute API</a> is used. If <code>false</code> the <a href="#using-the-jes-api">JES API</a> is used. If <code>null</code> or <code>undefined</code> the <a href="#using-jes-web-app">Web</a> approach is used.</li>
<li><code>contextName</code> - Compute context on which the requests will be called. If missing or not provided, defaults to <code>Job Execution Compute context</code>.</li>
<li><code>requestHistoryLimit</code> - Request history limit. Increasing this limit may affect browser performance, especially with debug (logs) enabled. Default is 10.</li>
</ul>
<p>The adapter supports a number of approaches for interfacing with Viya (<code>serverType</code> is <code>SASVIYA</code>). For maximum performance, be sure to <a href="https://sasjs.io/guide-viya/#shared-account-and-server-re-use">configure your compute context</a> with <code>reuseServerProcesses</code> as <code>true</code> and a system account in <code>runServerAs</code>. This functionality is available since Viya 3.5. This configuration is supported when <a href="https://sasjs.io/sasjs-cli-context/#sasjs-context-create">creating contexts using the CLI</a>.</p>
<a href="#using-jes-web-app" id="using-jes-web-app" style="color: inherit; text-decoration: none;">
<h3>Using JES Web App</h3>
</a>
<p>In this setup, all requests are routed through the JES web app, at <code>YOURSERVER/SASJobExecution?_program=/your/program</code>. This is the most reliable method, and also the slowest. One request is made to the JES app, and remaining requests (getting job uri, session spawning, passing parameters, running the program, fetching the log) are handled by the SAS server inside the JES app.</p>
<pre><code><span class="hl-0">{</span><br/><span class="hl-0"> </span><span class="hl-9">appLoc</span><span class="hl-0">:</span><span class="hl-4">&quot;/Your/Path&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-9">serverType</span><span class="hl-0">:</span><span class="hl-4">&quot;SASVIYA&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-9">contextName</span><span class="hl-0">: </span><span class="hl-4">&#39;yourComputeContext&#39;</span><br/><span class="hl-0">}</span>
</code></pre>
<p>Note - to use the web approach, the <code>useComputeApi</code> property must be <code>undefined</code> or <code>null</code>.</p>
<a href="#using-the-jes-api" id="using-the-jes-api" style="color: inherit; text-decoration: none;">
<h3>Using the JES API</h3>
</a>
<p>Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the <code>__job</code> parameter. Depending on your network bandwidth, it may or may not be faster than the JES Web approach.</p>
<p>This approach (<code>useComputeApi: false</code>) also ensures that jobs are displayed in Environment Manager.</p>
<pre><code class="language-json"><span class="hl-0">{</span><br/><span class="hl-0"> </span><span class="hl-10">appLoc</span><span class="hl-0">:</span><span class="hl-4">&quot;/Your/Path&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-10">serverType</span><span class="hl-0">:</span><span class="hl-4">&quot;SASVIYA&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-10">useComputeApi</span><span class="hl-0">: </span><span class="hl-1">false</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-10">contextName</span><span class="hl-0">: </span><span class="hl-10">&#39;yourComputeContext&#39;</span><br/><span class="hl-0">}</span>
</code></pre>
<a href="#using-the-compute-api" id="using-the-compute-api" style="color: inherit; text-decoration: none;">
<h3>Using the Compute API</h3>
</a>
<p>This approach is by far the fastest, as a result of the optimisations we have built into the adapter. With this configuration, in the first sasjs request, we take a URI map of the services in the target folder, and create a session manager. This manager will spawn a additional session every time a request is made. Subsequent requests will use the existing &#39;hot&#39; session, if it exists. Sessions are always deleted after every use, which actually makes this <em>less</em> resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes.</p>
<p>With this approach (<code>useComputeApi: true</code>), the requests/logs will <em>not</em> appear in the list in Environment manager.</p>
<pre><code class="language-json"><span class="hl-0">{</span><br/><span class="hl-0"> </span><span class="hl-10">appLoc</span><span class="hl-0">:</span><span class="hl-4">&quot;/Your/Path&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-10">serverType</span><span class="hl-0">:</span><span class="hl-4">&quot;SASVIYA&quot;</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-10">useComputeApi</span><span class="hl-0">: </span><span class="hl-1">true</span><span class="hl-0">,</span><br/><span class="hl-0"> </span><span class="hl-10">contextName</span><span class="hl-0">: </span><span class="hl-4">&quot;yourComputeContext&quot;</span><br/><span class="hl-0">}</span>
</code></pre>
<a href="#more-resources" id="more-resources" style="color: inherit; text-decoration: none;">
<h1>More resources</h1>
</a>
<p>For more information and examples specific to this adapter you can check out the <a href="https://sasjs.io/sasjs-adapter/">user guide</a> or the <a href="http://adapter.sasjs.io/">technical</a> documentation.</p>
<p>For more information on building web apps in general, check out these <a href="https://sasjs.io/training/resources/">resources</a> or contact the <a href="https://www.linkedin.com/in/allanbowe/">author</a> directly.</p>
<p>If you are a SAS 9 or SAS Viya customer you can also request a copy of <a href="https://datacontroller.io">Data Controller</a> - free for up to 5 users, this tool makes use of all parts of the SASjs framework.</p>
<a href="#star-gazing" id="star-gazing" style="color: inherit; text-decoration: none;">
<h2>Star Gazing</h2>
</a>
<p>If you find this library useful, help us grow our star graph!</p>
<p><img src="https://starchart.cc/sasjs/adapter.svg" alt=""></p>
<a href="#contributors-✨" id="contributors-✨" style="color: inherit; text-decoration: none;">
<h2>Contributors ✨</h2>
</a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<p><a href="#contributors-"><img src="https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square" alt="All Contributors"></a></p>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<p>Thanks goes to these wonderful people (<a href="https://allcontributors.org/docs/en/emoji-key">emoji key</a>):</p>
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-YuryShkoda" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Tests">⚠️</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a> <a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Amedjedovicm" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Tests">⚠️</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asaadjutt01" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Tests">⚠️</a> <a href="#mentoring-saadjutt01" title="Mentoring">🧑‍🏫</a> <a href="#infra-saadjutt01" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<p>This project follows the <a href="https://github.com/all-contributors/all-contributors">all-contributors</a> specification. Contributions of any kind welcome!</p>
</div></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class="current"><a href="modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="modules/SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="modules/SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="modules/SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="modules/SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="modules/types.html">types</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="assets/main.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>UploadFile | @sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@sasjs/adapter</a></li><li><a href="../modules/types.html">types</a></li><li><a href="types.UploadFile.html">UploadFile</a></li></ul><h1>Interface UploadFile</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel tsd-comment"><div class="tsd-comment tsd-typography"><div class="lead">
<p>Represents an object that is passed to the file uploader.</p>
</div></div></section><section class="tsd-panel tsd-hierarchy"><h3>Hierarchy</h3><ul class="tsd-hierarchy"><li><span class="target">UploadFile</span></li></ul></section><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Properties</h3><ul class="tsd-index-list"><li class="tsd-kind-property tsd-parent-kind-interface"><a href="types.UploadFile.html#file" class="tsd-kind-icon">file</a></li><li class="tsd-kind-property tsd-parent-kind-interface"><a href="types.UploadFile.html#fileName" class="tsd-kind-icon">file<wbr/>Name</a></li></ul></section></div></section></section><section class="tsd-panel-group tsd-member-group "><h2>Properties</h2><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-interface"><a id="file" class="tsd-anchor"></a><h3 class="tsd-anchor-link">file<a href="#file" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">file<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">File</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/UploadFile.ts#L6">types/UploadFile.ts:6</a></li></ul></aside></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-interface"><a id="fileName" class="tsd-anchor"></a><h3 class="tsd-anchor-link">file<wbr/>Name<a href="#fileName" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><div class="tsd-signature tsd-kind-icon">file<wbr/>Name<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/sasjs/adapter/blob/1ee07ee/src/types/UploadFile.ts#L7">types/UploadFile.ts:7</a></li></ul></aside></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="../modules/SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="../modules/SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="../modules/SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="../modules/SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class="current tsd-kind-module"><a href="../modules/types.html">types</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="current tsd-kind-interface tsd-parent-kind-module"><a href="types.UploadFile.html" class="tsd-kind-icon">Upload<wbr/>File</a><ul><li class="tsd-kind-property tsd-parent-kind-interface"><a href="types.UploadFile.html#file" class="tsd-kind-icon">file</a></li><li class="tsd-kind-property tsd-parent-kind-interface"><a href="types.UploadFile.html#fileName" class="tsd-kind-icon">file<wbr/>Name</a></li></ul></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li><li class="tsd-kind-property tsd-parent-kind-interface"><span class="tsd-kind-icon">Property</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>@sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="assets/style.css"/><link rel="stylesheet" href="assets/highlight.css"/><script async src="assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base="."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><h1> @sasjs/adapter</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Modules</h3><ul class="tsd-index-list"><li class="tsd-kind-module"><a href="modules/SAS9ApiClient.html" class="tsd-kind-icon">SAS9<wbr/>Api<wbr/>Client</a></li><li class="tsd-kind-module"><a href="modules/SASViyaApiClient.html" class="tsd-kind-icon">SASViya<wbr/>Api<wbr/>Client</a></li><li class="tsd-kind-module"><a href="modules/SASjs.html" class="tsd-kind-icon">SASjs</a></li><li class="tsd-kind-module"><a href="modules/SASjsApiClient.html" class="tsd-kind-icon">SASjs<wbr/>Api<wbr/>Client</a></li><li class="tsd-kind-module"><a href="modules/types.html" class="tsd-kind-icon">types</a></li></ul></section></div></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class="current"><a href="modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="modules/SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="modules/SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="modules/SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="modules/SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="modules/types.html">types</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="assets/main.js"></script></body></html>

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>SAS9ApiClient | @sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@sasjs/adapter</a></li><li><a href="SAS9ApiClient.html">SAS9ApiClient</a></li></ul><h1>Module SAS9ApiClient</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Classes</h3><ul class="tsd-index-list"><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SAS9ApiClient.SAS9ApiClient-1.html" class="tsd-kind-icon">SAS9<wbr/>Api<wbr/>Client</a></li></ul></section></div></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../modules.html">Modules</a></li><li class="current tsd-kind-module"><a href="SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="types.html">types</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SAS9ApiClient.SAS9ApiClient-1.html" class="tsd-kind-icon">SAS9<wbr/>Api<wbr/>Client</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>SASViyaApiClient | @sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@sasjs/adapter</a></li><li><a href="SASViyaApiClient.html">SASViyaApiClient</a></li></ul><h1>Module SASViyaApiClient</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Classes</h3><ul class="tsd-index-list"><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SASViyaApiClient.SASViyaApiClient-1.html" class="tsd-kind-icon">SASViya<wbr/>Api<wbr/>Client</a></li></ul></section></div></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class="current tsd-kind-module"><a href="SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="types.html">types</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SASViyaApiClient.SASViyaApiClient-1.html" class="tsd-kind-icon">SASViya<wbr/>Api<wbr/>Client</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>SASjs | @sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@sasjs/adapter</a></li><li><a href="SASjs.html">SASjs</a></li></ul><h1>Module SASjs</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Classes</h3><ul class="tsd-index-list"><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SASjs.SASjs-1.html" class="tsd-kind-icon">SASjs</a></li></ul></section></div></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class="current tsd-kind-module"><a href="SASjs.html">SASjs</a></li><li class=" tsd-kind-module"><a href="SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="types.html">types</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SASjs.SASjs-1.html" class="tsd-kind-icon">SASjs</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>SASjsApiClient | @sasjs/adapter</title><meta name="description" content="Documentation for @sasjs/adapter"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@sasjs/adapter</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@sasjs/adapter</a></li><li><a href="SASjsApiClient.html">SASjsApiClient</a></li></ul><h1>Module SASjsApiClient</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Classes</h3><ul class="tsd-index-list"><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SASjsApiClient.SASjsApiClient-1.html" class="tsd-kind-icon">SASjs<wbr/>Api<wbr/>Client</a></li></ul></section><section class="tsd-index-section "><h3>Interfaces</h3><ul class="tsd-index-list"><li class="tsd-kind-interface tsd-parent-kind-module"><a href="../interfaces/SASjsApiClient.SASjsAuthResponse.html" class="tsd-kind-icon">SASjs<wbr/>Auth<wbr/>Response</a></li></ul></section></div></section></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../modules.html">Modules</a></li><li class=" tsd-kind-module"><a href="SAS9ApiClient.html">SAS9<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="SASViyaApiClient.html">SASViya<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="SASjs.html">SASjs</a></li><li class="current tsd-kind-module"><a href="SASjsApiClient.html">SASjs<wbr/>Api<wbr/>Client</a></li><li class=" tsd-kind-module"><a href="types.html">types</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/SASjsApiClient.SASjsApiClient-1.html" class="tsd-kind-icon">SASjs<wbr/>Api<wbr/>Client</a></li><li class="tsd-kind-interface tsd-parent-kind-module"><a href="../interfaces/SASjsApiClient.SASjsAuthResponse.html" class="tsd-kind-icon">SASjs<wbr/>Auth<wbr/>Response</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>

File diff suppressed because one or more lines are too long

2880
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,11 @@
"description": "JavaScript adapter for SAS",
"homepage": "https://adapter.sasjs.io",
"scripts": {
"preinstall": "node checkNodeVersion",
"prebuild": "node checkNodeVersion",
"nodeVersionMessage": "echo \u001b[33m make sure you are running node lts version \u001b[0m",
"preinstall": "npm run nodeVersionMessage",
"prebuild": "npm run nodeVersionMessage",
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
"package:lib": "npm run build && copyfiles ./package.json ./checkNodeVersion.js build && cd build && npm version \"5.0.0\" && npm pack",
"package:lib": "npm run build && copyfiles ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
"publish:lib": "npm run build && cd build && npm publish",
"lint:fix": "npx prettier --loglevel silent --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
@@ -44,9 +45,7 @@
"license": "ISC",
"devDependencies": {
"@cypress/webpack-preprocessor": "5.9.1",
"@types/axios": "0.14.0",
"@types/express": "4.17.13",
"@types/form-data": "2.5.0",
"@types/jest": "27.4.0",
"@types/mime": "2.0.3",
"@types/pem": "1.9.6",
@@ -67,21 +66,19 @@
"semantic-release": "19.0.3",
"terser-webpack-plugin": "5.3.1",
"ts-jest": "27.1.3",
"ts-loader": "9.2.6",
"ts-loader": "9.4.0",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"typedoc": "0.22.11",
"typedoc-neo-theme": "1.1.1",
"typedoc-plugin-external-module-name": "4.0.6",
"typedoc-plugin-rename-defaults": "0.4.0",
"typescript": "4.5.5",
"typedoc": "0.23.24",
"typedoc-plugin-rename-defaults": "0.6.4",
"typescript": "4.8.3",
"webpack": "5.69.0",
"webpack-cli": "4.9.2"
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "2.44.0",
"axios": "0.26.0",
"@sasjs/utils": "2.52.0",
"axios": "0.27.2",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",
"https": "1.0.0",

View File

@@ -1,10 +0,0 @@
#!/bin/bash
if npm run cy:run -- --spec "cypress/integration/sasjs.tests.ts" ; then
echo "Cypress sasjs testing passed!"
else
curl -X POST --header "Content-Type:application/json" --data '{"username":"GitHub CI - Adapter SASJS-TESTS (FAIL)", "content":"Automated sasjs-tests failed on the @sasjs/adapter PR on following link.\n'$2'", "avatar_url":"https://i.ibb.co/Lpk7Xvq/error-outline.png"}' $1
echo "Cypress sasjs testing failed!"
exit 1
fi

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"private": true,
"dependencies": {
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
"@sasjs/test-framework": "^1.5.6",
"@sasjs/test-framework": "1.5.7",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.41",
"@types/react": "^17.0.1",
@@ -43,6 +43,6 @@
]
},
"devDependencies": {
"node-sass": "^7.0.1"
"node-sass": "7.0.3"
}
}

View File

@@ -0,0 +1,10 @@
#!/bin/bash
if npm run cy:run -- --spec "cypress/integration/sasjs.tests.ts" ; then
echo "Cypress sasjs testing passed!"
else
echo '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}'
curl -XPOST -d '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}' https://matrix.4gl.io/_matrix/client/r0/rooms/%21jRebyiGmHZlpfDwYXN:4gl.io:4gl.io/send/m.room.message?access_token=$1
echo "Cypress sasjs testing failed!"
exit 1
fi

View File

@@ -71,13 +71,12 @@ export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({
test: () => {
const fileLines = [`data;`, `do x=1 to 100;`, `output;`, `end;`, `run;`]
return adapter.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
)
return adapter.executeScript({
fileName: 'sasCode.sas',
linesOfCode: fileLines,
contextName: 'SAS Studio compute context',
debug: true
})
},
assertion: (res: any) => {
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
@@ -92,13 +91,12 @@ export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({
const fileLines = [`%abort;`]
return adapter
.executeScriptSASViya(
'sasCode.sas',
fileLines,
'SAS Studio compute context',
undefined,
true
)
.executeScript({
fileName: 'sasCode.sas',
linesOfCode: fileLines,
contextName: 'SAS Studio compute context',
debug: true
})
.catch((err: any) => err)
},
assertion: (res: any) => {

View File

@@ -1,4 +1,4 @@
import { compareTimestamps, asyncForEach } from './utils'
import { compareTimestamps, asyncForEach, validateInput } from './utils'
import {
SASjsConfig,
UploadFile,
@@ -17,13 +17,15 @@ import {
AuthConfig,
ExtraResponseAttributes,
SasAuthResponse,
ServicePackSASjs
ServicePackSASjs,
AuthConfigSas9
} from '@sasjs/utils/types'
import { RequestClient } from './request/RequestClient'
import { SasjsRequestClient } from './request/SasjsRequestClient'
import {
JobExecutor,
WebJobExecutor,
SasjsJobExecutor,
ComputeJobExecutor,
JesJobExecutor,
Sas9JobExecutor,
@@ -32,6 +34,16 @@ import {
import { ErrorResponse } from './types/errors'
import { LoginOptions, LoginResult } from './types/Login'
interface ExecuteScriptParams {
linesOfCode: string[]
fileName?: string
contextName?: string
runTime?: string
authConfig?: AuthConfig
authConfigSas9?: AuthConfigSas9
debug?: boolean
}
const defaultConfig: SASjsConfig = {
serverUrl: '',
pathSASJS: '/SASjsApi/stp/execute',
@@ -59,6 +71,7 @@ export default class SASjs {
private authManager: AuthManager | null = null
private requestClient: RequestClient | null = null
private webJobExecutor: JobExecutor | null = null
private sasjsJobExecutor: JobExecutor | null = null
private computeJobExecutor: JobExecutor | null = null
private jesJobExecutor: JobExecutor | null = null
private sas9JobExecutor: JobExecutor | null = null
@@ -77,70 +90,73 @@ export default class SASjs {
}
/**
* Executes SAS code on a SAS 9 server. Requires a runner to be present in
* the users home directory in metadata.
* @param linesOfCode - lines of sas code from the file to run.
* @param username - a string representing the username.
* @param password - a string representing the password.
*/
public async executeScriptSAS9(
linesOfCode: string[],
userName: string,
password: string
) {
this.isMethodSupported('executeScriptSAS9', [ServerType.Sas9])
return await this.sas9ApiClient?.executeScript(
linesOfCode,
userName,
password
)
}
/**
* Executes SAS code on a SASJS server
* @param code - a string of code from the file to run.
* Executes code on a SAS server.
* @param linesOfCode - lines of code to run.
* @param fileName - (required for server type sas viya) name of the file to run. It will be converted to path to the file being submitted for execution.
* @param contextName - (required for server type sas viya) context name on which code will be run on the server.
* @param runTime - (required for server type sasjs) a string to represent runTime for code execution.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute scripts.
*/
public async executeScriptSASjs(code: string, authConfig?: AuthConfig) {
this.isMethodSupported('executeScriptSASJS', [ServerType.Sasjs])
return await this.sasJSApiClient?.executeScript(code, authConfig)
}
/**
* Executes sas code in a SAS Viya compute session.
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
* @param linesOfCode - lines of sas code from the file to run.
* @param contextName - context name on which code will be run on the server.
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
* @param authConfigSas9 - (required for server type sas9) a valid username and password that are authorised to execute scripts.
* @param debug - (optional) if true, global debug config will be overriden
*/
public async executeScriptSASViya(
fileName: string,
linesOfCode: string[],
contextName: string,
authConfig?: AuthConfig,
debug?: boolean
) {
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
public async executeScript({
linesOfCode,
fileName,
contextName,
runTime,
authConfig,
authConfigSas9,
debug
}: ExecuteScriptParams) {
this.isMethodSupported('executeScript', [
ServerType.Sas9,
ServerType.Sasjs,
ServerType.SasViya
])
contextName = contextName || this.sasjsConfig.contextName
if (this.sasjsConfig.serverType === ServerType.Sas9) {
if (!authConfigSas9)
throw new Error('Auth config for sas9 is not provided')
if (!contextName) {
throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
return await this.sas9ApiClient?.executeScript(
linesOfCode,
authConfigSas9.userName,
authConfigSas9.password
)
}
return await this.sasViyaApiClient!.executeScript(
fileName,
linesOfCode,
contextName,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
return await this.sasJSApiClient?.executeScript(
linesOfCode.join('\n'),
runTime,
authConfig
)
}
if (this.sasjsConfig.serverType === ServerType.SasViya) {
contextName = contextName || this.sasjsConfig.contextName
if (!contextName) {
throw new Error(
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
)
}
if (!fileName) {
throw new Error(
'File name is required in case of SAS VIYA. Please provide a `fileName`.'
)
}
return await this.sasViyaApiClient!.executeScript(
fileName,
linesOfCode,
contextName,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
}
}
/**
@@ -686,12 +702,12 @@ export default class SASjs {
...config
}
const validationResult = this.validateInput(data)
const validationResult = validateInput(data)
// status is true if the data passes validation checks above
if (validationResult.status) {
if (config.serverType === ServerType.Sasjs) {
return await this.webJobExecutor!.execute(
return await this.sasjsJobExecutor!.execute(
sasJob,
data,
config,
@@ -748,74 +764,6 @@ export default class SASjs {
}
}
/**
* This function validates the input data structure and table naming convention
*
* @param data A json object that contains one or more tables, it can also be null
* @returns An object which contains two attributes: 1) status: boolean, 2) msg: string
*/
private validateInput(data: { [key: string]: any } | null): {
status: boolean
msg: string
} {
if (data === null) return { status: true, msg: '' }
const isSasFormatsTable = (key: string) =>
key.match(/^\$.*/) && Object.keys(data).includes(key.replace(/^\$/, ''))
for (const key in data) {
if (!key.match(/^[a-zA-Z_]/) && !isSasFormatsTable(key)) {
return {
status: false,
msg: 'First letter of table should be alphabet or underscore.'
}
}
if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) && !isSasFormatsTable(key)) {
return { status: false, msg: 'Table name should be alphanumeric.' }
}
if (key.length > 32) {
return {
status: false,
msg: 'Maximum length for table name could be 32 characters.'
}
}
if (this.getType(data[key]) !== 'Array' && !isSasFormatsTable(key)) {
return {
status: false,
msg: 'Parameter data contains invalid table structure.'
}
}
for (let i = 0; i < data[key].length; i++) {
if (this.getType(data[key][i]) !== 'object') {
return {
status: false,
msg: `Table ${key} contains invalid structure.`
}
}
}
}
return { status: true, msg: '' }
}
/**
* this function returns the type of variable
*
* @param data it could be anything, like string, array, object etc.
* @returns a string which tells the type of input parameter
*/
private getType(data: any): string {
if (Array.isArray(data)) {
return 'Array'
} else {
return typeof data
}
}
/**
* Creates the folders and services at the given location `appLoc` on the given server `serverUrl`.
* @param serviceJson - the JSON specifying the folders and services to be created.
@@ -888,29 +836,6 @@ export default class SASjs {
)
}
/**
* Creates the folders and services at the given location `appLoc` on the given server `serverUrl`.
* @param dataJson - the JSON specifying the folders and files to be created, can also includes
* appLoc, streamServiceName, streamWebFolder, streamLogo
* @param appLoc - (optional) the base folder in which to create the new folders and
* services. If not provided, is taken from SASjsConfig. Precedence will be of appLoc present in dataJson.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
*/
public async deployToSASjs(
dataJson: ServicePackSASjs,
appLoc?: string,
authConfig?: AuthConfig
) {
if (!appLoc) {
appLoc = this.sasjsConfig.appLoc
}
return await this.sasJSApiClient?.deploy(dataJson, appLoc, authConfig)
}
public async executeJobSASjs(query: ExecutionQuery, authConfig?: AuthConfig) {
return await this.sasJSApiClient?.executeJob(query, authConfig)
}
/**
* Kicks off execution of the given job via the compute API.
* @returns an object representing the compute session created for the given job.
@@ -970,6 +895,7 @@ export default class SASjs {
await this.computeJobExecutor?.resendWaitingRequests()
await this.jesJobExecutor?.resendWaitingRequests()
await this.fileUploader?.resendWaitingRequests()
await this.sasjsJobExecutor?.resendWaitingRequests()
}
/**
@@ -1117,6 +1043,12 @@ export default class SASjs {
this.sasViyaApiClient!
)
this.sasjsJobExecutor = new SasjsJobExecutor(
this.sasjsConfig.serverUrl,
this.jobsPath,
this.requestClient
)
this.sas9JobExecutor = new Sas9JobExecutor(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,

View File

@@ -1,28 +1,39 @@
import * as NodeFormData from 'form-data'
import { AuthConfig, ServerType, ServicePackSASjs } from '@sasjs/utils/types'
import { ExecutionQuery } from './types'
import { RequestClient } from './request/RequestClient'
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
import { parseWeboutResponse } from './utils'
import { getTokens } from './auth/getTokens'
export class SASjsApiClient {
constructor(private requestClient: RequestClient) {}
private async getAccessTokenForRequest(authConfig?: AuthConfig) {
if (authConfig) {
const { access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
)
return access_token
}
}
/**
* Creates the folders and services at the given location `appLoc` on the given server `serverUrl`.
* @param dataJson - the JSON specifying the folders and files to be created, can also includes
* appLoc, streamServiceName, streamWebFolder, streamLogo
* @param appLoc - the base folder in which to create the new folders and services.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
*/
public async deploy(
dataJson: ServicePackSASjs,
appLoc: string,
authConfig?: AuthConfig
) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
))
}
const access_token = await this.getAccessTokenForRequest(authConfig)
dataJson.appLoc = dataJson.appLoc || appLoc
const { result } = await this.requestClient.post<{
@@ -42,53 +53,80 @@ export class SASjsApiClient {
return Promise.resolve(result)
}
public async executeJob(query: ExecutionQuery, authConfig?: AuthConfig) {
const access_token = authConfig ? authConfig.access_token : undefined
/**
* Creates/updates files within SASjs drive using uploaded json compressed file.
* @param zipFilePath - Compressed file path; file should only contain one JSON file and
* should have same name as of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip
* Any other file or JSON file in zipped will be ignored!
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
*/
public async deployZipFile(zipFilePath: string, authConfig?: AuthConfig) {
const { createReadStream } = require('@sasjs/utils/file')
const access_token = await this.getAccessTokenForRequest(authConfig)
const file = await createReadStream(zipFilePath)
const formData = new NodeFormData()
formData.append('file', file)
const contentType = `multipart/form-data; boundary=${formData.getBoundary()}`
const { result } = await this.requestClient.post<{
status: string
message: string
log?: string
logPath?: string
error?: {}
_webout?: string
}>('SASjsApi/stp/execute', query, access_token)
if (Object.keys(result).includes('_webout')) {
result._webout = parseWeboutResponse(result._webout!)
}
streamServiceName?: string
example?: {}
}>(
'SASjsApi/drive/deploy/upload',
formData,
access_token,
contentType,
{},
{ maxContentLength: Infinity, maxBodyLength: Infinity }
)
return Promise.resolve(result)
}
public async executeJob(
query: ExecutionQuery,
appLoc: string,
authConfig?: AuthConfig
) {
const access_token = authConfig ? authConfig.access_token : undefined
let _program
if (query._program.startsWith('/')) {
_program = query._program
} else _program = `${appLoc}/${query._program}`
const response: any = await this.requestClient.post(
'SASjsApi/stp/execute',
{ _debug: 131, ...query, _program },
access_token
)
return { result: response.result, log: response.log }
}
/**
* Executes code on a SASJS server.
* @param code - a string of code to execute.
* @param runTime - a string to representing runTime for code execution
* @param authConfig - an object for authentication.
*/
public async executeScript(code: string, authConfig?: AuthConfig) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
))
}
public async executeScript(
code: string,
runTime: string = 'sas',
authConfig?: AuthConfig
) {
const access_token = await this.getAccessTokenForRequest(authConfig)
let parsedSasjsServerLog = ''
await this.requestClient
.post('SASjsApi/code/execute', { code }, access_token)
.post('SASjsApi/code/execute', { code, runTime }, access_token)
.then((res: any) => {
if (res.result?.log) {
parsedSasjsServerLog = res.result.log
.map((logLine: any) => logLine.line)
.join('\n')
}
})
.catch((err) => {
parsedSasjsServerLog = err
if (res.log) parsedSasjsServerLog = res.log
})
return parsedSasjsServerLog

View File

@@ -1,13 +1,15 @@
import { ServerType } from '@sasjs/utils/types'
import { RequestClient } from '../request/RequestClient'
import { LoginOptions, LoginResult } from '../types/Login'
import { LoginOptions, LoginResult, LoginResultInternal } from '../types/Login'
import { serialize } from '../utils'
import { extractUserLongNameSas9 } from '../utils/sas9/extractUserLongNameSas9'
import { openWebPage } from './openWebPage'
import { verifySas9Login } from './verifySas9Login'
import { verifySasViyaLogin } from './verifySasViyaLogin'
export class AuthManager {
public userName = ''
public userLongName = ''
private loginUrl: string
private logoutUrl: string
private redirectedLoginUrl = `/SASLogon/home`
@@ -33,15 +35,19 @@ export class AuthManager {
public async redirectedLogIn({
onLoggedOut
}: LoginOptions): Promise<LoginResult> {
const { isLoggedIn: isLoggedInAlready, userName: currentSessionUsername } =
await this.fetchUserName()
const {
isLoggedIn: isLoggedInAlready,
userName: currentSessionUserName,
userLongName: currentSessionUserLongName
} = await this.fetchUserName()
if (isLoggedInAlready) {
await this.loginCallback()
return {
isLoggedIn: true,
userName: currentSessionUsername
userName: currentSessionUserName,
userLongName: currentSessionUserLongName
}
}
@@ -56,7 +62,7 @@ export class AuthManager {
)
if (!loginPopup) {
return { isLoggedIn: false, userName: '' }
return { isLoggedIn: false, userName: '', userLongName: '' }
}
const { isLoggedIn } =
@@ -71,14 +77,14 @@ export class AuthManager {
await this.performCASSecurityCheck()
}
const { userName } = await this.fetchUserName()
const { userName, userLongName } = await this.fetchUserName()
await this.loginCallback()
return { isLoggedIn: true, userName }
return { isLoggedIn: true, userName, userLongName }
}
return { isLoggedIn: false, userName: '' }
return { isLoggedIn: false, userName: '', userLongName: '' }
}
/**
@@ -93,27 +99,26 @@ export class AuthManager {
username,
password
}
this.userName = ''
this.userLongName = ''
let {
isLoggedIn: isLoggedInAlready,
loginForm,
userName: currentSessionUsername
userLongName: currentSessionUserLongName
} = await this.checkSession()
if (isLoggedInAlready) {
if (currentSessionUsername === loginParams.username) {
await this.loginCallback()
await this.loginCallback()
this.userName = currentSessionUsername!
return {
isLoggedIn: true,
userName: this.userName
}
} else {
await this.logOut()
loginForm = await this.getNewLoginForm()
this.userName = loginParams.username
this.userLongName = currentSessionUserLongName
return {
isLoggedIn: true,
userName: this.userName,
userLongName: this.userLongName
}
} else this.userName = ''
}
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
@@ -128,10 +133,7 @@ export class AuthManager {
const res = await this.checkSession()
isLoggedIn = res.isLoggedIn
if (isLoggedIn) this.userName = res.userName
} else {
this.userName = loginParams.username
this.userLongName = res.userLongName
}
if (isLoggedIn) {
@@ -140,11 +142,13 @@ export class AuthManager {
}
this.loginCallback()
} else this.userName = ''
this.userName = loginParams.username
}
return {
isLoggedIn,
userName: this.userName
userName: this.userName,
userLongName: this.userLongName
}
}
@@ -195,15 +199,12 @@ export class AuthManager {
* Checks whether a session is active, or login is required.
* @returns - a promise which resolves with an object containing three values
* - a boolean `isLoggedIn`
* - a string `userName` and
* - a string `userName`,
* - a string `userFullName` and
* - a form `loginForm` if not loggedin.
*/
public async checkSession(): Promise<{
isLoggedIn: boolean
userName: string
loginForm?: any
}> {
const { isLoggedIn, userName } = await this.fetchUserName()
public async checkSession(): Promise<LoginResultInternal> {
const { isLoggedIn, userName, userLongName } = await this.fetchUserName()
let loginForm = null
if (!isLoggedIn) {
@@ -216,20 +217,29 @@ export class AuthManager {
return Promise.resolve({
isLoggedIn,
userName: userName.toLowerCase(),
userName,
userLongName,
loginForm
})
}
private async getNewLoginForm() {
if (this.serverType === ServerType.Sasjs) {
// server will be sending CSRF cookie,
// server will be sending CSRF token in response,
// need to save in cookie so that,
// http client will use it automatically
return this.requestClient.get('/', undefined)
return this.requestClient.get('/', undefined).then(({ result }) => {
const cookie =
/<script>document.cookie = '(XSRF-TOKEN=.*; Max-Age=86400; SameSite=Strict; Path=\/;)'<\/script>/.exec(
result as string
)?.[1]
if (cookie) document.cookie = cookie
})
}
const { result: formResponse } = await this.requestClient.get<string>(
this.loginUrl.replace('.do', ''),
this.loginUrl.replace('/SASLogon/login.do', '/SASLogon/login'),
undefined,
'text/plain'
)
@@ -237,10 +247,7 @@ export class AuthManager {
return await this.getLoginForm(formResponse)
}
private async fetchUserName(): Promise<{
isLoggedIn: boolean
userName: string
}> {
private async fetchUserName(): Promise<LoginResult> {
const url =
this.serverType === ServerType.SasViya
? `${this.serverUrl}/identities/users/@currentUser`
@@ -255,15 +262,19 @@ export class AuthManager {
})
const isLoggedIn = loginResponse !== 'authErr'
const userName = isLoggedIn ? this.extractUserName(loginResponse) : ''
if (!isLoggedIn) {
//We will logout to make sure cookies are removed and login form is presented
//Residue can happen in case of session expiration
await this.logOut()
return { isLoggedIn, userName: '', userLongName: '' }
}
return { isLoggedIn, userName }
return {
isLoggedIn,
userName: this.extractUserName(loginResponse),
userLongName: this.extractUserLongName(loginResponse)
}
}
private extractUserName = (response: any): string => {
@@ -272,15 +283,7 @@ export class AuthManager {
return response?.id
case ServerType.Sas9:
const matched = response?.match(/"title":"Log Off [0-1a-zA-Z ]*"/)
const username = matched?.[0].slice(17, -1)
if (!username.includes(' ')) return username
return username
.split(' ')
.map((name: string) => name.slice(0, 3).toLowerCase())
.join('')
return ''
case ServerType.Sasjs:
return response?.username
@@ -291,6 +294,23 @@ export class AuthManager {
}
}
private extractUserLongName = (response: any): string => {
switch (this.serverType) {
case ServerType.SasViya:
return response?.name
case ServerType.Sas9:
return extractUserLongNameSas9(response)
case ServerType.Sasjs:
return response?.displayName
default:
console.error('Server Type not found in extractUserName function')
return ''
}
}
private getLoginForm(response: any) {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
const matches = pattern.exec(response)
@@ -328,7 +348,7 @@ export class AuthManager {
this.loginUrl =
this.serverType === ServerType.SasViya
? tempLoginLink
: loginUrl.replace('.do', '')
: loginUrl.replace('/SASLogon/login.do', '/SASLogon/login')
}
}

View File

@@ -20,6 +20,7 @@ describe('AuthManager', () => {
const serverUrl = 'http://test-server.com'
const serverType = ServerType.SasViya
const userName = 'test-username'
const userLongName = 'test-user long name'
const password = 'test-password'
const requestClient = new RequestClient(serverUrl)
@@ -73,6 +74,7 @@ describe('AuthManager', () => {
Promise.resolve({
isLoggedIn: true,
userName,
userLongName,
loginForm: 'test'
})
)
@@ -95,49 +97,24 @@ describe('AuthManager', () => {
Promise.resolve({
isLoggedIn: true,
userName: 'someOtherUsername',
userLongName: 'someOtherUser Long name',
loginForm: null
})
)
jest
.spyOn(authManager, 'logOut')
.mockImplementation(() => Promise.resolve(true))
jest.spyOn(authManager, 'logOut')
jest
.spyOn<any, any>(authManager, 'getNewLoginForm')
.mockImplementation(() =>
Promise.resolve({
name: 'test'
})
)
mockedAxios.post.mockImplementation(() =>
Promise.resolve({ data: mockLoginSuccessResponse })
)
jest.spyOn<any, any>(authManager, 'getNewLoginForm')
jest.spyOn<any, any>(authManager, 'sendLoginRequest')
const loginResponse = await authManager.logIn(userName, password)
expect(loginResponse.isLoggedIn).toBeTruthy()
expect(loginResponse.userName).toEqual(userName)
const loginParams = serialize({
_service: 'default',
username: userName,
password,
name: 'test'
})
expect(authCallback).toHaveBeenCalledTimes(1)
expect(authManager.logOut).toHaveBeenCalledTimes(1)
expect(authManager['getNewLoginForm']).toHaveBeenCalledTimes(1)
expect(mockedAxios.post).toHaveBeenCalledWith(
`/SASLogon/login`,
loginParams,
{
withCredentials: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*'
}
}
)
expect(authManager.logOut).toHaveBeenCalledTimes(0)
expect(authManager['getNewLoginForm']).toHaveBeenCalledTimes(0)
expect(authManager['sendLoginRequest']).toHaveBeenCalledTimes(0)
expect(authCallback).toHaveBeenCalledTimes(1)
})
@@ -152,6 +129,7 @@ describe('AuthManager', () => {
Promise.resolve({
isLoggedIn: false,
userName: '',
userLongName: '',
loginForm: { name: 'test' }
})
)
@@ -196,6 +174,7 @@ describe('AuthManager', () => {
Promise.resolve({
isLoggedIn: false,
userName: '',
userLongName: '',
loginForm: { name: 'test' }
})
)
@@ -245,6 +224,7 @@ describe('AuthManager', () => {
Promise.resolve({
isLoggedIn: false,
userName: '',
userLongName: '',
loginForm: { name: 'test' }
})
)
@@ -290,6 +270,7 @@ describe('AuthManager', () => {
Promise.resolve({
isLoggedIn: false,
userName: 'test',
userLongName: 'test Long name',
loginForm: { name: 'test' }
})
)
@@ -560,43 +541,8 @@ describe('AuthManager', () => {
)
})
it('return session information when logged in - SAS9', async () => {
// username cannot have `-` and cannot be uppercased
const username = 'testusername'
const serverType = ServerType.Sas9
const authManager = new AuthManager(
serverUrl,
serverType,
requestClient,
authCallback
)
mockedAxios.get.mockImplementation(() =>
Promise.resolve({
data: `"title":"Log Off ${username}","url":"javascript: clearFrame(\"/SASStoredProcess/do?_action=logoff\")"' })`
})
)
const response = await authManager.checkSession()
expect(response.isLoggedIn).toBeTruthy()
expect(response.userName).toEqual(username)
expect(mockedAxios.get).toHaveBeenNthCalledWith(
1,
`http://test-server.com/SASStoredProcess`,
{
withCredentials: true,
responseType: 'text',
transformResponse: undefined,
headers: {
Accept: '*/*',
'Content-Type': 'text/plain'
}
}
)
})
it('return session information when logged in - SAS9 - having full name in html', async () => {
const fullname = 'FirstName LastName'
const username = 'firlas'
const serverType = ServerType.Sas9
const authManager = new AuthManager(
serverUrl,
@@ -612,7 +558,8 @@ describe('AuthManager', () => {
const response = await authManager.checkSession()
expect(response.isLoggedIn).toBeTruthy()
expect(response.userName).toEqual(username)
expect(response.userName).toEqual('')
expect(response.userLongName).toEqual(fullname)
expect(mockedAxios.get).toHaveBeenNthCalledWith(
1,
`http://test-server.com/SASStoredProcess`,

View File

@@ -3,4 +3,6 @@ export * from './types'
export * from './types/errors'
export * from './SASViyaApiClient'
export * from './SAS9ApiClient'
export * from './SASjsApiClient'
export * from './request/SasjsRequestClient'
export default SASjs

View File

@@ -1,7 +1,8 @@
import {
getValidJson,
parseSasViyaDebugResponse,
parseWeboutResponse
parseWeboutResponse,
SASJS_LOGS_SEPARATOR
} from '../utils'
import { UploadFile } from '../types/UploadFile'
import {
@@ -99,21 +100,8 @@ export class FileUploader extends BaseJobExecutor {
? parseWeboutResponse(res.result, uploadUrl)
: res.result
break
case ServerType.Sasjs:
if (typeof res.result._webout === 'object') {
jsonResponse = res.result._webout
} else {
const webout = parseWeboutResponse(
res.result._webout,
uploadUrl
)
jsonResponse = getValidJson(webout)
}
break
}
} else if (this.serverType === ServerType.Sasjs) {
jsonResponse = getValidJson(res.result._webout)
} else {
} else if (this.serverType !== ServerType.Sasjs) {
jsonResponse =
typeof res.result === 'string'
? getValidJson(res.result)

View File

@@ -1,6 +1,6 @@
import { AuthConfig, ServerType } from '@sasjs/utils/types'
import { ExtraResponseAttributes } from '@sasjs/utils/types'
import { asyncForEach } from '../utils'
import { asyncForEach, isRelativePath } from '../utils'
export type ExecuteFunction = () => Promise<any>
@@ -45,4 +45,17 @@ export abstract class BaseJobExecutor implements JobExecutor {
protected appendWaitingRequest(request: ExecuteFunction) {
this.waitingRequests.push(request)
}
protected getRequestParams(config: any): any {
const requestParams: any = {}
if (config.debug) {
requestParams['_omittextlog'] = 'false'
requestParams['_omitsessionresults'] = 'false'
requestParams['_debug'] = 131
}
return requestParams
}
}

View File

@@ -102,7 +102,7 @@ export class Sas9JobExecutor extends BaseJobExecutor {
return requestPromise
}
private getRequestParams(config: any): any {
protected getRequestParams(config: any): any {
const requestParams: any = {}
if (config.debug) {

View File

@@ -0,0 +1,141 @@
import * as NodeFormData from 'form-data'
import {
AuthConfig,
ExtraResponseAttributes,
ServerType
} from '@sasjs/utils/types'
import {
ErrorResponse,
JobExecutionError,
LoginRequiredError
} from '../types/errors'
import { generateFileUploadForm } from '../file/generateFileUploadForm'
import { RequestClient } from '../request/RequestClient'
import { isRelativePath, appendExtraResponseAttributes } from '../utils'
import { BaseJobExecutor } from './JobExecutor'
export class SasjsJobExecutor extends BaseJobExecutor {
constructor(
serverUrl: string,
private jobsPath: string,
private requestClient: RequestClient
) {
super(serverUrl, ServerType.Sasjs)
}
async execute(
sasJob: string,
data: any,
config: any,
loginRequiredCallback?: any,
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
const loginCallback = loginRequiredCallback
const program =
isRelativePath(sasJob) && config.appLoc
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
let requestParams = {
...this.getRequestParams(config)
}
/**
* Use the available form data object (FormData in Browser, NodeFormData in
* Node)
*/
let formData =
typeof FormData === 'undefined' ? new NodeFormData() : new FormData()
if (data) {
// file upload approach
try {
formData = generateFileUploadForm(formData, data)
} catch (e: any) {
return Promise.reject(new ErrorResponse(e?.message, e))
}
}
for (const key in requestParams) {
if (requestParams.hasOwnProperty(key)) {
formData.append(key, requestParams[key])
}
}
/* The NodeFormData object does not set the request header - so, set it */
const contentType =
formData instanceof NodeFormData && typeof FormData === 'undefined'
? `multipart/form-data; boundary=${formData.getBoundary()}`
: undefined
const requestPromise = new Promise((resolve, reject) => {
this.requestClient!.post(
apiUrl,
formData,
authConfig?.access_token,
contentType
)
.then(async (res: any) => {
if (Object.entries(res.result).length < 1) {
throw new JobExecutionError(
0,
`No webout was returned by job ${program}. Please check the SAS log for more info.`,
res.log
)
}
this.requestClient!.appendRequest(res, sasJob, config.debug)
const responseObject = appendExtraResponseAttributes(
res,
extraResponseAttributes
)
resolve(responseObject)
})
.catch(async (e: Error) => {
if (e instanceof JobExecutionError) {
this.requestClient!.appendRequest(e, sasJob, config.debug)
reject(new ErrorResponse(e?.message, e))
}
if (e instanceof LoginRequiredError) {
if (!loginRequiredCallback) {
reject(
new ErrorResponse(
'Request is not authenticated. Make sure .env file exists with valid credentials.',
e
)
)
}
this.appendWaitingRequest(() => {
return this.execute(
sasJob,
data,
config,
loginRequiredCallback,
authConfig,
extraResponseAttributes
).then(
(res: any) => {
resolve(res)
},
(err: any) => {
reject(err)
}
)
})
if (loginCallback) await loginCallback()
} else reject(new ErrorResponse(e?.message, e))
})
})
return requestPromise
}
}

View File

@@ -16,12 +16,10 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
import {
isRelativePath,
parseSasViyaDebugResponse,
appendExtraResponseAttributes,
getValidJson
appendExtraResponseAttributes
} from '../utils'
import { BaseJobExecutor } from './JobExecutor'
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
import { Server } from 'https'
export interface WaitingRequstPromise {
promise: Promise<any> | null
@@ -121,7 +119,6 @@ export class WebJobExecutor extends BaseJobExecutor {
const stringifiedData = JSON.stringify(data)
if (
config.serverType === ServerType.Sas9 ||
config.serverType === ServerType.Sasjs ||
stringifiedData.length > 500000 ||
stringifiedData.includes(';')
) {
@@ -164,31 +161,7 @@ export class WebJobExecutor extends BaseJobExecutor {
contentType
)
.then(async (res: any) => {
const parsedSasjsServerLog =
this.serverType === ServerType.Sasjs
? res.result.log.map((logLine: any) => logLine.line).join('\n')
: res.result.log
const resObj =
this.serverType === ServerType.Sasjs
? {
result: res.result._webout,
log: parsedSasjsServerLog
}
: res
if (
this.serverType === ServerType.Sasjs &&
res.result._webout.length < 1
) {
throw new JobExecutionError(
0,
`No webout was returned by job ${program}. Server type is SASJS and the calling function is WebJobExecutor. Please check the SAS log for more info.`,
parsedSasjsServerLog
)
}
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
this.requestClient!.appendRequest(res, sasJob, config.debug)
let jsonResponse = res.result
@@ -207,21 +180,11 @@ export class WebJobExecutor extends BaseJobExecutor {
? parseWeboutResponse(res.result, apiUrl)
: res.result
break
case ServerType.Sasjs:
if (typeof res.result._webout === 'object') {
jsonResponse = res.result._webout
} else {
const webout = parseWeboutResponse(res.result._webout, apiUrl)
jsonResponse = getValidJson(webout)
}
break
}
} else if (this.serverType === ServerType.Sasjs) {
jsonResponse = getValidJson(res.result._webout)
}
const responseObject = appendExtraResponseAttributes(
{ result: jsonResponse, log: parsedSasjsServerLog },
{ result: jsonResponse, log: res.log },
extraResponseAttributes
)
resolve(responseObject)
@@ -261,9 +224,7 @@ export class WebJobExecutor extends BaseJobExecutor {
})
if (loginCallback) await loginCallback()
} else {
reject(new ErrorResponse(e?.message, e))
}
} else reject(new ErrorResponse(e?.message, e))
})
})
@@ -301,39 +262,4 @@ export class WebJobExecutor extends BaseJobExecutor {
}
return uri
}
private getRequestParams(config: any): any {
const requestParams: any = {}
if (config.debug) {
requestParams['_omittextlog'] = 'false'
requestParams['_omitsessionresults'] = 'false'
requestParams['_debug'] = 131
}
return requestParams
}
private parseSAS9ErrorResponse(response: string) {
const logLines = response.split('\n')
const parsedLines: string[] = []
let firstErrorLineIndex: number = -1
logLines.map((line: string, index: number) => {
if (
line.toLowerCase().includes('error') &&
!line.toLowerCase().includes('this request completed with errors.') &&
firstErrorLineIndex === -1
) {
firstErrorLineIndex = index
}
})
for (let i = firstErrorLineIndex - 10; i <= firstErrorLineIndex + 10; i++) {
parsedLines.push(logLines[i])
}
return parsedLines.join(', ')
}
}

View File

@@ -4,3 +4,4 @@ export * from './JesJobExecutor'
export * from './JobExecutor'
export * from './Sas9JobExecutor'
export * from './WebJobExecutor'
export * from './SasjsJobExecutor'

View File

@@ -19,7 +19,7 @@ import {
parseSourceCode,
createAxiosInstance
} from '../utils'
import { InvalidCsrfError } from '../types/errors/InvalidCsrfError'
import { InvalidSASjsCsrfError } from '../types/errors/InvalidSASjsCsrfError'
export interface HttpClient {
get<T>(
@@ -133,29 +133,13 @@ export class RequestClient implements HttpClient {
} else {
sasWork = response.log
}
} else if (response?.result?.log) {
//In this scenario we know we got the response from SASJS server
//Log is array of `{ line: '' }` so we need to convert it back to text
//To be able to parse it with current functions.
let log: string = ''
if (typeof log !== 'string') {
log = response.result.log
.map((logLine: any) => logLine.line)
.join('\n')
}
sourceCode = parseSourceCode(log)
generatedCode = parseGeneratedCode(log)
if (response?.result?._webout) {
sasWork = response.result._webout.WORK
} else {
sasWork = log
}
} else if (response?.result) {
sourceCode = parseSourceCode(response.result)
generatedCode = parseGeneratedCode(response.result)
// We parse only if it's a string, otherwise it would throw error
if (typeof response.result === 'string') {
sourceCode = parseSourceCode(response.result)
generatedCode = parseGeneratedCode(response.result)
}
sasWork = response.result.WORK
}
}
@@ -499,12 +483,20 @@ export class RequestClient implements HttpClient {
throw e
}
if (e instanceof InvalidCsrfError) {
// Fetching root will inject CSRF token in cookie
if (e instanceof InvalidSASjsCsrfError) {
// Fetching root and creating CSRF cookie
await this.httpClient
.get('/', {
withCredentials: true
})
.then((response) => {
const cookie =
/<script>document.cookie = '(XSRF-TOKEN=.*; Max-Age=86400; SameSite=Strict; Path=\/;)'<\/script>/.exec(
response.data
)?.[1]
if (cookie) document.cookie = cookie
})
.catch((err) => {
throw prefixMessage(err, 'Error while re-fetching CSRF token.')
})
@@ -611,8 +603,11 @@ export const throwIfError = (response: AxiosResponse) => {
throw new LoginRequiredError(response.data)
}
if (response.data.toLowerCase() === 'invalid csrf token!') {
throw new InvalidCsrfError()
if (
typeof response.data === 'string' &&
response.data.toLowerCase() === 'invalid csrf token!'
) {
throw new InvalidSASjsCsrfError()
}
break
case 401:
@@ -696,7 +691,9 @@ const parseError = (data: string) => {
const parts = data.split(/stored process not found: /i)
if (parts.length > 1) {
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
const message = `Stored process not found: ${storedProcessPath}`
const message = storedProcessPath.endsWith('runner')
? `SASJS runner not found. Here's the link (https://cli.sasjs.io/auth/#sasjs-runner) to the SAS code for registering the SASjs runner`
: `Stored process not found: ${storedProcessPath}`
return new JobExecutionError(500, message, '')
}
}

View File

@@ -4,6 +4,7 @@ import axiosCookieJarSupport from 'axios-cookiejar-support'
import * as tough from 'tough-cookie'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient, throwIfError } from './RequestClient'
import { JobExecutionError } from '../types/errors'
/**
* Specific request client for SAS9 in Node.js environments.
@@ -69,6 +70,8 @@ export class Sas9RequestClient extends RequestClient {
return this.parseResponse<T>(response)
})
.catch(async (e: any) => {
if (e instanceof JobExecutionError) throw e
return await this.handleError(
e,
() =>

View File

@@ -1,9 +1,12 @@
import { RequestClient } from './RequestClient'
import { AxiosResponse } from 'axios'
import { SASJS_LOGS_SEPARATOR, getValidJson } from '../utils'
/**
* Specific request client for SASJS.
* Append tokens in headers.
*/
export class SasjsRequestClient extends RequestClient {
getHeaders = (accessToken: string | undefined, contentType: string) => {
const headers: any = {}
@@ -20,4 +23,32 @@ export class SasjsRequestClient extends RequestClient {
return headers
}
protected parseResponse<T>(response: AxiosResponse<any>) {
const etag = response?.headers ? response.headers['etag'] : ''
let parsedResponse = {}
let log
try {
if (typeof response.data === 'string') {
parsedResponse = JSON.parse(response.data)
} else {
parsedResponse = response.data
}
} catch {
if (response.data.includes(SASJS_LOGS_SEPARATOR)) {
const splittedResponse = response.data.split(SASJS_LOGS_SEPARATOR)
log = splittedResponse[1]
if (splittedResponse[0].trim())
parsedResponse = getValidJson(splittedResponse[0])
} else parsedResponse = response.data
}
return {
result: parsedResponse as T,
log,
etag,
status: response.status
}
}
}

View File

@@ -5,4 +5,11 @@ export interface LoginOptions {
export interface LoginResult {
isLoggedIn: boolean
userName: string
userLongName: string
}
export interface LoginResultInternal {
isLoggedIn: boolean
userName: string
userLongName: string
loginForm?: any
}

View File

@@ -1,9 +0,0 @@
export class InvalidCsrfError extends Error {
constructor() {
const message = 'Invalid CSRF token!'
super(`Auth error: ${message}`)
this.name = 'InvalidCsrfError'
Object.setPrototypeOf(this, InvalidCsrfError.prototype)
}
}

View File

@@ -0,0 +1,9 @@
export class InvalidSASjsCsrfError extends Error {
constructor() {
const message = 'Invalid CSRF token!'
super(`Auth error: ${message}`)
this.name = 'InvalidSASjsCsrfError'
Object.setPrototypeOf(this, InvalidSASjsCsrfError.prototype)
}
}

2
src/utils/constants.ts Normal file
View File

@@ -0,0 +1,2 @@
export const SASJS_LOGS_SEPARATOR =
'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784'

View File

@@ -1,20 +1,22 @@
export * from './appendExtraResponseAttributes'
export * from './asyncForEach'
export * from './compareTimestamps'
export * from './convertToCsv'
export * from './constants'
export * from './createAxiosInstance'
export * from './delay'
export * from './fetchLogByChunks'
export * from './getValidJson'
export * from './isNode'
export * from './isRelativePath'
export * from './isUri'
export * from './isUrl'
export * from './needsRetry'
export * from './parseGeneratedCode'
export * from './parseSourceCode'
export * from './parseSasViyaLog'
export * from './parseSourceCode'
export * from './parseViyaDebugResponse'
export * from './parseWeboutResponse'
export * from './serialize'
export * from './splitChunks'
export * from './parseWeboutResponse'
export * from './fetchLogByChunks'
export * from './getValidJson'
export * from './parseViyaDebugResponse'
export * from './appendExtraResponseAttributes'
export * from './validateInput'

View File

@@ -0,0 +1,31 @@
/**
* Dictionary should contain only languages in SAS where `logout` text
* is represented with more then one word
*/
const dictionary = ['Log Off']
/**
* Extracts user full name assuming the first word after "title" means log off if not found otherwise in the dictionary
* @param response SAS response content
* @returns user full name
*/
export const extractUserLongNameSas9 = (response: string) => {
const regex = /"title":\s?".*?"/
const matched = response?.match(regex)
let fullName = matched?.[0].split(':')[1].trim()
let breakIndex = fullName?.indexOf(' ')
if (!fullName) return 'unknown'
dictionary.map((logoutWord) => {
const index = fullName?.indexOf(logoutWord) || -1
if (index > -1) {
breakIndex = index + logoutWord.length
}
})
//Cut only name
return fullName.slice(breakIndex, -1).trim()
}

View File

@@ -1,4 +1,4 @@
import { convertToCSV, isFormatsTable } from './convertToCsv'
import { convertToCSV, isFormatsTable } from '../convertToCsv'
describe('convertToCsv', () => {
const tableName = 'testTable'

View File

@@ -0,0 +1,97 @@
import { extractUserLongNameSas9 } from '../sas9/extractUserLongNameSas9'
describe('Extract username SAS9 English - two word logout handled language', () => {
const logoutWord = 'Log Off'
it('should return username with space after colon', () => {
const response = ` "title": "${logoutWord} SAS User One",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('SAS User One')
})
it('should return username without space after colon', () => {
const response = ` "title":"${logoutWord} SAS User One",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('SAS User One')
})
it('should return username with one word user name', () => {
const response = ` "title": "${logoutWord} SasUserOne",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('SasUserOne')
})
it('should return username unknown', () => {
const response = ` invalid",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('unknown')
})
})
describe('Extract username SAS9 two word logout unhandled language', () => {
const logoutWord = 'Log out'
it('should return username with space after colon', () => {
const response = ` "title": "${logoutWord} SAS User One",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('out SAS User One')
})
it('should return username without space after colon', () => {
const response = ` "title":"${logoutWord} SAS User One",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('out SAS User One')
})
it('should return username with one word user name', () => {
const response = ` "title": "${logoutWord} SasUserOne",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('out SasUserOne')
})
it('should return username unknown', () => {
const response = ` invalid",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('unknown')
})
})
describe('Extract username SAS9 Spanish - one word logout languages', () => {
const logoutWord = 'Desconexión'
it('should return username with space after colon', () => {
const response = ` "title": "${logoutWord} SAS User One",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('SAS User One')
})
it('should return username without space after colon', () => {
const response = ` "title":"${logoutWord} SAS User One",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('SAS User One')
})
it('should return username with one word user name', () => {
const response = ` "title": "${logoutWord} SasUserOne",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('SasUserOne')
})
it('should return username unknown', () => {
const response = ` invalid",`
const username = extractUserLongNameSas9(response)
expect(username).toEqual('unknown')
})
})

View File

@@ -0,0 +1,84 @@
import {
validateInput,
INVALID_TABLE_STRUCTURE,
MORE_INFO
} from '../validateInput'
const tableArray = [{ col1: 'first col value' }]
const stringData: any = { table1: tableArray }
describe('validateInput', () => {
it('should not return an error message if input data valid', () => {
const validationResult = validateInput(stringData)
expect(validationResult).toEqual({
status: true,
msg: ''
})
})
it('should not return an error message if input data is null', () => {
const validationResult = validateInput(null)
expect(validationResult).toEqual({
status: true,
msg: ''
})
})
it('should return an error message if input data is an array', () => {
const validationResult = validateInput(tableArray)
expect(validationResult).toEqual({
status: false,
msg: INVALID_TABLE_STRUCTURE
})
})
it('should return an error message if first letter of table is neither alphabet nor underscore', () => {
const validationResult = validateInput({ '1stTable': tableArray })
expect(validationResult).toEqual({
status: false,
msg: 'First letter of table should be alphabet or underscore.'
})
})
it('should return an error message if table name contains a character other than alphanumeric or underscore', () => {
const validationResult = validateInput({ 'table!': tableArray })
expect(validationResult).toEqual({
status: false,
msg: 'Table name should be alphanumeric.'
})
})
it('should return an error message if length of table name contains exceeds 32', () => {
const validationResult = validateInput({
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: tableArray
})
expect(validationResult).toEqual({
status: false,
msg: 'Maximum length for table name could be 32 characters.'
})
})
it('should return an error message if table does not have array of objects', () => {
const validationResult = validateInput({ table: stringData })
expect(validationResult).toEqual({
status: false,
msg: INVALID_TABLE_STRUCTURE
})
})
it('should return an error message if a table array has an item other than object', () => {
const validationResult = validateInput({ table1: ['invalid'] })
expect(validationResult).toEqual({
status: false,
msg: `Table table1 contains invalid structure. ${MORE_INFO}`
})
})
it('should return an error message if a row in a table contains an column with undefined value', () => {
const validationResult = validateInput({ table1: [{ column: undefined }] })
expect(validationResult).toEqual({
status: false,
msg: `A row in table table1 contains invalid value. Can't assign undefined to column.`
})
})
})

View File

@@ -0,0 +1,90 @@
export const MORE_INFO =
'For more info see https://sasjs.io/sasjs-adapter/#request-response'
export const INVALID_TABLE_STRUCTURE = `Parameter data contains invalid table structure. ${MORE_INFO}`
/**
* This function validates the input data structure and table naming convention
*
* @param data A json object that contains one or more tables, it can also be null
* @returns An object which contains two attributes: 1) status: boolean, 2) msg: string
*/
export const validateInput = (
data: { [key: string]: any } | null
): {
status: boolean
msg: string
} => {
if (data === null) return { status: true, msg: '' }
if (getType(data) !== 'object') {
return {
status: false,
msg: INVALID_TABLE_STRUCTURE
}
}
const isSasFormatsTable = (key: string) =>
key.match(/^\$.*/) && Object.keys(data).includes(key.replace(/^\$/, ''))
for (const key in data) {
if (!key.match(/^[a-zA-Z_]/) && !isSasFormatsTable(key)) {
return {
status: false,
msg: 'First letter of table should be alphabet or underscore.'
}
}
if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) && !isSasFormatsTable(key)) {
return { status: false, msg: 'Table name should be alphanumeric.' }
}
if (key.length > 32) {
return {
status: false,
msg: 'Maximum length for table name could be 32 characters.'
}
}
if (getType(data[key]) !== 'Array' && !isSasFormatsTable(key)) {
return {
status: false,
msg: INVALID_TABLE_STRUCTURE
}
}
for (const item of data[key]) {
if (getType(item) !== 'object') {
return {
status: false,
msg: `Table ${key} contains invalid structure. ${MORE_INFO}`
}
} else {
const attributes = Object.keys(item)
for (const attribute of attributes) {
if (item[attribute] === undefined) {
return {
status: false,
msg: `A row in table ${key} contains invalid value. Can't assign undefined to ${attribute}.`
}
}
}
}
}
}
return { status: true, msg: '' }
}
/**
* this function returns the type of variable
*
* @param data it could be anything, like string, array, object etc.
* @returns a string which tells the type of input parameter
*/
const getType = (data: any): string => {
if (Array.isArray(data)) {
return 'Array'
} else {
return typeof data
}
}