diff --git a/.all-contributorsrc b/.all-contributorsrc index 187ba80..cba9f96 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -106,6 +106,16 @@ "userTesting", "doc" ] + }, + { + "login": "saramartinelli1992", + "name": "Sara", + "avatar_url": "https://avatars.githubusercontent.com/u/100193908?v=4", + "profile": "https://github.com/saramartinelli1992", + "contributions": [ + "userTesting", + "platform" + ] } ], "contributorsPerLine": 7, diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..cea3c67 --- /dev/null +++ b/.github/issue_template.md @@ -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 diff --git a/.github/reviewer-lottery.yml b/.github/reviewer-lottery.yml index e96566d..0c3b845 100644 --- a/.github/reviewer-lottery.yml +++ b/.github/reviewer-lottery.yml @@ -5,7 +5,3 @@ groups: - YuryShkoda - medjedovicm - sabhas - - name: SASjs QA - reviewers: 1 - usernames: - - VladislavParhomchik diff --git a/.github/vpn/config.ovpn b/.github/vpn/config.ovpn index abd1a74..85139b0 100644 --- a/.github/vpn/config.ovpn +++ b/.github/vpn/config.ovpn @@ -1,30 +1,25 @@ -cipher AES-256-CBC -setenv FORWARD_COMPATIBLE 1 +# Client 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 +tls-client 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 +# this will connect with whatever proto DNS tells us (https://community.openvpn.net/openvpn/ticket/934) +proto tcp +remote vpn.4gl.io 7494 +resolv-retry infinite +cipher AES-256-CBC +auth SHA256 +script-security 2 +keepalive 10 120 +remote-cert-tls server +# Keys ca ca.crt cert user.crt key user.key tls-auth tls.key 1 + +# Security +nobind +persist-key +persist-tun +verb 3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5988625..8f293d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [lts/fermium] + node-version: [lts/hydrogen] steps: - uses: actions/checkout@v2 @@ -39,12 +39,6 @@ jobs: env: CI: true - - name: Install SSH Key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.DCGITLAB_KEY }} - known_hosts: 'placeholder' - - name: Write VPN Files run: | echo "$CA_CRT" > .github/vpn/ca.crt @@ -63,13 +57,16 @@ jobs: 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 wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-jammy.list sudo apt update - sudo apt install openvpn3=16~beta+focal + sudo apt install openvpn3=17~betaUb22042+jammy - name: Start Open VPN 3 run: openvpn3 session-start --config .github/vpn/config.ovpn + - name: install pm2 + run: npm i -g pm2 + - name: Deploy sasjs-tests run: | npm install -g replace-in-files-cli @@ -80,12 +77,12 @@ jobs: replace-in-files --regex='"userName".*' --replacement='"userName":"${{ secrets.SASJS_USERNAME }}",' ./public/config.json replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./public/config.json replace-in-files --regex='"serverType".*' --replacement='"serverType":"SASJS",' ./public/config.json - npm run update:adapter && npm run build - scp -o stricthostkeychecking=no -r ./build/* ${{ secrets.DCGITLAB_DEPLOY_PATH_VIYA }} + npm run update:adapter + pm2 start --name sasjs-test npm -- start - name: Run cypress on sasjs run: | - replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"${{ secrets.SASJS_TEST_URL_VIYA }}",' ./cypress.json + replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"http://localhost:3000",' ./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-tests/sasjs-cypress-run.sh ${{ secrets.MATRIX_TOKEN }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} diff --git a/.github/workflows/generateDocs.yml b/.github/workflows/generateDocs.yml new file mode 100644 index 0000000..9fe2db2 --- /dev/null +++ b/.github/workflows/generateDocs.yml @@ -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/hydrogen] + + 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 + diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 549657d..94629f9 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - node-version: [lts/fermium] + node-version: [lts/hydrogen] steps: - uses: actions/checkout@v2 @@ -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 }} diff --git a/.gitignore b/.gitignore index 41db3ac..68c8cae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ node_modules build +docs + .env /coverage -.DS_Store +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 8e404e2..b08ef9c 100644 --- a/README.md +++ b/README.md @@ -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. @@ -153,6 +153,14 @@ The response object will contain returned tables and columns. Table names are a 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. +### Session Manager + +To execute a script on Viya a session has to be created first which is time-consuming (~15sec). That is why a Session Manager has been created which is implementing the following logic: + +1. When the first session is requested, we also create one more session (hot session) for future requests. Please notice two pending POST requests to create a session within the same context: ![the first session request](./screenshots/session-manager-first-request.png) +2. When a subsequent request for a session is received and there is a hot session available (not expired), this session is returned and an asynchronous request to create another hot session is sent. Please notice that there is a pending POST request to create a new session while a job has been already finished execution (POST request with status 201): ![subsequent session request](./screenshots/subsequent-session-request.png) +3. When a subsequent request for a session is received and there is no available hot session, 2 requests are sent asynchronously to create a session. The first created session will be returned and another session will be reserved for future requests. + ### Variable Types The SAS type (char/numeric) of the values is determined according to a set of rules: @@ -332,7 +340,7 @@ If you find this library useful, help us grow our star graph! ## Contributors โœจ -[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-) Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -341,18 +349,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - - - - - - - - - - - - + + + + + + + + + + + + + + +

Krishna Acondy

๐Ÿ’ป ๐Ÿš‡ ๐Ÿ“ ๐Ÿ–‹ ๐Ÿค” ๐Ÿ“น

Yury Shkoda

๐Ÿ’ป ๐Ÿš‡ ๐Ÿค” โš ๏ธ ๐Ÿ“น

Mihajlo Medjedovic

๐Ÿ’ป ๐Ÿš‡ โš ๏ธ ๐Ÿ‘€

Allan Bowe

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿง‘โ€๐Ÿซ ๐Ÿšง

Muhammad Saad

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿง‘โ€๐Ÿซ ๐Ÿš‡

Sabir Hassan

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿค”

VladislavParhomchik

โš ๏ธ ๐Ÿ‘€

Rud Faden

๐Ÿ““ ๐Ÿ“–
Krishna Acondy
Krishna Acondy

๐Ÿ’ป ๐Ÿš‡ ๐Ÿ“ ๐Ÿ–‹ ๐Ÿค” ๐Ÿ“น
Yury Shkoda
Yury Shkoda

๐Ÿ’ป ๐Ÿš‡ ๐Ÿค” โš ๏ธ ๐Ÿ“น
Mihajlo Medjedovic
Mihajlo Medjedovic

๐Ÿ’ป ๐Ÿš‡ โš ๏ธ ๐Ÿ‘€
Allan Bowe
Allan Bowe

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿง‘โ€๐Ÿซ ๐Ÿšง
Muhammad Saad
Muhammad Saad

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿง‘โ€๐Ÿซ ๐Ÿš‡
Sabir Hassan
Sabir Hassan

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿค”
VladislavParhomchik
VladislavParhomchik

โš ๏ธ ๐Ÿ‘€
Rud Faden
Rud Faden

๐Ÿ““ ๐Ÿ“–
Sara
Sara

๐Ÿ““ ๐Ÿ“ฆ
diff --git a/cypress/integration/sasjs.tests.ts b/cypress/integration/sasjs.tests.ts index 74fa950..f7579c7 100644 --- a/cypress/integration/sasjs.tests.ts +++ b/cypress/integration/sasjs.tests.ts @@ -14,65 +14,83 @@ context('sasjs-tests', function () { it('Should have all tests successfull', (done) => { cy.get('body').then(($body) => { - if ($body.find('input[placeholder="User Name"]').length > 0) { - cy.get('input[placeholder="User Name"]').type(username) - cy.get('input[placeholder="Password"]').type(password) - cy.get('.submit-button').click() - } + cy.wait(1000).then(() => { + const startButton = $body.find( + '.ui.massive.icon.primary.left.labeled.button' + )[0] - cy.get('input[placeholder="User Name"]', { timeout: 40000 }) - .should('not.exist') - .then(() => { - cy.get('.ui.massive.icon.primary.left.labeled.button') - .click() - .then(() => { - cy.get('.ui.massive.loading.primary.button', { - timeout: testingFinishTimeout - }) - .should('not.exist') - .then(() => { - cy.get('span.icon.failed') - .should('not.exist') - .then(() => { - done() - }) + if ( + !startButton || + (startButton && !Cypress.dom.isVisible(startButton)) + ) { + cy.get('input[placeholder="User Name"]').type(username) + cy.get('input[placeholder="Password"]').type(password) + cy.get('.submit-button').click() + } + + cy.get('input[placeholder="User Name"]', { timeout: 40000 }) + .should('not.exist') + .then(() => { + cy.get('.ui.massive.icon.primary.left.labeled.button') + .click() + .then(() => { + cy.get('.ui.massive.loading.primary.button', { + timeout: testingFinishTimeout }) - }) - }) + .should('not.exist') + .then(() => { + cy.get('span.icon.failed') + .should('not.exist') + .then(() => { + done() + }) + }) + }) + }) + }) }) }) it('Should have all tests successfull with debug on', (done) => { cy.get('body').then(($body) => { - if ($body.find('input[placeholder="User Name"]').length > 0) { - cy.get('input[placeholder="User Name"]').type(username) - cy.get('input[placeholder="Password"]').type(password) - cy.get('.submit-button').click() - } + cy.wait(1000).then(() => { + const startButton = $body.find( + '.ui.massive.icon.primary.left.labeled.button' + )[0] - cy.get('.ui.fitted.toggle.checkbox label') - .click() - .then(() => { - cy.get('input[placeholder="User Name"]', { timeout: 40000 }) - .should('not.exist') - .then(() => { - cy.get('.ui.massive.icon.primary.left.labeled.button') - .click() - .then(() => { - cy.get('.ui.massive.loading.primary.button', { - timeout: testingFinishTimeout - }) - .should('not.exist') - .then(() => { - cy.get('span.icon.failed') - .should('not.exist') - .then(() => { - done() - }) + if ( + !startButton || + (startButton && !Cypress.dom.isVisible(startButton)) + ) { + cy.get('input[placeholder="User Name"]').type(username) + cy.get('input[placeholder="Password"]').type(password) + cy.get('.submit-button').click() + } + + cy.get('.ui.fitted.toggle.checkbox label') + .click() + .then(() => { + cy.get('input[placeholder="User Name"]', { timeout: 40000 }) + .should('not.exist') + .then(() => { + cy.get('.ui.massive.icon.primary.left.labeled.button') + .click() + .then(() => { + cy.get('.ui.massive.loading.primary.button', { + timeout: testingFinishTimeout }) - }) - }) - }) + .should('not.exist') + .then(() => { + cy.get('span.icon.failed') + .should('not.exist') + .then(() => { + done() + }) + }) + }) + }) + }) + }) }) }) }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..119ab03 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000..d68db96 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e2ac661..0000000 --- a/docs/.nojekyll +++ /dev/null @@ -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. \ No newline at end of file diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 8cf9617..0000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -adapter.sasjs.io \ No newline at end of file diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css deleted file mode 100644 index 7edb131..0000000 --- a/docs/assets/highlight.css +++ /dev/null @@ -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); } diff --git a/docs/assets/icons.css b/docs/assets/icons.css deleted file mode 100644 index 776a356..0000000 --- a/docs/assets/icons.css +++ /dev/null @@ -1,1043 +0,0 @@ -.tsd-kind-icon { - display: block; - position: relative; - padding-left: 20px; - text-indent: -20px; -} -.tsd-kind-icon:before { - content: ""; - display: inline-block; - vertical-align: middle; - width: 17px; - height: 17px; - margin: 0 3px 2px 0; - background-image: url(./icons.png); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-kind-icon:before { - background-image: url(./icons@2x.png); - background-size: 238px 204px; - } -} - -.tsd-signature.tsd-kind-icon:before { - background-position: 0 -153px; -} - -.tsd-kind-object-literal > .tsd-kind-icon:before { - background-position: 0px -17px; -} -.tsd-kind-object-literal.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -17px; -} -.tsd-kind-object-literal.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -17px; -} - -.tsd-kind-class > .tsd-kind-icon:before { - background-position: 0px -34px; -} -.tsd-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -34px; -} -.tsd-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -34px; -} - -.tsd-kind-class.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -51px; -} - -.tsd-kind-interface > .tsd-kind-icon:before { - background-position: 0px -68px; -} -.tsd-kind-interface.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -68px; -} -.tsd-kind-interface.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -68px; -} - -.tsd-kind-interface.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -34px -85px; -} - -.tsd-kind-namespace > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-namespace.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-namespace.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-module > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-module.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-module.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-enum > .tsd-kind-icon:before { - background-position: 0px -119px; -} -.tsd-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -119px; -} -.tsd-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -119px; -} - -.tsd-kind-enum-member > .tsd-kind-icon:before { - background-position: 0px -136px; -} -.tsd-kind-enum-member.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -136px; -} -.tsd-kind-enum-member.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -136px; -} - -.tsd-kind-signature > .tsd-kind-icon:before { - background-position: 0px -153px; -} -.tsd-kind-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -153px; -} -.tsd-kind-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -153px; -} - -.tsd-kind-type-alias > .tsd-kind-icon:before { - background-position: 0px -170px; -} -.tsd-kind-type-alias.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -170px; -} -.tsd-kind-type-alias.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -170px; -} - -.tsd-kind-type-alias.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -34px -187px; -} - -.tsd-kind-variable > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-variable.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-variable.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-property > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-property.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-property.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-get-signature > .tsd-kind-icon:before { - background-position: -136px -17px; -} -.tsd-kind-get-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -17px; -} -.tsd-kind-get-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -17px; -} - -.tsd-kind-set-signature > .tsd-kind-icon:before { - background-position: -136px -34px; -} -.tsd-kind-set-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -34px; -} -.tsd-kind-set-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -34px; -} - -.tsd-kind-accessor > .tsd-kind-icon:before { - background-position: -136px -51px; -} -.tsd-kind-accessor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -51px; -} -.tsd-kind-accessor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -51px; -} - -.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-call-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-function.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-method.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-constructor > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-constructor-signature > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-index-signature > .tsd-kind-icon:before { - background-position: -136px -119px; -} -.tsd-kind-index-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -119px; -} -.tsd-kind-index-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -119px; -} - -.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -136px; -} -.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -136px; -} -.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -136px; -} - -.tsd-is-static > .tsd-kind-icon:before { - background-position: -136px -153px; -} -.tsd-is-static.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -153px; -} -.tsd-is-static.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -153px; -} -.tsd-is-static.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -153px; -} - -.tsd-is-static.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -187px; -} diff --git a/docs/assets/icons.png b/docs/assets/icons.png deleted file mode 100644 index 3836d5f..0000000 Binary files a/docs/assets/icons.png and /dev/null differ diff --git a/docs/assets/icons@2x.png b/docs/assets/icons@2x.png deleted file mode 100644 index 5a209e2..0000000 Binary files a/docs/assets/icons@2x.png and /dev/null differ diff --git a/docs/assets/main.js b/docs/assets/main.js deleted file mode 100644 index 99f331b..0000000 --- a/docs/assets/main.js +++ /dev/null @@ -1,52 +0,0 @@ -(()=>{var Ce=Object.create;var J=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,_e=Object.prototype.hasOwnProperty;var Me=t=>J(t,"__esModule",{value:!0});var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Oe(e))!_e.call(t,i)&&(r||i!=="default")&&J(t,i,{get:()=>e[i],enumerable:!(n=Pe(e,i))||n.enumerable});return t},Ae=(t,e)=>De(Me(J(t!=null?Ce(Re(t)):{},"default",!e&&t&&t.__esModule?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var de=Fe((ce,he)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(r+=n[l+1]*i[h+1],l+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),p=s.str.charAt(1),v;p in s.node.edges?v=s.node.edges[p]:(v=new t.TokenSet,s.node.edges[p]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ce=="object"?he.exports=r():e.lunr=r()}(this,function(){return t})})()});var le=[];function N(t,e){le.push({selector:e,constructor:t})}var X=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n}),n.dataset.hasInstance=String(!0))})})}};var Q=class{constructor(e){this.el=e.el}};var Z=class{constructor(){this.listeners={}}addEventListener(e,r){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(r)}removeEventListener(e,r){if(!(e in this.listeners))return;let n=this.listeners[e];for(let i=0,s=n.length;i{let r=Date.now();return(...n)=>{r+e-Date.now()<0&&(t(...n),r=Date.now())}};var ee=class extends Z{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.secondaryNav=document.querySelector(".tsd-navigation.secondary"),window.addEventListener("scroll",K(()=>this.onScroll(),10)),window.addEventListener("resize",K(()=>this.onResize(),10)),this.onResize(),this.onScroll()}triggerResize(){let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onScroll(){this.scrollTop=window.scrollY||0;let e=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(e),this.hideShowToolbar()}hideShowToolbar(){var r;let e=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0,e!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),(r=this.secondaryNav)==null||r.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop}},I=ee;I.instance=new ee;var te=class extends Q{constructor(e){super(e);this.anchors=[];this.index=-1;I.instance.addEventListener("resize",()=>this.onResize()),I.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let e=window.location.href;e.indexOf("#")!=-1&&(e=e.substr(0,e.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let n=r.href;if(n.indexOf("#")==-1||n.substr(0,e.length)!=e)return;let i=n.substr(n.indexOf("#")+1),s=document.querySelector("a.tsd-anchor[name="+i+"]"),o=r.parentNode;!s||!o||this.anchors.push({link:o,anchor:s,position:0})}),this.onResize()}onResize(){let e;for(let n=0,i=this.anchors.length;nn.position-i.position);let r=new CustomEvent("scroll",{detail:{scrollTop:I.instance.scrollTop}});this.onScroll(r)}onScroll(e){let r=e.detail.scrollTop+5,n=this.anchors,i=n.length-1,s=this.index;for(;s>-1&&n[s].position>r;)s-=1;for(;s-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=s,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ue=(t,e=100)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(n),e)}};var fe=Ae(de());function pe(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Ve(t,n,r,s)}function Ve(t,e,r,n){r.addEventListener("input",ue(()=>{ze(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ne(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?me(e,-1):s.key==="ArrowDown"?me(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=fe.Index.load(window.searchData.index))}function ze(t,e,r,n){if(He(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=n.index.search(`*${i}*`);for(let o=0,a=Math.min(10,s.length);o${ve(u.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=u.classes;let p=document.createElement("a");p.href=n.base+u.url,p.classList.add("tsd-kind-icon"),p.innerHTML=l,h.append(p),e.appendChild(h)}}function me(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function Ne(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function ve(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(re(t.substring(s,o)),`${re(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(re(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function re(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var ge=class{constructor(e,r){this.signature=e,this.description=r}addClass(e){return this.signature.classList.add(e),this.description.classList.add(e),this}removeClass(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this}},ne=class extends Q{constructor(e){super(e);this.groups=[];this.index=-1;this.createGroups(),this.container&&(this.el.classList.add("active"),Array.from(this.el.children).forEach(r=>{r.addEventListener("touchstart",n=>this.onClick(n)),r.addEventListener("click",n=>this.onClick(n))}),this.container.classList.add("active"),this.setIndex(0))}setIndex(e){if(e<0&&(e=0),e>this.groups.length-1&&(e=this.groups.length-1),this.index==e)return;let r=this.groups[e];if(this.index>-1){let n=this.groups[this.index];n.removeClass("current").addClass("fade-out"),r.addClass("current"),r.addClass("fade-in"),I.instance.triggerResize(),setTimeout(()=>{n.removeClass("fade-out"),r.removeClass("fade-in")},300)}else r.addClass("current"),I.instance.triggerResize();this.index=e}createGroups(){let e=this.el.children;if(e.length<2)return;this.container=this.el.nextElementSibling;let r=this.container.children;this.groups=[];for(let n=0;n{r.signature===e.currentTarget&&this.setIndex(n)})}};var C="mousedown",ye="mousemove",_="mouseup",G={x:0,y:0},xe=!1,ie=!1,Be=!1,A=!1,Le=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Le?"is-mobile":"not-mobile");Le&&"ontouchstart"in document.documentElement&&(Be=!0,C="touchstart",ye="touchmove",_="touchend");document.addEventListener(C,t=>{ie=!0,A=!1;let e=C=="touchstart"?t.targetTouches[0]:t;G.y=e.pageY||0,G.x=e.pageX||0});document.addEventListener(ye,t=>{if(!!ie&&!A){let e=C=="touchstart"?t.targetTouches[0]:t,r=G.x-(e.pageX||0),n=G.y-(e.pageY||0);A=Math.sqrt(r*r+n*n)>10}});document.addEventListener(_,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var se=class extends Q{constructor(e){super(e);this.className=this.el.dataset.toggle||"",this.el.addEventListener(_,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(C,r=>this.onDocumentPointerDown(r)),document.addEventListener(_,r=>this.onDocumentPointerUp(r))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(e){A||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!A&&this.active&&e.target.closest(".col-menu")){let r=e.target.closest("a");if(r){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substr(0,n.indexOf("#"))),r.href.substr(0,n.length)==n&&setTimeout(()=>this.setActive(!1),250)}}}};var oe=class{constructor(e,r){this.key=e,this.value=r,this.defaultValue=r,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}initialize(){}setValue(e){if(this.value==e)return;let r=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(r,e)}},ae=class extends oe{initialize(){let e=document.querySelector("#tsd-filter-"+this.key);!e||(this.checkbox=e,this.checkbox.addEventListener("change",()=>{this.setValue(this.checkbox.checked)}))}handleValueChange(e,r){!this.checkbox||(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))}fromLocalStorage(e){return e=="true"}toLocalStorage(e){return e?"true":"false"}},Ee=class extends oe{initialize(){document.documentElement.classList.add("toggle-"+this.key+this.value);let e=document.querySelector("#tsd-filter-"+this.key);if(!e)return;this.select=e;let r=()=>{this.select.classList.add("active")},n=()=>{this.select.classList.remove("active")};this.select.addEventListener(C,r),this.select.addEventListener("mouseover",r),this.select.addEventListener("mouseleave",n),this.select.querySelectorAll("li").forEach(i=>{i.addEventListener(_,s=>{e.classList.remove("active"),this.setValue(s.target.dataset.value||"")})}),document.addEventListener(C,i=>{this.select.contains(i.target)||this.select.classList.remove("active")})}handleValueChange(e,r){this.select.querySelectorAll("li.selected").forEach(s=>{s.classList.remove("selected")});let n=this.select.querySelector('li[data-value="'+r+'"]'),i=this.select.querySelector(".tsd-select-label");n&&i&&(n.classList.add("selected"),i.textContent=n.textContent),document.documentElement.classList.remove("toggle-"+e),document.documentElement.classList.add("toggle-"+r)}fromLocalStorage(e){return e}toLocalStorage(e){return e}},Y=class extends Q{constructor(e){super(e);this.optionVisibility=new Ee("visibility","private"),this.optionInherited=new ae("inherited",!0),this.optionExternals=new ae("externals",!0)}static isSupported(){try{return typeof window.localStorage!="undefined"}catch{return!1}}};function be(t){let e=localStorage.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{localStorage.setItem("tsd-theme",t.value),we(t.value)})}function we(t){switch(t){case"os":document.body.classList.remove("light","dark");break;case"light":document.body.classList.remove("dark"),document.body.classList.add("light");break;case"dark":document.body.classList.remove("light"),document.body.classList.add("dark");break}}pe();N(te,".menu-highlight");N(ne,".tsd-signatures");N(se,"a[data-toggle]");Y.isSupported()?N(Y,"#tsd-filter"):document.documentElement.classList.add("no-filter");var Te=document.getElementById("theme");Te&&be(Te);var qe=new X;Object.defineProperty(window,"app",{value:qe});})(); -/*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - */ -/*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - */ -/** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - */ diff --git a/docs/assets/search.js b/docs/assets/search.js deleted file mode 100644 index b4f13c1..0000000 --- a/docs/assets/search.js +++ /dev/null @@ -1 +0,0 @@ -window.searchData = JSON.parse("{\"kinds\":{\"2\":\"Module\",\"8\":\"Enumeration\",\"16\":\"Enumeration member\",\"64\":\"Function\",\"128\":\"Class\",\"256\":\"Interface\",\"512\":\"Constructor\",\"1024\":\"Property\",\"2048\":\"Method\",\"65536\":\"Type literal\",\"262144\":\"Accessor\"},\"rows\":[{\"id\":0,\"kind\":2,\"name\":\"SASjs\",\"url\":\"modules/SASjs.html\",\"classes\":\"tsd-kind-module\"},{\"id\":1,\"kind\":128,\"name\":\"SASjs\",\"url\":\"classes/SASjs.SASjs-1.html\",\"classes\":\"tsd-kind-class tsd-parent-kind-module\",\"parent\":\"SASjs\"},{\"id\":2,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/SASjs.SASjs-1.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":3,\"kind\":2048,\"name\":\"getCsrfToken\",\"url\":\"classes/SASjs.SASjs-1.html#getCsrfToken\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":4,\"kind\":2048,\"name\":\"executeScriptSAS9\",\"url\":\"classes/SASjs.SASjs-1.html#executeScriptSAS9\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":5,\"kind\":2048,\"name\":\"executeScriptSASViya\",\"url\":\"classes/SASjs.SASjs-1.html#executeScriptSASViya\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":6,\"kind\":2048,\"name\":\"getComputeContexts\",\"url\":\"classes/SASjs.SASjs-1.html#getComputeContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":7,\"kind\":2048,\"name\":\"getLauncherContexts\",\"url\":\"classes/SASjs.SASjs-1.html#getLauncherContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":8,\"kind\":2048,\"name\":\"getDefaultComputeContexts\",\"url\":\"classes/SASjs.SASjs-1.html#getDefaultComputeContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":9,\"kind\":2048,\"name\":\"getExecutableContexts\",\"url\":\"classes/SASjs.SASjs-1.html#getExecutableContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":10,\"kind\":2048,\"name\":\"createComputeContext\",\"url\":\"classes/SASjs.SASjs-1.html#createComputeContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":11,\"kind\":2048,\"name\":\"createLauncherContext\",\"url\":\"classes/SASjs.SASjs-1.html#createLauncherContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":12,\"kind\":2048,\"name\":\"editComputeContext\",\"url\":\"classes/SASjs.SASjs-1.html#editComputeContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":13,\"kind\":2048,\"name\":\"deleteComputeContext\",\"url\":\"classes/SASjs.SASjs-1.html#deleteComputeContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":14,\"kind\":2048,\"name\":\"getComputeContextByName\",\"url\":\"classes/SASjs.SASjs-1.html#getComputeContextByName\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":15,\"kind\":2048,\"name\":\"getComputeContextById\",\"url\":\"classes/SASjs.SASjs-1.html#getComputeContextById\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":16,\"kind\":2048,\"name\":\"createSession\",\"url\":\"classes/SASjs.SASjs-1.html#createSession\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":17,\"kind\":2048,\"name\":\"createFolder\",\"url\":\"classes/SASjs.SASjs-1.html#createFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":18,\"kind\":2048,\"name\":\"createFile\",\"url\":\"classes/SASjs.SASjs-1.html#createFile\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":19,\"kind\":2048,\"name\":\"getFolder\",\"url\":\"classes/SASjs.SASjs-1.html#getFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":20,\"kind\":2048,\"name\":\"deleteFolder\",\"url\":\"classes/SASjs.SASjs-1.html#deleteFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":21,\"kind\":2048,\"name\":\"listFolder\",\"url\":\"classes/SASjs.SASjs-1.html#listFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":22,\"kind\":2048,\"name\":\"moveFolder\",\"url\":\"classes/SASjs.SASjs-1.html#moveFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":23,\"kind\":2048,\"name\":\"createJobDefinition\",\"url\":\"classes/SASjs.SASjs-1.html#createJobDefinition\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":24,\"kind\":2048,\"name\":\"getAuthCode\",\"url\":\"classes/SASjs.SASjs-1.html#getAuthCode\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":25,\"kind\":2048,\"name\":\"getAccessToken\",\"url\":\"classes/SASjs.SASjs-1.html#getAccessToken\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":26,\"kind\":2048,\"name\":\"refreshTokens\",\"url\":\"classes/SASjs.SASjs-1.html#refreshTokens\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":27,\"kind\":2048,\"name\":\"deleteClient\",\"url\":\"classes/SASjs.SASjs-1.html#deleteClient\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":28,\"kind\":2048,\"name\":\"getSasjsConfig\",\"url\":\"classes/SASjs.SASjs-1.html#getSasjsConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":29,\"kind\":2048,\"name\":\"getUserName\",\"url\":\"classes/SASjs.SASjs-1.html#getUserName\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":30,\"kind\":2048,\"name\":\"setSASjsConfig\",\"url\":\"classes/SASjs.SASjs-1.html#setSASjsConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":31,\"kind\":2048,\"name\":\"setDebugState\",\"url\":\"classes/SASjs.SASjs-1.html#setDebugState\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":32,\"kind\":2048,\"name\":\"checkSession\",\"url\":\"classes/SASjs.SASjs-1.html#checkSession\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":33,\"kind\":2048,\"name\":\"logIn\",\"url\":\"classes/SASjs.SASjs-1.html#logIn\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":34,\"kind\":2048,\"name\":\"logOut\",\"url\":\"classes/SASjs.SASjs-1.html#logOut\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":35,\"kind\":2048,\"name\":\"uploadFile\",\"url\":\"classes/SASjs.SASjs-1.html#uploadFile\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":36,\"kind\":2048,\"name\":\"request\",\"url\":\"classes/SASjs.SASjs-1.html#request\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":37,\"kind\":2048,\"name\":\"deployServicePack\",\"url\":\"classes/SASjs.SASjs-1.html#deployServicePack\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":38,\"kind\":2048,\"name\":\"deployToSASjs\",\"url\":\"classes/SASjs.SASjs-1.html#deployToSASjs\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":39,\"kind\":2048,\"name\":\"executeJobSASjs\",\"url\":\"classes/SASjs.SASjs-1.html#executeJobSASjs\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":40,\"kind\":2048,\"name\":\"startComputeJob\",\"url\":\"classes/SASjs.SASjs-1.html#startComputeJob\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":41,\"kind\":2048,\"name\":\"fetchLogFileContent\",\"url\":\"classes/SASjs.SASjs-1.html#fetchLogFileContent\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":42,\"kind\":2048,\"name\":\"getSasRequests\",\"url\":\"classes/SASjs.SASjs-1.html#getSasRequests\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":43,\"kind\":2048,\"name\":\"clearSasRequests\",\"url\":\"classes/SASjs.SASjs-1.html#clearSasRequests\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjs.SASjs\"},{\"id\":44,\"kind\":2,\"name\":\"SAS9ApiClient\",\"url\":\"modules/SAS9ApiClient.html\",\"classes\":\"tsd-kind-module\"},{\"id\":45,\"kind\":128,\"name\":\"SAS9ApiClient\",\"url\":\"classes/SAS9ApiClient.SAS9ApiClient-1.html\",\"classes\":\"tsd-kind-class tsd-parent-kind-module\",\"parent\":\"SAS9ApiClient\"},{\"id\":46,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/SAS9ApiClient.SAS9ApiClient-1.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"SAS9ApiClient.SAS9ApiClient\"},{\"id\":47,\"kind\":2048,\"name\":\"getConfig\",\"url\":\"classes/SAS9ApiClient.SAS9ApiClient-1.html#getConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SAS9ApiClient.SAS9ApiClient\"},{\"id\":48,\"kind\":2048,\"name\":\"setConfig\",\"url\":\"classes/SAS9ApiClient.SAS9ApiClient-1.html#setConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SAS9ApiClient.SAS9ApiClient\"},{\"id\":49,\"kind\":2048,\"name\":\"executeScript\",\"url\":\"classes/SAS9ApiClient.SAS9ApiClient-1.html#executeScript\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SAS9ApiClient.SAS9ApiClient\"},{\"id\":50,\"kind\":2,\"name\":\"SASjsApiClient\",\"url\":\"modules/SASjsApiClient.html\",\"classes\":\"tsd-kind-module\"},{\"id\":51,\"kind\":128,\"name\":\"SASjsApiClient\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html\",\"classes\":\"tsd-kind-class tsd-parent-kind-module\",\"parent\":\"SASjsApiClient\"},{\"id\":52,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":53,\"kind\":2048,\"name\":\"setConfig\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#setConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":54,\"kind\":2048,\"name\":\"deploy\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#deploy\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":55,\"kind\":2048,\"name\":\"executeJob\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#executeJob\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":56,\"kind\":2048,\"name\":\"getAccessToken\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#getAccessToken\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":57,\"kind\":2048,\"name\":\"refreshTokens\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#refreshTokens\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":58,\"kind\":2048,\"name\":\"getAuthCode\",\"url\":\"classes/SASjsApiClient.SASjsApiClient-1.html#getAuthCode\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASjsApiClient.SASjsApiClient\"},{\"id\":59,\"kind\":256,\"name\":\"SASjsAuthResponse\",\"url\":\"interfaces/SASjsApiClient.SASjsAuthResponse.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"SASjsApiClient\"},{\"id\":60,\"kind\":1024,\"name\":\"access_token\",\"url\":\"interfaces/SASjsApiClient.SASjsAuthResponse.html#access_token\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"SASjsApiClient.SASjsAuthResponse\"},{\"id\":61,\"kind\":1024,\"name\":\"refresh_token\",\"url\":\"interfaces/SASjsApiClient.SASjsAuthResponse.html#refresh_token\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"SASjsApiClient.SASjsAuthResponse\"},{\"id\":62,\"kind\":2,\"name\":\"SASViyaApiClient\",\"url\":\"modules/SASViyaApiClient.html\",\"classes\":\"tsd-kind-module\"},{\"id\":63,\"kind\":128,\"name\":\"SASViyaApiClient\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html\",\"classes\":\"tsd-kind-class tsd-parent-kind-module\",\"parent\":\"SASViyaApiClient\"},{\"id\":64,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":65,\"kind\":2048,\"name\":\"appendRequest\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#appendRequest\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":66,\"kind\":262144,\"name\":\"debug\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#debug\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":67,\"kind\":2048,\"name\":\"getJobsInFolder\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getJobsInFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":68,\"kind\":2048,\"name\":\"getConfig\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":69,\"kind\":2048,\"name\":\"setConfig\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#setConfig\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":70,\"kind\":2048,\"name\":\"getComputeContexts\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getComputeContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":71,\"kind\":2048,\"name\":\"getDefaultComputeContexts\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getDefaultComputeContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":72,\"kind\":2048,\"name\":\"getLauncherContexts\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getLauncherContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":73,\"kind\":2048,\"name\":\"getExecutableContexts\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getExecutableContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":74,\"kind\":2048,\"name\":\"createSession\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#createSession\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":75,\"kind\":2048,\"name\":\"createComputeContext\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#createComputeContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":76,\"kind\":2048,\"name\":\"createLauncherContext\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#createLauncherContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":77,\"kind\":2048,\"name\":\"editComputeContext\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#editComputeContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":78,\"kind\":2048,\"name\":\"deleteComputeContext\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#deleteComputeContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":79,\"kind\":2048,\"name\":\"executeScript\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#executeScript\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":80,\"kind\":2048,\"name\":\"getFolder\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":81,\"kind\":2048,\"name\":\"createFile\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#createFile\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":82,\"kind\":2048,\"name\":\"createFolder\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#createFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":83,\"kind\":2048,\"name\":\"createJobDefinition\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#createJobDefinition\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":84,\"kind\":2048,\"name\":\"getAuthCode\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getAuthCode\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":85,\"kind\":2048,\"name\":\"getAccessToken\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getAccessToken\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":86,\"kind\":2048,\"name\":\"refreshTokens\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#refreshTokens\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":87,\"kind\":2048,\"name\":\"deleteClient\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#deleteClient\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":88,\"kind\":2048,\"name\":\"executeComputeJob\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#executeComputeJob\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":89,\"kind\":2048,\"name\":\"executeJob\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#executeJob\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":90,\"kind\":2048,\"name\":\"getComputeContextByName\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getComputeContextByName\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":91,\"kind\":2048,\"name\":\"getComputeContextById\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#getComputeContextById\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":92,\"kind\":2048,\"name\":\"listFolder\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#listFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":93,\"kind\":2048,\"name\":\"moveFolder\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#moveFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":94,\"kind\":2048,\"name\":\"deleteFolder\",\"url\":\"classes/SASViyaApiClient.SASViyaApiClient-1.html#deleteFolder\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SASViyaApiClient.SASViyaApiClient\"},{\"id\":95,\"kind\":2,\"name\":\"types\",\"url\":\"modules/types.html\",\"classes\":\"tsd-kind-module\"},{\"id\":96,\"kind\":256,\"name\":\"Context\",\"url\":\"interfaces/types.Context.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":97,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.Context.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Context\"},{\"id\":98,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.Context.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Context\"},{\"id\":99,\"kind\":1024,\"name\":\"createdBy\",\"url\":\"interfaces/types.Context.html#createdBy\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Context\"},{\"id\":100,\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/types.Context.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Context\"},{\"id\":101,\"kind\":1024,\"name\":\"attributes\",\"url\":\"interfaces/types.Context.html#attributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Context\"},{\"id\":102,\"kind\":256,\"name\":\"EditContextInput\",\"url\":\"interfaces/types.EditContextInput.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":103,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.EditContextInput.html#name-1\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":104,\"kind\":1024,\"name\":\"description\",\"url\":\"interfaces/types.EditContextInput.html#description\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":105,\"kind\":1024,\"name\":\"launchContext\",\"url\":\"interfaces/types.EditContextInput.html#launchContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":106,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/types.EditContextInput.html#__type-1\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":107,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.EditContextInput.html#__type-1.name\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.EditContextInput.__type\"},{\"id\":108,\"kind\":1024,\"name\":\"environment\",\"url\":\"interfaces/types.EditContextInput.html#environment\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":109,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/types.EditContextInput.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":110,\"kind\":1024,\"name\":\"options\",\"url\":\"interfaces/types.EditContextInput.html#__type.options\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.EditContextInput.__type\"},{\"id\":111,\"kind\":1024,\"name\":\"autoExecLines\",\"url\":\"interfaces/types.EditContextInput.html#__type.autoExecLines\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.EditContextInput.__type\"},{\"id\":112,\"kind\":1024,\"name\":\"attributes\",\"url\":\"interfaces/types.EditContextInput.html#attributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":113,\"kind\":1024,\"name\":\"authorizedUsers\",\"url\":\"interfaces/types.EditContextInput.html#authorizedUsers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":114,\"kind\":1024,\"name\":\"authorizeAllAuthenticatedUsers\",\"url\":\"interfaces/types.EditContextInput.html#authorizeAllAuthenticatedUsers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":115,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.EditContextInput.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.EditContextInput\"},{\"id\":116,\"kind\":256,\"name\":\"ContextAllAttributes\",\"url\":\"interfaces/types.ContextAllAttributes.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":117,\"kind\":1024,\"name\":\"attributes\",\"url\":\"interfaces/types.ContextAllAttributes.html#attributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":118,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":119,\"kind\":1024,\"name\":\"reuseServerProcesses\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type.reuseServerProcesses\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.ContextAllAttributes.__type\"},{\"id\":120,\"kind\":1024,\"name\":\"runServerAs\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type.runServerAs\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.ContextAllAttributes.__type\"},{\"id\":121,\"kind\":1024,\"name\":\"modifiedTimeStamp\",\"url\":\"interfaces/types.ContextAllAttributes.html#modifiedTimeStamp\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":122,\"kind\":1024,\"name\":\"createdBy\",\"url\":\"interfaces/types.ContextAllAttributes.html#createdBy\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":123,\"kind\":1024,\"name\":\"creationTimeStamp\",\"url\":\"interfaces/types.ContextAllAttributes.html#creationTimeStamp\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":124,\"kind\":1024,\"name\":\"launchType\",\"url\":\"interfaces/types.ContextAllAttributes.html#launchType\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":125,\"kind\":1024,\"name\":\"environment\",\"url\":\"interfaces/types.ContextAllAttributes.html#environment\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":126,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type-1\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":127,\"kind\":1024,\"name\":\"autoExecLines\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type-1.autoExecLines\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.ContextAllAttributes.__type\"},{\"id\":128,\"kind\":1024,\"name\":\"launchContext\",\"url\":\"interfaces/types.ContextAllAttributes.html#launchContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":129,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type-2\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":130,\"kind\":1024,\"name\":\"contextName\",\"url\":\"interfaces/types.ContextAllAttributes.html#__type-2.contextName\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.ContextAllAttributes.__type\"},{\"id\":131,\"kind\":1024,\"name\":\"modifiedBy\",\"url\":\"interfaces/types.ContextAllAttributes.html#modifiedBy\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":132,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.ContextAllAttributes.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":133,\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/types.ContextAllAttributes.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":134,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.ContextAllAttributes.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ContextAllAttributes\"},{\"id\":135,\"kind\":256,\"name\":\"CsrfToken\",\"url\":\"interfaces/types.CsrfToken.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":136,\"kind\":1024,\"name\":\"headerName\",\"url\":\"interfaces/types.CsrfToken.html#headerName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.CsrfToken\"},{\"id\":137,\"kind\":1024,\"name\":\"value\",\"url\":\"interfaces/types.CsrfToken.html#value\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.CsrfToken\"},{\"id\":138,\"kind\":256,\"name\":\"Folder\",\"url\":\"interfaces/types.Folder.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":139,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.Folder.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Folder\"},{\"id\":140,\"kind\":1024,\"name\":\"uri\",\"url\":\"interfaces/types.Folder.html#uri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Folder\"},{\"id\":141,\"kind\":1024,\"name\":\"links\",\"url\":\"interfaces/types.Folder.html#links\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Folder\"},{\"id\":142,\"kind\":1024,\"name\":\"memberCount\",\"url\":\"interfaces/types.Folder.html#memberCount\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Folder\"},{\"id\":143,\"kind\":256,\"name\":\"File\",\"url\":\"interfaces/types.File.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":144,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.File.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.File\"},{\"id\":145,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.File.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.File\"},{\"id\":146,\"kind\":1024,\"name\":\"parentUri\",\"url\":\"interfaces/types.File.html#parentUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.File\"},{\"id\":147,\"kind\":1024,\"name\":\"links\",\"url\":\"interfaces/types.File.html#links\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.File\"},{\"id\":148,\"kind\":256,\"name\":\"Job\",\"url\":\"interfaces/types.Job.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":149,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.Job.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":150,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.Job.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":151,\"kind\":1024,\"name\":\"uri\",\"url\":\"interfaces/types.Job.html#uri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":152,\"kind\":1024,\"name\":\"createdBy\",\"url\":\"interfaces/types.Job.html#createdBy\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":153,\"kind\":1024,\"name\":\"code\",\"url\":\"interfaces/types.Job.html#code\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":154,\"kind\":1024,\"name\":\"links\",\"url\":\"interfaces/types.Job.html#links\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":155,\"kind\":1024,\"name\":\"results\",\"url\":\"interfaces/types.Job.html#results\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":156,\"kind\":1024,\"name\":\"error\",\"url\":\"interfaces/types.Job.html#error\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":157,\"kind\":1024,\"name\":\"logStatistics\",\"url\":\"interfaces/types.Job.html#logStatistics\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Job\"},{\"id\":158,\"kind\":256,\"name\":\"JobDefinition\",\"url\":\"interfaces/types.JobDefinition.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":159,\"kind\":1024,\"name\":\"code\",\"url\":\"interfaces/types.JobDefinition.html#code\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.JobDefinition\"},{\"id\":160,\"kind\":256,\"name\":\"JobResult\",\"url\":\"interfaces/types.JobResult.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":161,\"kind\":1024,\"name\":\"_webout.json\",\"url\":\"interfaces/types.JobResult.html#_webout_json\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.JobResult\"},{\"id\":162,\"kind\":256,\"name\":\"Link\",\"url\":\"interfaces/types.Link.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":163,\"kind\":1024,\"name\":\"method\",\"url\":\"interfaces/types.Link.html#method\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Link\"},{\"id\":164,\"kind\":1024,\"name\":\"rel\",\"url\":\"interfaces/types.Link.html#rel\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Link\"},{\"id\":165,\"kind\":1024,\"name\":\"href\",\"url\":\"interfaces/types.Link.html#href\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Link\"},{\"id\":166,\"kind\":1024,\"name\":\"uri\",\"url\":\"interfaces/types.Link.html#uri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Link\"},{\"id\":167,\"kind\":1024,\"name\":\"type\",\"url\":\"interfaces/types.Link.html#type\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Link\"},{\"id\":168,\"kind\":128,\"name\":\"SASjsConfig\",\"url\":\"classes/types.SASjsConfig.html\",\"classes\":\"tsd-kind-class tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":169,\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/types.SASjsConfig.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":170,\"kind\":1024,\"name\":\"serverUrl\",\"url\":\"classes/types.SASjsConfig.html#serverUrl\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":171,\"kind\":1024,\"name\":\"pathSASJS\",\"url\":\"classes/types.SASjsConfig.html#pathSASJS\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":172,\"kind\":1024,\"name\":\"pathSAS9\",\"url\":\"classes/types.SASjsConfig.html#pathSAS9\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":173,\"kind\":1024,\"name\":\"pathSASViya\",\"url\":\"classes/types.SASjsConfig.html#pathSASViya\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":174,\"kind\":1024,\"name\":\"appLoc\",\"url\":\"classes/types.SASjsConfig.html#appLoc\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":175,\"kind\":1024,\"name\":\"serverType\",\"url\":\"classes/types.SASjsConfig.html#serverType\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":176,\"kind\":1024,\"name\":\"debug\",\"url\":\"classes/types.SASjsConfig.html#debug\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":177,\"kind\":1024,\"name\":\"contextName\",\"url\":\"classes/types.SASjsConfig.html#contextName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":178,\"kind\":1024,\"name\":\"useComputeApi\",\"url\":\"classes/types.SASjsConfig.html#useComputeApi\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":179,\"kind\":1024,\"name\":\"httpsAgentOptions\",\"url\":\"classes/types.SASjsConfig.html#httpsAgentOptions\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":180,\"kind\":1024,\"name\":\"loginMechanism\",\"url\":\"classes/types.SASjsConfig.html#loginMechanism\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":181,\"kind\":1024,\"name\":\"requestHistoryLimit\",\"url\":\"classes/types.SASjsConfig.html#requestHistoryLimit\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"types.SASjsConfig\"},{\"id\":182,\"kind\":8,\"name\":\"LoginMechanism\",\"url\":\"enums/types.LoginMechanism.html\",\"classes\":\"tsd-kind-enum tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":183,\"kind\":16,\"name\":\"Default\",\"url\":\"enums/types.LoginMechanism.html#Default\",\"classes\":\"tsd-kind-enum-member tsd-parent-kind-enum\",\"parent\":\"types.LoginMechanism\"},{\"id\":184,\"kind\":16,\"name\":\"Redirected\",\"url\":\"enums/types.LoginMechanism.html#Redirected\",\"classes\":\"tsd-kind-enum-member tsd-parent-kind-enum\",\"parent\":\"types.LoginMechanism\"},{\"id\":185,\"kind\":256,\"name\":\"SASjsRequest\",\"url\":\"interfaces/types.SASjsRequest.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":186,\"kind\":1024,\"name\":\"serviceLink\",\"url\":\"interfaces/types.SASjsRequest.html#serviceLink\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SASjsRequest\"},{\"id\":187,\"kind\":1024,\"name\":\"timestamp\",\"url\":\"interfaces/types.SASjsRequest.html#timestamp\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SASjsRequest\"},{\"id\":188,\"kind\":1024,\"name\":\"sourceCode\",\"url\":\"interfaces/types.SASjsRequest.html#sourceCode\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SASjsRequest\"},{\"id\":189,\"kind\":1024,\"name\":\"generatedCode\",\"url\":\"interfaces/types.SASjsRequest.html#generatedCode\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SASjsRequest\"},{\"id\":190,\"kind\":1024,\"name\":\"logFile\",\"url\":\"interfaces/types.SASjsRequest.html#logFile\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SASjsRequest\"},{\"id\":191,\"kind\":1024,\"name\":\"SASWORK\",\"url\":\"interfaces/types.SASjsRequest.html#SASWORK\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SASjsRequest\"},{\"id\":192,\"kind\":256,\"name\":\"Session\",\"url\":\"interfaces/types.Session.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":193,\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/types.Session.html#id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Session\"},{\"id\":194,\"kind\":1024,\"name\":\"state\",\"url\":\"interfaces/types.Session.html#state\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Session\"},{\"id\":195,\"kind\":1024,\"name\":\"links\",\"url\":\"interfaces/types.Session.html#links\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Session\"},{\"id\":196,\"kind\":1024,\"name\":\"attributes\",\"url\":\"interfaces/types.Session.html#attributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Session\"},{\"id\":197,\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/types.Session.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"types.Session\"},{\"id\":198,\"kind\":1024,\"name\":\"sessionInactiveTimeout\",\"url\":\"interfaces/types.Session.html#__type.sessionInactiveTimeout\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"types.Session.__type\"},{\"id\":199,\"kind\":1024,\"name\":\"creationTimeStamp\",\"url\":\"interfaces/types.Session.html#creationTimeStamp\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.Session\"},{\"id\":200,\"kind\":256,\"name\":\"SessionVariable\",\"url\":\"interfaces/types.SessionVariable.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":201,\"kind\":1024,\"name\":\"value\",\"url\":\"interfaces/types.SessionVariable.html#value\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.SessionVariable\"},{\"id\":202,\"kind\":256,\"name\":\"UploadFile\",\"url\":\"interfaces/types.UploadFile.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":203,\"kind\":1024,\"name\":\"file\",\"url\":\"interfaces/types.UploadFile.html#file\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.UploadFile\"},{\"id\":204,\"kind\":1024,\"name\":\"fileName\",\"url\":\"interfaces/types.UploadFile.html#fileName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.UploadFile\"},{\"id\":205,\"kind\":256,\"name\":\"PollOptions\",\"url\":\"interfaces/types.PollOptions.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":206,\"kind\":1024,\"name\":\"maxPollCount\",\"url\":\"interfaces/types.PollOptions.html#maxPollCount\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.PollOptions\"},{\"id\":207,\"kind\":1024,\"name\":\"pollInterval\",\"url\":\"interfaces/types.PollOptions.html#pollInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.PollOptions\"},{\"id\":208,\"kind\":1024,\"name\":\"streamLog\",\"url\":\"interfaces/types.PollOptions.html#streamLog\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.PollOptions\"},{\"id\":209,\"kind\":1024,\"name\":\"logFolderPath\",\"url\":\"interfaces/types.PollOptions.html#logFolderPath\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.PollOptions\"},{\"id\":210,\"kind\":256,\"name\":\"WriteStream\",\"url\":\"interfaces/types.WriteStream.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":211,\"kind\":2048,\"name\":\"write\",\"url\":\"interfaces/types.WriteStream.html#write\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"types.WriteStream\"},{\"id\":212,\"kind\":1024,\"name\":\"path\",\"url\":\"interfaces/types.WriteStream.html#path\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.WriteStream\"},{\"id\":213,\"kind\":256,\"name\":\"FileTree\",\"url\":\"interfaces/types.FileTree.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":214,\"kind\":1024,\"name\":\"members\",\"url\":\"interfaces/types.FileTree.html#members\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.FileTree\"},{\"id\":215,\"kind\":8,\"name\":\"MemberType\",\"url\":\"enums/types.MemberType.html\",\"classes\":\"tsd-kind-enum tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":216,\"kind\":16,\"name\":\"folder\",\"url\":\"enums/types.MemberType.html#folder\",\"classes\":\"tsd-kind-enum-member tsd-parent-kind-enum\",\"parent\":\"types.MemberType\"},{\"id\":217,\"kind\":16,\"name\":\"service\",\"url\":\"enums/types.MemberType.html#service\",\"classes\":\"tsd-kind-enum-member tsd-parent-kind-enum\",\"parent\":\"types.MemberType\"},{\"id\":218,\"kind\":256,\"name\":\"FolderMember\",\"url\":\"interfaces/types.FolderMember.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":219,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.FolderMember.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.FolderMember\"},{\"id\":220,\"kind\":1024,\"name\":\"type\",\"url\":\"interfaces/types.FolderMember.html#type\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.FolderMember\"},{\"id\":221,\"kind\":1024,\"name\":\"members\",\"url\":\"interfaces/types.FolderMember.html#members\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.FolderMember\"},{\"id\":222,\"kind\":256,\"name\":\"ServiceMember\",\"url\":\"interfaces/types.ServiceMember.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":223,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/types.ServiceMember.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ServiceMember\"},{\"id\":224,\"kind\":1024,\"name\":\"type\",\"url\":\"interfaces/types.ServiceMember.html#type\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ServiceMember\"},{\"id\":225,\"kind\":1024,\"name\":\"code\",\"url\":\"interfaces/types.ServiceMember.html#code\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ServiceMember\"},{\"id\":226,\"kind\":64,\"name\":\"isFileTree\",\"url\":\"modules/types.html#isFileTree\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":227,\"kind\":256,\"name\":\"ExecutionQuery\",\"url\":\"interfaces/types.ExecutionQuery.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"types\"},{\"id\":228,\"kind\":1024,\"name\":\"_program\",\"url\":\"interfaces/types.ExecutionQuery.html#_program\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ExecutionQuery\"},{\"id\":229,\"kind\":1024,\"name\":\"_debug\",\"url\":\"interfaces/types.ExecutionQuery.html#_debug\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"types.ExecutionQuery\"}],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"parent\"],\"fieldVectors\":[[\"name/0\",[0,41.897]],[\"parent/0\",[]],[\"name/1\",[0,41.897]],[\"parent/1\",[0,4.152]],[\"name/2\",[1,37.377]],[\"parent/2\",[2,1.678]],[\"name/3\",[3,50.37]],[\"parent/3\",[2,1.678]],[\"name/4\",[4,50.37]],[\"parent/4\",[2,1.678]],[\"name/5\",[5,50.37]],[\"parent/5\",[2,1.678]],[\"name/6\",[6,45.261]],[\"parent/6\",[2,1.678]],[\"name/7\",[7,45.261]],[\"parent/7\",[2,1.678]],[\"name/8\",[8,45.261]],[\"parent/8\",[2,1.678]],[\"name/9\",[9,45.261]],[\"parent/9\",[2,1.678]],[\"name/10\",[10,45.261]],[\"parent/10\",[2,1.678]],[\"name/11\",[11,45.261]],[\"parent/11\",[2,1.678]],[\"name/12\",[12,45.261]],[\"parent/12\",[2,1.678]],[\"name/13\",[13,45.261]],[\"parent/13\",[2,1.678]],[\"name/14\",[14,45.261]],[\"parent/14\",[2,1.678]],[\"name/15\",[15,45.261]],[\"parent/15\",[2,1.678]],[\"name/16\",[16,45.261]],[\"parent/16\",[2,1.678]],[\"name/17\",[17,45.261]],[\"parent/17\",[2,1.678]],[\"name/18\",[18,45.261]],[\"parent/18\",[2,1.678]],[\"name/19\",[19,45.261]],[\"parent/19\",[2,1.678]],[\"name/20\",[20,45.261]],[\"parent/20\",[2,1.678]],[\"name/21\",[21,45.261]],[\"parent/21\",[2,1.678]],[\"name/22\",[22,45.261]],[\"parent/22\",[2,1.678]],[\"name/23\",[23,45.261]],[\"parent/23\",[2,1.678]],[\"name/24\",[24,41.897]],[\"parent/24\",[2,1.678]],[\"name/25\",[25,41.897]],[\"parent/25\",[2,1.678]],[\"name/26\",[26,41.897]],[\"parent/26\",[2,1.678]],[\"name/27\",[27,45.261]],[\"parent/27\",[2,1.678]],[\"name/28\",[28,50.37]],[\"parent/28\",[2,1.678]],[\"name/29\",[29,50.37]],[\"parent/29\",[2,1.678]],[\"name/30\",[30,50.37]],[\"parent/30\",[2,1.678]],[\"name/31\",[31,50.37]],[\"parent/31\",[2,1.678]],[\"name/32\",[32,50.37]],[\"parent/32\",[2,1.678]],[\"name/33\",[33,50.37]],[\"parent/33\",[2,1.678]],[\"name/34\",[34,50.37]],[\"parent/34\",[2,1.678]],[\"name/35\",[35,45.261]],[\"parent/35\",[2,1.678]],[\"name/36\",[36,50.37]],[\"parent/36\",[2,1.678]],[\"name/37\",[37,50.37]],[\"parent/37\",[2,1.678]],[\"name/38\",[38,50.37]],[\"parent/38\",[2,1.678]],[\"name/39\",[39,50.37]],[\"parent/39\",[2,1.678]],[\"name/40\",[40,50.37]],[\"parent/40\",[2,1.678]],[\"name/41\",[41,50.37]],[\"parent/41\",[2,1.678]],[\"name/42\",[42,50.37]],[\"parent/42\",[2,1.678]],[\"name/43\",[43,50.37]],[\"parent/43\",[2,1.678]],[\"name/44\",[44,41.897]],[\"parent/44\",[]],[\"name/45\",[44,41.897]],[\"parent/45\",[44,4.152]],[\"name/46\",[1,37.377]],[\"parent/46\",[45,3.903]],[\"name/47\",[46,45.261]],[\"parent/47\",[45,3.903]],[\"name/48\",[47,41.897]],[\"parent/48\",[45,3.903]],[\"name/49\",[48,45.261]],[\"parent/49\",[45,3.903]],[\"name/50\",[49,39.383]],[\"parent/50\",[]],[\"name/51\",[49,39.383]],[\"parent/51\",[49,3.903]],[\"name/52\",[1,37.377]],[\"parent/52\",[50,3.397]],[\"name/53\",[47,41.897]],[\"parent/53\",[50,3.397]],[\"name/54\",[51,50.37]],[\"parent/54\",[50,3.397]],[\"name/55\",[52,45.261]],[\"parent/55\",[50,3.397]],[\"name/56\",[25,41.897]],[\"parent/56\",[50,3.397]],[\"name/57\",[26,41.897]],[\"parent/57\",[50,3.397]],[\"name/58\",[24,41.897]],[\"parent/58\",[50,3.397]],[\"name/59\",[53,50.37]],[\"parent/59\",[49,3.903]],[\"name/60\",[54,50.37]],[\"parent/60\",[55,4.485]],[\"name/61\",[56,50.37]],[\"parent/61\",[55,4.485]],[\"name/62\",[57,41.897]],[\"parent/62\",[]],[\"name/63\",[57,41.897]],[\"parent/63\",[57,4.152]],[\"name/64\",[1,37.377]],[\"parent/64\",[58,1.974]],[\"name/65\",[59,50.37]],[\"parent/65\",[58,1.974]],[\"name/66\",[60,45.261]],[\"parent/66\",[58,1.974]],[\"name/67\",[61,50.37]],[\"parent/67\",[58,1.974]],[\"name/68\",[46,45.261]],[\"parent/68\",[58,1.974]],[\"name/69\",[47,41.897]],[\"parent/69\",[58,1.974]],[\"name/70\",[6,45.261]],[\"parent/70\",[58,1.974]],[\"name/71\",[8,45.261]],[\"parent/71\",[58,1.974]],[\"name/72\",[7,45.261]],[\"parent/72\",[58,1.974]],[\"name/73\",[9,45.261]],[\"parent/73\",[58,1.974]],[\"name/74\",[16,45.261]],[\"parent/74\",[58,1.974]],[\"name/75\",[10,45.261]],[\"parent/75\",[58,1.974]],[\"name/76\",[11,45.261]],[\"parent/76\",[58,1.974]],[\"name/77\",[12,45.261]],[\"parent/77\",[58,1.974]],[\"name/78\",[13,45.261]],[\"parent/78\",[58,1.974]],[\"name/79\",[48,45.261]],[\"parent/79\",[58,1.974]],[\"name/80\",[19,45.261]],[\"parent/80\",[58,1.974]],[\"name/81\",[18,45.261]],[\"parent/81\",[58,1.974]],[\"name/82\",[17,45.261]],[\"parent/82\",[58,1.974]],[\"name/83\",[23,45.261]],[\"parent/83\",[58,1.974]],[\"name/84\",[24,41.897]],[\"parent/84\",[58,1.974]],[\"name/85\",[25,41.897]],[\"parent/85\",[58,1.974]],[\"name/86\",[26,41.897]],[\"parent/86\",[58,1.974]],[\"name/87\",[27,45.261]],[\"parent/87\",[58,1.974]],[\"name/88\",[62,50.37]],[\"parent/88\",[58,1.974]],[\"name/89\",[52,45.261]],[\"parent/89\",[58,1.974]],[\"name/90\",[14,45.261]],[\"parent/90\",[58,1.974]],[\"name/91\",[15,45.261]],[\"parent/91\",[58,1.974]],[\"name/92\",[21,45.261]],[\"parent/92\",[58,1.974]],[\"name/93\",[22,45.261]],[\"parent/93\",[58,1.974]],[\"name/94\",[20,45.261]],[\"parent/94\",[58,1.974]],[\"name/95\",[63,22.037]],[\"parent/95\",[]],[\"name/96\",[64,50.37]],[\"parent/96\",[63,2.184]],[\"name/97\",[65,33.024]],[\"parent/97\",[66,3.704]],[\"name/98\",[67,34.275]],[\"parent/98\",[66,3.704]],[\"name/99\",[68,41.897]],[\"parent/99\",[66,3.704]],[\"name/100\",[69,45.261]],[\"parent/100\",[66,3.704]],[\"name/101\",[70,39.383]],[\"parent/101\",[66,3.704]],[\"name/102\",[71,50.37]],[\"parent/102\",[63,2.184]],[\"name/103\",[65,33.024]],[\"parent/103\",[72,3.063]],[\"name/104\",[73,50.37]],[\"parent/104\",[72,3.063]],[\"name/105\",[74,45.261]],[\"parent/105\",[72,3.063]],[\"name/106\",[75,35.706]],[\"parent/106\",[72,3.063]],[\"name/107\",[65,33.024]],[\"parent/107\",[76,4.152]],[\"name/108\",[77,45.261]],[\"parent/108\",[72,3.063]],[\"name/109\",[75,35.706]],[\"parent/109\",[72,3.063]],[\"name/110\",[78,50.37]],[\"parent/110\",[76,4.152]],[\"name/111\",[79,45.261]],[\"parent/111\",[76,4.152]],[\"name/112\",[70,39.383]],[\"parent/112\",[72,3.063]],[\"name/113\",[80,50.37]],[\"parent/113\",[72,3.063]],[\"name/114\",[81,50.37]],[\"parent/114\",[72,3.063]],[\"name/115\",[67,34.275]],[\"parent/115\",[72,3.063]],[\"name/116\",[82,50.37]],[\"parent/116\",[63,2.184]],[\"name/117\",[70,39.383]],[\"parent/117\",[83,2.743]],[\"name/118\",[75,35.706]],[\"parent/118\",[83,2.743]],[\"name/119\",[84,50.37]],[\"parent/119\",[85,3.903]],[\"name/120\",[86,50.37]],[\"parent/120\",[85,3.903]],[\"name/121\",[87,50.37]],[\"parent/121\",[83,2.743]],[\"name/122\",[68,41.897]],[\"parent/122\",[83,2.743]],[\"name/123\",[88,45.261]],[\"parent/123\",[83,2.743]],[\"name/124\",[89,50.37]],[\"parent/124\",[83,2.743]],[\"name/125\",[77,45.261]],[\"parent/125\",[83,2.743]],[\"name/126\",[75,35.706]],[\"parent/126\",[83,2.743]],[\"name/127\",[79,45.261]],[\"parent/127\",[85,3.903]],[\"name/128\",[74,45.261]],[\"parent/128\",[83,2.743]],[\"name/129\",[75,35.706]],[\"parent/129\",[83,2.743]],[\"name/130\",[90,45.261]],[\"parent/130\",[85,3.903]],[\"name/131\",[91,50.37]],[\"parent/131\",[83,2.743]],[\"name/132\",[67,34.275]],[\"parent/132\",[83,2.743]],[\"name/133\",[69,45.261]],[\"parent/133\",[83,2.743]],[\"name/134\",[65,33.024]],[\"parent/134\",[83,2.743]],[\"name/135\",[92,50.37]],[\"parent/135\",[63,2.184]],[\"name/136\",[93,50.37]],[\"parent/136\",[94,4.485]],[\"name/137\",[95,45.261]],[\"parent/137\",[94,4.485]],[\"name/138\",[96,45.261]],[\"parent/138\",[63,2.184]],[\"name/139\",[67,34.275]],[\"parent/139\",[97,3.903]],[\"name/140\",[98,41.897]],[\"parent/140\",[97,3.903]],[\"name/141\",[99,39.383]],[\"parent/141\",[97,3.903]],[\"name/142\",[100,50.37]],[\"parent/142\",[97,3.903]],[\"name/143\",[101,45.261]],[\"parent/143\",[63,2.184]],[\"name/144\",[67,34.275]],[\"parent/144\",[102,3.903]],[\"name/145\",[65,33.024]],[\"parent/145\",[102,3.903]],[\"name/146\",[103,50.37]],[\"parent/146\",[102,3.903]],[\"name/147\",[99,39.383]],[\"parent/147\",[102,3.903]],[\"name/148\",[104,50.37]],[\"parent/148\",[63,2.184]],[\"name/149\",[67,34.275]],[\"parent/149\",[105,3.162]],[\"name/150\",[65,33.024]],[\"parent/150\",[105,3.162]],[\"name/151\",[98,41.897]],[\"parent/151\",[105,3.162]],[\"name/152\",[68,41.897]],[\"parent/152\",[105,3.162]],[\"name/153\",[106,41.897]],[\"parent/153\",[105,3.162]],[\"name/154\",[99,39.383]],[\"parent/154\",[105,3.162]],[\"name/155\",[107,50.37]],[\"parent/155\",[105,3.162]],[\"name/156\",[108,50.37]],[\"parent/156\",[105,3.162]],[\"name/157\",[109,50.37]],[\"parent/157\",[105,3.162]],[\"name/158\",[110,50.37]],[\"parent/158\",[63,2.184]],[\"name/159\",[106,41.897]],[\"parent/159\",[111,4.992]],[\"name/160\",[112,50.37]],[\"parent/160\",[63,2.184]],[\"name/161\",[113,50.37]],[\"parent/161\",[114,4.992]],[\"name/162\",[115,50.37]],[\"parent/162\",[63,2.184]],[\"name/163\",[116,50.37]],[\"parent/163\",[117,3.704]],[\"name/164\",[118,50.37]],[\"parent/164\",[117,3.704]],[\"name/165\",[119,50.37]],[\"parent/165\",[117,3.704]],[\"name/166\",[98,41.897]],[\"parent/166\",[117,3.704]],[\"name/167\",[120,41.897]],[\"parent/167\",[117,3.704]],[\"name/168\",[121,50.37]],[\"parent/168\",[63,2.184]],[\"name/169\",[1,37.377]],[\"parent/169\",[122,2.814]],[\"name/170\",[123,50.37]],[\"parent/170\",[122,2.814]],[\"name/171\",[124,50.37]],[\"parent/171\",[122,2.814]],[\"name/172\",[125,50.37]],[\"parent/172\",[122,2.814]],[\"name/173\",[126,50.37]],[\"parent/173\",[122,2.814]],[\"name/174\",[127,50.37]],[\"parent/174\",[122,2.814]],[\"name/175\",[128,50.37]],[\"parent/175\",[122,2.814]],[\"name/176\",[60,45.261]],[\"parent/176\",[122,2.814]],[\"name/177\",[90,45.261]],[\"parent/177\",[122,2.814]],[\"name/178\",[129,50.37]],[\"parent/178\",[122,2.814]],[\"name/179\",[130,50.37]],[\"parent/179\",[122,2.814]],[\"name/180\",[131,45.261]],[\"parent/180\",[122,2.814]],[\"name/181\",[132,50.37]],[\"parent/181\",[122,2.814]],[\"name/182\",[131,45.261]],[\"parent/182\",[63,2.184]],[\"name/183\",[133,50.37]],[\"parent/183\",[134,4.485]],[\"name/184\",[135,50.37]],[\"parent/184\",[134,4.485]],[\"name/185\",[136,50.37]],[\"parent/185\",[63,2.184]],[\"name/186\",[137,50.37]],[\"parent/186\",[138,3.538]],[\"name/187\",[139,50.37]],[\"parent/187\",[138,3.538]],[\"name/188\",[140,50.37]],[\"parent/188\",[138,3.538]],[\"name/189\",[141,50.37]],[\"parent/189\",[138,3.538]],[\"name/190\",[142,50.37]],[\"parent/190\",[138,3.538]],[\"name/191\",[143,50.37]],[\"parent/191\",[138,3.538]],[\"name/192\",[144,50.37]],[\"parent/192\",[63,2.184]],[\"name/193\",[67,34.275]],[\"parent/193\",[145,3.538]],[\"name/194\",[146,50.37]],[\"parent/194\",[145,3.538]],[\"name/195\",[99,39.383]],[\"parent/195\",[145,3.538]],[\"name/196\",[70,39.383]],[\"parent/196\",[145,3.538]],[\"name/197\",[75,35.706]],[\"parent/197\",[145,3.538]],[\"name/198\",[147,50.37]],[\"parent/198\",[148,4.992]],[\"name/199\",[88,45.261]],[\"parent/199\",[145,3.538]],[\"name/200\",[149,50.37]],[\"parent/200\",[63,2.184]],[\"name/201\",[95,45.261]],[\"parent/201\",[150,4.992]],[\"name/202\",[35,45.261]],[\"parent/202\",[63,2.184]],[\"name/203\",[101,45.261]],[\"parent/203\",[151,4.485]],[\"name/204\",[152,50.37]],[\"parent/204\",[151,4.485]],[\"name/205\",[153,50.37]],[\"parent/205\",[63,2.184]],[\"name/206\",[154,50.37]],[\"parent/206\",[155,3.903]],[\"name/207\",[156,50.37]],[\"parent/207\",[155,3.903]],[\"name/208\",[157,50.37]],[\"parent/208\",[155,3.903]],[\"name/209\",[158,50.37]],[\"parent/209\",[155,3.903]],[\"name/210\",[159,50.37]],[\"parent/210\",[63,2.184]],[\"name/211\",[160,50.37]],[\"parent/211\",[161,4.485]],[\"name/212\",[162,50.37]],[\"parent/212\",[161,4.485]],[\"name/213\",[163,50.37]],[\"parent/213\",[63,2.184]],[\"name/214\",[164,45.261]],[\"parent/214\",[165,4.992]],[\"name/215\",[166,50.37]],[\"parent/215\",[63,2.184]],[\"name/216\",[96,45.261]],[\"parent/216\",[167,4.485]],[\"name/217\",[168,50.37]],[\"parent/217\",[167,4.485]],[\"name/218\",[169,50.37]],[\"parent/218\",[63,2.184]],[\"name/219\",[65,33.024]],[\"parent/219\",[170,4.152]],[\"name/220\",[120,41.897]],[\"parent/220\",[170,4.152]],[\"name/221\",[164,45.261]],[\"parent/221\",[170,4.152]],[\"name/222\",[171,50.37]],[\"parent/222\",[63,2.184]],[\"name/223\",[65,33.024]],[\"parent/223\",[172,4.152]],[\"name/224\",[120,41.897]],[\"parent/224\",[172,4.152]],[\"name/225\",[106,41.897]],[\"parent/225\",[172,4.152]],[\"name/226\",[173,50.37]],[\"parent/226\",[63,2.184]],[\"name/227\",[174,50.37]],[\"parent/227\",[63,2.184]],[\"name/228\",[175,50.37]],[\"parent/228\",[176,4.485]],[\"name/229\",[177,50.37]],[\"parent/229\",[176,4.485]]],\"invertedIndex\":[[\"__type\",{\"_index\":75,\"name\":{\"106\":{},\"109\":{},\"118\":{},\"126\":{},\"129\":{},\"197\":{}},\"parent\":{}}],[\"_debug\",{\"_index\":177,\"name\":{\"229\":{}},\"parent\":{}}],[\"_program\",{\"_index\":175,\"name\":{\"228\":{}},\"parent\":{}}],[\"_webout.json\",{\"_index\":113,\"name\":{\"161\":{}},\"parent\":{}}],[\"access_token\",{\"_index\":54,\"name\":{\"60\":{}},\"parent\":{}}],[\"appendrequest\",{\"_index\":59,\"name\":{\"65\":{}},\"parent\":{}}],[\"apploc\",{\"_index\":127,\"name\":{\"174\":{}},\"parent\":{}}],[\"attributes\",{\"_index\":70,\"name\":{\"101\":{},\"112\":{},\"117\":{},\"196\":{}},\"parent\":{}}],[\"authorizeallauthenticatedusers\",{\"_index\":81,\"name\":{\"114\":{}},\"parent\":{}}],[\"authorizedusers\",{\"_index\":80,\"name\":{\"113\":{}},\"parent\":{}}],[\"autoexeclines\",{\"_index\":79,\"name\":{\"111\":{},\"127\":{}},\"parent\":{}}],[\"checksession\",{\"_index\":32,\"name\":{\"32\":{}},\"parent\":{}}],[\"clearsasrequests\",{\"_index\":43,\"name\":{\"43\":{}},\"parent\":{}}],[\"code\",{\"_index\":106,\"name\":{\"153\":{},\"159\":{},\"225\":{}},\"parent\":{}}],[\"constructor\",{\"_index\":1,\"name\":{\"2\":{},\"46\":{},\"52\":{},\"64\":{},\"169\":{}},\"parent\":{}}],[\"context\",{\"_index\":64,\"name\":{\"96\":{}},\"parent\":{}}],[\"contextallattributes\",{\"_index\":82,\"name\":{\"116\":{}},\"parent\":{}}],[\"contextname\",{\"_index\":90,\"name\":{\"130\":{},\"177\":{}},\"parent\":{}}],[\"createcomputecontext\",{\"_index\":10,\"name\":{\"10\":{},\"75\":{}},\"parent\":{}}],[\"createdby\",{\"_index\":68,\"name\":{\"99\":{},\"122\":{},\"152\":{}},\"parent\":{}}],[\"createfile\",{\"_index\":18,\"name\":{\"18\":{},\"81\":{}},\"parent\":{}}],[\"createfolder\",{\"_index\":17,\"name\":{\"17\":{},\"82\":{}},\"parent\":{}}],[\"createjobdefinition\",{\"_index\":23,\"name\":{\"23\":{},\"83\":{}},\"parent\":{}}],[\"createlaunchercontext\",{\"_index\":11,\"name\":{\"11\":{},\"76\":{}},\"parent\":{}}],[\"createsession\",{\"_index\":16,\"name\":{\"16\":{},\"74\":{}},\"parent\":{}}],[\"creationtimestamp\",{\"_index\":88,\"name\":{\"123\":{},\"199\":{}},\"parent\":{}}],[\"csrftoken\",{\"_index\":92,\"name\":{\"135\":{}},\"parent\":{}}],[\"debug\",{\"_index\":60,\"name\":{\"66\":{},\"176\":{}},\"parent\":{}}],[\"default\",{\"_index\":133,\"name\":{\"183\":{}},\"parent\":{}}],[\"deleteclient\",{\"_index\":27,\"name\":{\"27\":{},\"87\":{}},\"parent\":{}}],[\"deletecomputecontext\",{\"_index\":13,\"name\":{\"13\":{},\"78\":{}},\"parent\":{}}],[\"deletefolder\",{\"_index\":20,\"name\":{\"20\":{},\"94\":{}},\"parent\":{}}],[\"deploy\",{\"_index\":51,\"name\":{\"54\":{}},\"parent\":{}}],[\"deployservicepack\",{\"_index\":37,\"name\":{\"37\":{}},\"parent\":{}}],[\"deploytosasjs\",{\"_index\":38,\"name\":{\"38\":{}},\"parent\":{}}],[\"description\",{\"_index\":73,\"name\":{\"104\":{}},\"parent\":{}}],[\"editcomputecontext\",{\"_index\":12,\"name\":{\"12\":{},\"77\":{}},\"parent\":{}}],[\"editcontextinput\",{\"_index\":71,\"name\":{\"102\":{}},\"parent\":{}}],[\"environment\",{\"_index\":77,\"name\":{\"108\":{},\"125\":{}},\"parent\":{}}],[\"error\",{\"_index\":108,\"name\":{\"156\":{}},\"parent\":{}}],[\"executecomputejob\",{\"_index\":62,\"name\":{\"88\":{}},\"parent\":{}}],[\"executejob\",{\"_index\":52,\"name\":{\"55\":{},\"89\":{}},\"parent\":{}}],[\"executejobsasjs\",{\"_index\":39,\"name\":{\"39\":{}},\"parent\":{}}],[\"executescript\",{\"_index\":48,\"name\":{\"49\":{},\"79\":{}},\"parent\":{}}],[\"executescriptsas9\",{\"_index\":4,\"name\":{\"4\":{}},\"parent\":{}}],[\"executescriptsasviya\",{\"_index\":5,\"name\":{\"5\":{}},\"parent\":{}}],[\"executionquery\",{\"_index\":174,\"name\":{\"227\":{}},\"parent\":{}}],[\"fetchlogfilecontent\",{\"_index\":41,\"name\":{\"41\":{}},\"parent\":{}}],[\"file\",{\"_index\":101,\"name\":{\"143\":{},\"203\":{}},\"parent\":{}}],[\"filename\",{\"_index\":152,\"name\":{\"204\":{}},\"parent\":{}}],[\"filetree\",{\"_index\":163,\"name\":{\"213\":{}},\"parent\":{}}],[\"folder\",{\"_index\":96,\"name\":{\"138\":{},\"216\":{}},\"parent\":{}}],[\"foldermember\",{\"_index\":169,\"name\":{\"218\":{}},\"parent\":{}}],[\"generatedcode\",{\"_index\":141,\"name\":{\"189\":{}},\"parent\":{}}],[\"getaccesstoken\",{\"_index\":25,\"name\":{\"25\":{},\"56\":{},\"85\":{}},\"parent\":{}}],[\"getauthcode\",{\"_index\":24,\"name\":{\"24\":{},\"58\":{},\"84\":{}},\"parent\":{}}],[\"getcomputecontextbyid\",{\"_index\":15,\"name\":{\"15\":{},\"91\":{}},\"parent\":{}}],[\"getcomputecontextbyname\",{\"_index\":14,\"name\":{\"14\":{},\"90\":{}},\"parent\":{}}],[\"getcomputecontexts\",{\"_index\":6,\"name\":{\"6\":{},\"70\":{}},\"parent\":{}}],[\"getconfig\",{\"_index\":46,\"name\":{\"47\":{},\"68\":{}},\"parent\":{}}],[\"getcsrftoken\",{\"_index\":3,\"name\":{\"3\":{}},\"parent\":{}}],[\"getdefaultcomputecontexts\",{\"_index\":8,\"name\":{\"8\":{},\"71\":{}},\"parent\":{}}],[\"getexecutablecontexts\",{\"_index\":9,\"name\":{\"9\":{},\"73\":{}},\"parent\":{}}],[\"getfolder\",{\"_index\":19,\"name\":{\"19\":{},\"80\":{}},\"parent\":{}}],[\"getjobsinfolder\",{\"_index\":61,\"name\":{\"67\":{}},\"parent\":{}}],[\"getlaunchercontexts\",{\"_index\":7,\"name\":{\"7\":{},\"72\":{}},\"parent\":{}}],[\"getsasjsconfig\",{\"_index\":28,\"name\":{\"28\":{}},\"parent\":{}}],[\"getsasrequests\",{\"_index\":42,\"name\":{\"42\":{}},\"parent\":{}}],[\"getusername\",{\"_index\":29,\"name\":{\"29\":{}},\"parent\":{}}],[\"headername\",{\"_index\":93,\"name\":{\"136\":{}},\"parent\":{}}],[\"href\",{\"_index\":119,\"name\":{\"165\":{}},\"parent\":{}}],[\"httpsagentoptions\",{\"_index\":130,\"name\":{\"179\":{}},\"parent\":{}}],[\"id\",{\"_index\":67,\"name\":{\"98\":{},\"115\":{},\"132\":{},\"139\":{},\"144\":{},\"149\":{},\"193\":{}},\"parent\":{}}],[\"isfiletree\",{\"_index\":173,\"name\":{\"226\":{}},\"parent\":{}}],[\"job\",{\"_index\":104,\"name\":{\"148\":{}},\"parent\":{}}],[\"jobdefinition\",{\"_index\":110,\"name\":{\"158\":{}},\"parent\":{}}],[\"jobresult\",{\"_index\":112,\"name\":{\"160\":{}},\"parent\":{}}],[\"launchcontext\",{\"_index\":74,\"name\":{\"105\":{},\"128\":{}},\"parent\":{}}],[\"launchtype\",{\"_index\":89,\"name\":{\"124\":{}},\"parent\":{}}],[\"link\",{\"_index\":115,\"name\":{\"162\":{}},\"parent\":{}}],[\"links\",{\"_index\":99,\"name\":{\"141\":{},\"147\":{},\"154\":{},\"195\":{}},\"parent\":{}}],[\"listfolder\",{\"_index\":21,\"name\":{\"21\":{},\"92\":{}},\"parent\":{}}],[\"logfile\",{\"_index\":142,\"name\":{\"190\":{}},\"parent\":{}}],[\"logfolderpath\",{\"_index\":158,\"name\":{\"209\":{}},\"parent\":{}}],[\"login\",{\"_index\":33,\"name\":{\"33\":{}},\"parent\":{}}],[\"loginmechanism\",{\"_index\":131,\"name\":{\"180\":{},\"182\":{}},\"parent\":{}}],[\"logout\",{\"_index\":34,\"name\":{\"34\":{}},\"parent\":{}}],[\"logstatistics\",{\"_index\":109,\"name\":{\"157\":{}},\"parent\":{}}],[\"maxpollcount\",{\"_index\":154,\"name\":{\"206\":{}},\"parent\":{}}],[\"membercount\",{\"_index\":100,\"name\":{\"142\":{}},\"parent\":{}}],[\"members\",{\"_index\":164,\"name\":{\"214\":{},\"221\":{}},\"parent\":{}}],[\"membertype\",{\"_index\":166,\"name\":{\"215\":{}},\"parent\":{}}],[\"method\",{\"_index\":116,\"name\":{\"163\":{}},\"parent\":{}}],[\"modifiedby\",{\"_index\":91,\"name\":{\"131\":{}},\"parent\":{}}],[\"modifiedtimestamp\",{\"_index\":87,\"name\":{\"121\":{}},\"parent\":{}}],[\"movefolder\",{\"_index\":22,\"name\":{\"22\":{},\"93\":{}},\"parent\":{}}],[\"name\",{\"_index\":65,\"name\":{\"97\":{},\"103\":{},\"107\":{},\"134\":{},\"145\":{},\"150\":{},\"219\":{},\"223\":{}},\"parent\":{}}],[\"options\",{\"_index\":78,\"name\":{\"110\":{}},\"parent\":{}}],[\"parenturi\",{\"_index\":103,\"name\":{\"146\":{}},\"parent\":{}}],[\"path\",{\"_index\":162,\"name\":{\"212\":{}},\"parent\":{}}],[\"pathsas9\",{\"_index\":125,\"name\":{\"172\":{}},\"parent\":{}}],[\"pathsasjs\",{\"_index\":124,\"name\":{\"171\":{}},\"parent\":{}}],[\"pathsasviya\",{\"_index\":126,\"name\":{\"173\":{}},\"parent\":{}}],[\"pollinterval\",{\"_index\":156,\"name\":{\"207\":{}},\"parent\":{}}],[\"polloptions\",{\"_index\":153,\"name\":{\"205\":{}},\"parent\":{}}],[\"redirected\",{\"_index\":135,\"name\":{\"184\":{}},\"parent\":{}}],[\"refresh_token\",{\"_index\":56,\"name\":{\"61\":{}},\"parent\":{}}],[\"refreshtokens\",{\"_index\":26,\"name\":{\"26\":{},\"57\":{},\"86\":{}},\"parent\":{}}],[\"rel\",{\"_index\":118,\"name\":{\"164\":{}},\"parent\":{}}],[\"request\",{\"_index\":36,\"name\":{\"36\":{}},\"parent\":{}}],[\"requesthistorylimit\",{\"_index\":132,\"name\":{\"181\":{}},\"parent\":{}}],[\"results\",{\"_index\":107,\"name\":{\"155\":{}},\"parent\":{}}],[\"reuseserverprocesses\",{\"_index\":84,\"name\":{\"119\":{}},\"parent\":{}}],[\"runserveras\",{\"_index\":86,\"name\":{\"120\":{}},\"parent\":{}}],[\"sas9apiclient\",{\"_index\":44,\"name\":{\"44\":{},\"45\":{}},\"parent\":{\"45\":{}}}],[\"sas9apiclient.sas9apiclient\",{\"_index\":45,\"name\":{},\"parent\":{\"46\":{},\"47\":{},\"48\":{},\"49\":{}}}],[\"sasjs\",{\"_index\":0,\"name\":{\"0\":{},\"1\":{}},\"parent\":{\"1\":{}}}],[\"sasjs.sasjs\",{\"_index\":2,\"name\":{},\"parent\":{\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"16\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"26\":{},\"27\":{},\"28\":{},\"29\":{},\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"sasjsapiclient\",{\"_index\":49,\"name\":{\"50\":{},\"51\":{}},\"parent\":{\"51\":{},\"59\":{}}}],[\"sasjsapiclient.sasjsapiclient\",{\"_index\":50,\"name\":{},\"parent\":{\"52\":{},\"53\":{},\"54\":{},\"55\":{},\"56\":{},\"57\":{},\"58\":{}}}],[\"sasjsapiclient.sasjsauthresponse\",{\"_index\":55,\"name\":{},\"parent\":{\"60\":{},\"61\":{}}}],[\"sasjsauthresponse\",{\"_index\":53,\"name\":{\"59\":{}},\"parent\":{}}],[\"sasjsconfig\",{\"_index\":121,\"name\":{\"168\":{}},\"parent\":{}}],[\"sasjsrequest\",{\"_index\":136,\"name\":{\"185\":{}},\"parent\":{}}],[\"sasviyaapiclient\",{\"_index\":57,\"name\":{\"62\":{},\"63\":{}},\"parent\":{\"63\":{}}}],[\"sasviyaapiclient.sasviyaapiclient\",{\"_index\":58,\"name\":{},\"parent\":{\"64\":{},\"65\":{},\"66\":{},\"67\":{},\"68\":{},\"69\":{},\"70\":{},\"71\":{},\"72\":{},\"73\":{},\"74\":{},\"75\":{},\"76\":{},\"77\":{},\"78\":{},\"79\":{},\"80\":{},\"81\":{},\"82\":{},\"83\":{},\"84\":{},\"85\":{},\"86\":{},\"87\":{},\"88\":{},\"89\":{},\"90\":{},\"91\":{},\"92\":{},\"93\":{},\"94\":{}}}],[\"saswork\",{\"_index\":143,\"name\":{\"191\":{}},\"parent\":{}}],[\"servertype\",{\"_index\":128,\"name\":{\"175\":{}},\"parent\":{}}],[\"serverurl\",{\"_index\":123,\"name\":{\"170\":{}},\"parent\":{}}],[\"service\",{\"_index\":168,\"name\":{\"217\":{}},\"parent\":{}}],[\"servicelink\",{\"_index\":137,\"name\":{\"186\":{}},\"parent\":{}}],[\"servicemember\",{\"_index\":171,\"name\":{\"222\":{}},\"parent\":{}}],[\"session\",{\"_index\":144,\"name\":{\"192\":{}},\"parent\":{}}],[\"sessioninactivetimeout\",{\"_index\":147,\"name\":{\"198\":{}},\"parent\":{}}],[\"sessionvariable\",{\"_index\":149,\"name\":{\"200\":{}},\"parent\":{}}],[\"setconfig\",{\"_index\":47,\"name\":{\"48\":{},\"53\":{},\"69\":{}},\"parent\":{}}],[\"setdebugstate\",{\"_index\":31,\"name\":{\"31\":{}},\"parent\":{}}],[\"setsasjsconfig\",{\"_index\":30,\"name\":{\"30\":{}},\"parent\":{}}],[\"sourcecode\",{\"_index\":140,\"name\":{\"188\":{}},\"parent\":{}}],[\"startcomputejob\",{\"_index\":40,\"name\":{\"40\":{}},\"parent\":{}}],[\"state\",{\"_index\":146,\"name\":{\"194\":{}},\"parent\":{}}],[\"streamlog\",{\"_index\":157,\"name\":{\"208\":{}},\"parent\":{}}],[\"timestamp\",{\"_index\":139,\"name\":{\"187\":{}},\"parent\":{}}],[\"type\",{\"_index\":120,\"name\":{\"167\":{},\"220\":{},\"224\":{}},\"parent\":{}}],[\"types\",{\"_index\":63,\"name\":{\"95\":{}},\"parent\":{\"96\":{},\"102\":{},\"116\":{},\"135\":{},\"138\":{},\"143\":{},\"148\":{},\"158\":{},\"160\":{},\"162\":{},\"168\":{},\"182\":{},\"185\":{},\"192\":{},\"200\":{},\"202\":{},\"205\":{},\"210\":{},\"213\":{},\"215\":{},\"218\":{},\"222\":{},\"226\":{},\"227\":{}}}],[\"types.context\",{\"_index\":66,\"name\":{},\"parent\":{\"97\":{},\"98\":{},\"99\":{},\"100\":{},\"101\":{}}}],[\"types.contextallattributes\",{\"_index\":83,\"name\":{},\"parent\":{\"117\":{},\"118\":{},\"121\":{},\"122\":{},\"123\":{},\"124\":{},\"125\":{},\"126\":{},\"128\":{},\"129\":{},\"131\":{},\"132\":{},\"133\":{},\"134\":{}}}],[\"types.contextallattributes.__type\",{\"_index\":85,\"name\":{},\"parent\":{\"119\":{},\"120\":{},\"127\":{},\"130\":{}}}],[\"types.csrftoken\",{\"_index\":94,\"name\":{},\"parent\":{\"136\":{},\"137\":{}}}],[\"types.editcontextinput\",{\"_index\":72,\"name\":{},\"parent\":{\"103\":{},\"104\":{},\"105\":{},\"106\":{},\"108\":{},\"109\":{},\"112\":{},\"113\":{},\"114\":{},\"115\":{}}}],[\"types.editcontextinput.__type\",{\"_index\":76,\"name\":{},\"parent\":{\"107\":{},\"110\":{},\"111\":{}}}],[\"types.executionquery\",{\"_index\":176,\"name\":{},\"parent\":{\"228\":{},\"229\":{}}}],[\"types.file\",{\"_index\":102,\"name\":{},\"parent\":{\"144\":{},\"145\":{},\"146\":{},\"147\":{}}}],[\"types.filetree\",{\"_index\":165,\"name\":{},\"parent\":{\"214\":{}}}],[\"types.folder\",{\"_index\":97,\"name\":{},\"parent\":{\"139\":{},\"140\":{},\"141\":{},\"142\":{}}}],[\"types.foldermember\",{\"_index\":170,\"name\":{},\"parent\":{\"219\":{},\"220\":{},\"221\":{}}}],[\"types.job\",{\"_index\":105,\"name\":{},\"parent\":{\"149\":{},\"150\":{},\"151\":{},\"152\":{},\"153\":{},\"154\":{},\"155\":{},\"156\":{},\"157\":{}}}],[\"types.jobdefinition\",{\"_index\":111,\"name\":{},\"parent\":{\"159\":{}}}],[\"types.jobresult\",{\"_index\":114,\"name\":{},\"parent\":{\"161\":{}}}],[\"types.link\",{\"_index\":117,\"name\":{},\"parent\":{\"163\":{},\"164\":{},\"165\":{},\"166\":{},\"167\":{}}}],[\"types.loginmechanism\",{\"_index\":134,\"name\":{},\"parent\":{\"183\":{},\"184\":{}}}],[\"types.membertype\",{\"_index\":167,\"name\":{},\"parent\":{\"216\":{},\"217\":{}}}],[\"types.polloptions\",{\"_index\":155,\"name\":{},\"parent\":{\"206\":{},\"207\":{},\"208\":{},\"209\":{}}}],[\"types.sasjsconfig\",{\"_index\":122,\"name\":{},\"parent\":{\"169\":{},\"170\":{},\"171\":{},\"172\":{},\"173\":{},\"174\":{},\"175\":{},\"176\":{},\"177\":{},\"178\":{},\"179\":{},\"180\":{},\"181\":{}}}],[\"types.sasjsrequest\",{\"_index\":138,\"name\":{},\"parent\":{\"186\":{},\"187\":{},\"188\":{},\"189\":{},\"190\":{},\"191\":{}}}],[\"types.servicemember\",{\"_index\":172,\"name\":{},\"parent\":{\"223\":{},\"224\":{},\"225\":{}}}],[\"types.session\",{\"_index\":145,\"name\":{},\"parent\":{\"193\":{},\"194\":{},\"195\":{},\"196\":{},\"197\":{},\"199\":{}}}],[\"types.session.__type\",{\"_index\":148,\"name\":{},\"parent\":{\"198\":{}}}],[\"types.sessionvariable\",{\"_index\":150,\"name\":{},\"parent\":{\"201\":{}}}],[\"types.uploadfile\",{\"_index\":151,\"name\":{},\"parent\":{\"203\":{},\"204\":{}}}],[\"types.writestream\",{\"_index\":161,\"name\":{},\"parent\":{\"211\":{},\"212\":{}}}],[\"uploadfile\",{\"_index\":35,\"name\":{\"35\":{},\"202\":{}},\"parent\":{}}],[\"uri\",{\"_index\":98,\"name\":{\"140\":{},\"151\":{},\"166\":{}},\"parent\":{}}],[\"usecomputeapi\",{\"_index\":129,\"name\":{\"178\":{}},\"parent\":{}}],[\"value\",{\"_index\":95,\"name\":{\"137\":{},\"201\":{}},\"parent\":{}}],[\"version\",{\"_index\":69,\"name\":{\"100\":{},\"133\":{}},\"parent\":{}}],[\"write\",{\"_index\":160,\"name\":{\"211\":{}},\"parent\":{}}],[\"writestream\",{\"_index\":159,\"name\":{\"210\":{}},\"parent\":{}}]],\"pipeline\":[]}}"); \ No newline at end of file diff --git a/docs/assets/style.css b/docs/assets/style.css deleted file mode 100644 index a16ed02..0000000 --- a/docs/assets/style.css +++ /dev/null @@ -1,1413 +0,0 @@ -@import url("./icons.css"); - -:root { - /* Light */ - --light-color-background: #fcfcfc; - --light-color-secondary-background: #fff; - --light-color-text: #222; - --light-color-text-aside: #707070; - --light-color-link: #4da6ff; - --light-color-menu-divider: #eee; - --light-color-menu-divider-focus: #000; - --light-color-menu-label: #707070; - --light-color-panel: var(--light-color-secondary-background); - --light-color-panel-divider: #eee; - --light-color-comment-tag: #707070; - --light-color-comment-tag-text: #fff; - --light-color-ts: #9600ff; - --light-color-ts-interface: #647f1b; - --light-color-ts-enum: #937210; - --light-color-ts-class: #0672de; - --light-color-ts-private: #707070; - --light-color-toolbar: #fff; - --light-color-toolbar-text: #333; - --light-icon-filter: invert(0); - --light-external-icon: url("data:image/svg+xml;utf8,"); - - /* Dark */ - --dark-color-background: #36393f; - --dark-color-secondary-background: #2f3136; - --dark-color-text: #ffffff; - --dark-color-text-aside: #e6e4e4; - --dark-color-link: #00aff4; - --dark-color-menu-divider: #eee; - --dark-color-menu-divider-focus: #000; - --dark-color-menu-label: #707070; - --dark-color-panel: var(--dark-color-secondary-background); - --dark-color-panel-divider: #818181; - --dark-color-comment-tag: #dcddde; - --dark-color-comment-tag-text: #2f3136; - --dark-color-ts: #c97dff; - --dark-color-ts-interface: #9cbe3c; - --dark-color-ts-enum: #d6ab29; - --dark-color-ts-class: #3695f3; - --dark-color-ts-private: #e2e2e2; - --dark-color-toolbar: #34373c; - --dark-color-toolbar-text: #ffffff; - --dark-icon-filter: invert(1); - --dark-external-icon: url("data:image/svg+xml;utf8,"); -} - -@media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-secondary-background: var(--light-color-secondary-background); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-menu-divider: var(--light-color-menu-divider); - --color-menu-divider-focus: var(--light-color-menu-divider-focus); - --color-menu-label: var(--light-color-menu-label); - --color-panel: var(--light-color-panel); - --color-panel-divider: var(--light-color-panel-divider); - --color-comment-tag: var(--light-color-comment-tag); - --color-comment-tag-text: var(--light-color-comment-tag-text); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-private: var(--light-color-ts-private); - --color-toolbar: var(--light-color-toolbar); - --color-toolbar-text: var(--light-color-toolbar-text); - --icon-filter: var(--light-icon-filter); - --external-icon: var(--light-external-icon); - } -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-secondary-background: var(--dark-color-secondary-background); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-menu-divider: var(--dark-color-menu-divider); - --color-menu-divider-focus: var(--dark-color-menu-divider-focus); - --color-menu-label: var(--dark-color-menu-label); - --color-panel: var(--dark-color-panel); - --color-panel-divider: var(--dark-color-panel-divider); - --color-comment-tag: var(--dark-color-comment-tag); - --color-comment-tag-text: var(--dark-color-comment-tag-text); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-private: var(--dark-color-ts-private); - --color-toolbar: var(--dark-color-toolbar); - --color-toolbar-text: var(--dark-color-toolbar-text); - --icon-filter: var(--dark-icon-filter); - --external-icon: var(--dark-external-icon); - } -} - -body { - margin: 0; -} - -body.light { - --color-background: var(--light-color-background); - --color-secondary-background: var(--light-color-secondary-background); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-menu-divider: var(--light-color-menu-divider); - --color-menu-divider-focus: var(--light-color-menu-divider-focus); - --color-menu-label: var(--light-color-menu-label); - --color-panel: var(--light-color-panel); - --color-panel-divider: var(--light-color-panel-divider); - --color-comment-tag: var(--light-color-comment-tag); - --color-comment-tag-text: var(--light-color-comment-tag-text); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-private: var(--light-color-ts-private); - --color-toolbar: var(--light-color-toolbar); - --color-toolbar-text: var(--light-color-toolbar-text); - --icon-filter: var(--light-icon-filter); - --external-icon: var(--light-external-icon); -} - -body.dark { - --color-background: var(--dark-color-background); - --color-secondary-background: var(--dark-color-secondary-background); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-menu-divider: var(--dark-color-menu-divider); - --color-menu-divider-focus: var(--dark-color-menu-divider-focus); - --color-menu-label: var(--dark-color-menu-label); - --color-panel: var(--dark-color-panel); - --color-panel-divider: var(--dark-color-panel-divider); - --color-comment-tag: var(--dark-color-comment-tag); - --color-comment-tag-text: var(--dark-color-comment-tag-text); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-private: var(--dark-color-ts-private); - --color-toolbar: var(--dark-color-toolbar); - --color-toolbar-text: var(--dark-color-toolbar-text); - --icon-filter: var(--dark-icon-filter); - --external-icon: var(--dark-external-icon); -} - -h1, -h2, -h3, -h4, -h5, -h6 { - line-height: 1.2; -} - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -h2 { - font-size: 1.5em; - margin: 0.83em 0; -} - -h3 { - font-size: 1.17em; - margin: 1em 0; -} - -h4, -.tsd-index-panel h3 { - font-size: 1em; - margin: 1.33em 0; -} - -h5 { - font-size: 0.83em; - margin: 1.67em 0; -} - -h6 { - font-size: 0.67em; - margin: 2.33em 0; -} - -pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; -} - -dl, -menu, -ol, -ul { - margin: 1em 0; -} - -dd { - margin: 0 0 0 40px; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 40px; -} -@media (max-width: 640px) { - .container { - padding: 0 20px; - } -} - -.container-main { - padding-bottom: 200px; -} - -.row { - display: flex; - position: relative; - margin: 0 -10px; -} -.row:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; -} - -.col-4, -.col-8 { - box-sizing: border-box; - float: left; - padding: 0 10px; -} - -.col-4 { - width: 33.3333333333%; -} -.col-8 { - width: 66.6666666667%; -} - -ul.tsd-descriptions > li > :first-child, -.tsd-panel > :first-child, -.col-8 > :first-child, -.col-4 > :first-child, -ul.tsd-descriptions > li > :first-child > :first-child, -.tsd-panel > :first-child > :first-child, -.col-8 > :first-child > :first-child, -.col-4 > :first-child > :first-child, -ul.tsd-descriptions > li > :first-child > :first-child > :first-child, -.tsd-panel > :first-child > :first-child > :first-child, -.col-8 > :first-child > :first-child > :first-child, -.col-4 > :first-child > :first-child > :first-child { - margin-top: 0; -} -ul.tsd-descriptions > li > :last-child, -.tsd-panel > :last-child, -.col-8 > :last-child, -.col-4 > :last-child, -ul.tsd-descriptions > li > :last-child > :last-child, -.tsd-panel > :last-child > :last-child, -.col-8 > :last-child > :last-child, -.col-4 > :last-child > :last-child, -ul.tsd-descriptions > li > :last-child > :last-child > :last-child, -.tsd-panel > :last-child > :last-child > :last-child, -.col-8 > :last-child > :last-child > :last-child, -.col-4 > :last-child > :last-child > :last-child { - margin-bottom: 0; -} - -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } -} -@keyframes fade-in-delayed { - 0% { - opacity: 0; - } - 33% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes fade-out-delayed { - 0% { - opacity: 1; - visibility: visible; - } - 66% { - opacity: 0; - } - 100% { - opacity: 0; - } -} -@keyframes shift-to-left { - from { - transform: translate(0, 0); - } - to { - transform: translate(-25%, 0); - } -} -@keyframes unshift-to-left { - from { - transform: translate(-25%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } -} -body { - background: var(--color-background); - font-family: "Segoe UI", sans-serif; - font-size: 16px; - color: var(--color-text); -} - -a { - color: var(--color-link); - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; -} - -code, -pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 14px; -} - -pre { - padding: 10px; -} -pre code { - padding: 0; - font-size: 100%; -} - -blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; -} - -.tsd-typography { - line-height: 1.333em; -} -.tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-typography h4, -.tsd-typography .tsd-index-panel h3, -.tsd-index-panel .tsd-typography h3, -.tsd-typography h5, -.tsd-typography h6 { - font-size: 1em; - margin: 0; -} -.tsd-typography h5, -.tsd-typography h6 { - font-weight: normal; -} -.tsd-typography p, -.tsd-typography ul, -.tsd-typography ol { - margin: 1em 0; -} - -@media (min-width: 901px) and (max-width: 1024px) { - html .col-content { - width: 72%; - } - html .col-menu { - width: 28%; - } - html .tsd-navigation { - padding-left: 10px; - } -} -@media (max-width: 900px) { - html .col-content { - float: none; - width: 100%; - } - html .col-menu { - position: fixed !important; - overflow: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - width: 100%; - padding: 20px 20px 0 0; - max-width: 450px; - visibility: hidden; - background-color: var(--color-panel); - transform: translate(100%, 0); - } - html .col-menu > *:last-child { - padding-bottom: 20px; - } - html .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu :is(header, footer, .col-content) { - animation: shift-to-left 0.4s; - } - - .to-has-menu .col-menu { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu :is(header, footer, .col-content) { - animation: unshift-to-left 0.4s; - } - - .from-has-menu .col-menu { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu :is(header, footer, .col-content) { - transform: translate(-25%, 0); - } - .has-menu .col-menu { - visibility: visible; - transform: translate(0, 0); - display: grid; - grid-template-rows: auto 1fr; - max-height: 100vh; - } - .has-menu .tsd-navigation { - max-height: 100%; - } -} - -.tsd-page-title { - padding: 70px 0 20px 0; - margin: 0 0 40px 0; - background: var(--color-panel); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.35); -} -.tsd-page-title h1 { - margin: 0; -} - -.tsd-breadcrumb { - margin: 0; - padding: 0; - color: var(--color-text-aside); -} -.tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; -} -.tsd-breadcrumb a:hover { - text-decoration: underline; -} -.tsd-breadcrumb li { - display: inline; -} -.tsd-breadcrumb li:after { - content: " / "; -} - -dl.tsd-comment-tags { - overflow: hidden; -} -dl.tsd-comment-tags dt { - float: left; - padding: 1px 5px; - margin: 0 10px 0 0; - border-radius: 4px; - border: 1px solid var(--color-comment-tag); - color: var(--color-comment-tag); - font-size: 0.8em; - font-weight: normal; -} -dl.tsd-comment-tags dd { - margin: 0 0 10px 0; -} -dl.tsd-comment-tags dd:before, -dl.tsd-comment-tags dd:after { - display: table; - content: " "; -} -dl.tsd-comment-tags dd pre, -dl.tsd-comment-tags dd:after { - clear: both; -} -dl.tsd-comment-tags p { - margin: 0; -} - -.tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; -} -.tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; -} - -.toggle-protected .tsd-is-private { - display: none; -} - -.toggle-public .tsd-is-private, -.toggle-public .tsd-is-protected, -.toggle-public .tsd-is-private-protected { - display: none; -} - -.toggle-inherited .tsd-is-inherited { - display: none; -} - -.toggle-externals .tsd-is-external { - display: none; -} - -#tsd-filter { - position: relative; - display: inline-block; - height: 40px; - vertical-align: bottom; -} -.no-filter #tsd-filter { - display: none; -} -#tsd-filter .tsd-filter-group { - display: inline-block; - height: 40px; - vertical-align: bottom; - white-space: nowrap; -} -#tsd-filter input { - display: none; -} -@media (max-width: 900px) { - #tsd-filter .tsd-filter-group { - display: block; - position: absolute; - top: 40px; - right: 20px; - height: auto; - background-color: var(--color-panel); - visibility: hidden; - transform: translate(50%, 0); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); - } - .has-options #tsd-filter .tsd-filter-group { - visibility: visible; - } - .to-has-options #tsd-filter .tsd-filter-group { - animation: fade-in 0.2s; - } - .from-has-options #tsd-filter .tsd-filter-group { - animation: fade-out 0.2s; - } - #tsd-filter label, - #tsd-filter .tsd-select { - display: block; - padding-right: 20px; - } -} - -footer { - border-top: 1px solid var(--color-panel-divider); - background-color: var(--color-panel); -} -footer:after { - content: ""; - display: table; -} -footer.with-border-bottom { - border-bottom: 1px solid var(--color-panel-divider); -} -footer .tsd-legend-group { - font-size: 0; -} -footer .tsd-legend { - display: inline-block; - width: 25%; - padding: 0; - font-size: 16px; - list-style: none; - line-height: 1.333em; - vertical-align: top; -} -@media (max-width: 900px) { - footer .tsd-legend { - width: 50%; - } -} - -.tsd-hierarchy { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-hierarchy .target { - font-weight: bold; -} - -.tsd-index-panel .tsd-index-content { - margin-bottom: -30px !important; -} -.tsd-index-panel .tsd-index-section { - margin-bottom: 30px !important; -} -.tsd-index-panel h3 { - margin: 0 -20px 10px -20px; - padding: 0 20px 10px 20px; - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 3; - -moz-column-count: 3; - -ms-column-count: 3; - -o-column-count: 3; - column-count: 3; - -webkit-column-gap: 20px; - -moz-column-gap: 20px; - -ms-column-gap: 20px; - -o-column-gap: 20px; - column-gap: 20px; - padding: 0; - list-style: none; - line-height: 1.333em; -} -@media (max-width: 900px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 1; - -moz-column-count: 1; - -ms-column-count: 1; - -o-column-count: 1; - column-count: 1; - } -} -@media (min-width: 901px) and (max-width: 1024px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; - } -} -.tsd-index-panel ul.tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; -} -.tsd-index-panel a, -.tsd-index-panel .tsd-parent-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-parent-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-parent-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-parent-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-is-private a { - color: var(--color-ts-private); -} - -.tsd-flag { - display: inline-block; - padding: 1px 5px; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 14px; - font-weight: normal; -} - -.tsd-anchor { - position: absolute; - top: -100px; -} - -.tsd-member { - position: relative; -} -.tsd-member .tsd-anchor + h3 { - margin-top: 0; - margin-bottom: 0; - border-bottom: none; -} -.tsd-member [data-tsd-kind] { - color: var(--color-ts); -} -.tsd-member [data-tsd-kind="Interface"] { - color: var(--color-ts-interface); -} -.tsd-member [data-tsd-kind="Enum"] { - color: var(--color-ts-enum); -} -.tsd-member [data-tsd-kind="Class"] { - color: var(--color-ts-class); -} -.tsd-member [data-tsd-kind="Private"] { - color: var(--color-ts-private); -} - -.tsd-navigation { - margin: 0 0 0 40px; -} -.tsd-navigation a { - display: block; - padding-top: 2px; - padding-bottom: 2px; - border-left: 2px solid transparent; - color: var(--color-text); - text-decoration: none; - transition: border-left-color 0.1s; -} -.tsd-navigation a:hover { - text-decoration: underline; -} -.tsd-navigation ul { - margin: 0; - padding: 0; - list-style: none; -} -.tsd-navigation li { - padding: 0; -} - -.tsd-navigation.primary { - padding-bottom: 40px; -} -.tsd-navigation.primary a { - display: block; - padding-top: 6px; - padding-bottom: 6px; -} -.tsd-navigation.primary ul li a { - padding-left: 5px; -} -.tsd-navigation.primary ul li li a { - padding-left: 25px; -} -.tsd-navigation.primary ul li li li a { - padding-left: 45px; -} -.tsd-navigation.primary ul li li li li a { - padding-left: 65px; -} -.tsd-navigation.primary ul li li li li li a { - padding-left: 85px; -} -.tsd-navigation.primary ul li li li li li li a { - padding-left: 105px; -} -.tsd-navigation.primary > ul { - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-navigation.primary li { - border-top: 1px solid var(--color-panel-divider); -} -.tsd-navigation.primary li.current > a { - font-weight: bold; -} -.tsd-navigation.primary li.label span { - display: block; - padding: 20px 0 6px 5px; - color: var(--color-menu-label); -} -.tsd-navigation.primary li.globals + li > span, -.tsd-navigation.primary li.globals + li > a { - padding-top: 20px; -} - -.tsd-navigation.secondary { - max-height: calc(100vh - 1rem - 40px); - overflow: auto; - position: sticky; - top: calc(0.5rem + 40px); - transition: 0.3s; -} -.tsd-navigation.secondary.tsd-navigation--toolbar-hide { - max-height: calc(100vh - 1rem); - top: 0.5rem; -} -.tsd-navigation.secondary ul { - transition: opacity 0.2s; -} -.tsd-navigation.secondary ul li a { - padding-left: 25px; -} -.tsd-navigation.secondary ul li li a { - padding-left: 45px; -} -.tsd-navigation.secondary ul li li li a { - padding-left: 65px; -} -.tsd-navigation.secondary ul li li li li a { - padding-left: 85px; -} -.tsd-navigation.secondary ul li li li li li a { - padding-left: 105px; -} -.tsd-navigation.secondary ul li li li li li li a { - padding-left: 125px; -} -.tsd-navigation.secondary ul.current a { - border-left-color: var(--color-panel-divider); -} -.tsd-navigation.secondary li.focus > a, -.tsd-navigation.secondary ul.current li.focus > a { - border-left-color: var(--color-menu-divider-focus); -} -.tsd-navigation.secondary li.current { - margin-top: 20px; - margin-bottom: 20px; - border-left-color: var(--color-panel-divider); -} -.tsd-navigation.secondary li.current > a { - font-weight: bold; -} - -@media (min-width: 901px) { - .menu-sticky-wrap { - position: static; - } -} - -.tsd-panel { - margin: 20px 0; - padding: 20px; - background-color: var(--color-panel); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -.tsd-panel:empty { - display: none; -} -.tsd-panel > h1, -.tsd-panel > h2, -.tsd-panel > h3 { - margin: 1.5em -20px 10px -20px; - padding: 0 20px 10px 20px; - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-panel > h1.tsd-before-signature, -.tsd-panel > h2.tsd-before-signature, -.tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: 0; -} -.tsd-panel table { - display: block; - width: 100%; - overflow: auto; - margin-top: 10px; - word-break: normal; - word-break: keep-all; - border-collapse: collapse; -} -.tsd-panel table th { - font-weight: bold; -} -.tsd-panel table th, -.tsd-panel table td { - padding: 6px 13px; - border: 1px solid var(--color-panel-divider); -} -.tsd-panel table tr { - background: var(--color-background); -} -.tsd-panel table tr:nth-child(even) { - background: var(--color-secondary-background); -} - -.tsd-panel-group { - margin: 60px 0; -} -.tsd-panel-group > h1, -.tsd-panel-group > h2, -.tsd-panel-group > h3 { - padding-left: 20px; - padding-right: 20px; -} - -#tsd-search { - transition: background-color 0.2s; -} -#tsd-search .title { - position: relative; - z-index: 2; -} -#tsd-search .field { - position: absolute; - left: 0; - top: 0; - right: 40px; - height: 40px; -} -#tsd-search .field input { - box-sizing: border-box; - position: relative; - top: -50px; - z-index: 1; - width: 100%; - padding: 0 10px; - opacity: 0; - outline: 0; - border: 0; - background: transparent; - color: var(--color-text); -} -#tsd-search .field label { - position: absolute; - overflow: hidden; - right: -40px; -} -#tsd-search .field input, -#tsd-search .title { - transition: opacity 0.2s; -} -#tsd-search .results { - position: absolute; - visibility: hidden; - top: 40px; - width: 100%; - margin: 0; - padding: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -#tsd-search .results li { - padding: 0 10px; - background-color: var(--color-background); -} -#tsd-search .results li:nth-child(even) { - background-color: var(--color-panel); -} -#tsd-search .results li.state { - display: none; -} -#tsd-search .results li.current, -#tsd-search .results li:hover { - background-color: var(--color-panel-divider); -} -#tsd-search .results a { - display: block; -} -#tsd-search .results a:before { - top: 10px; -} -#tsd-search .results span.parent { - color: var(--color-text-aside); - font-weight: normal; -} -#tsd-search.has-focus { - background-color: var(--color-panel-divider); -} -#tsd-search.has-focus .field input { - top: 0; - opacity: 1; -} -#tsd-search.has-focus .title { - z-index: 0; - opacity: 0; -} -#tsd-search.has-focus .results { - visibility: visible; -} -#tsd-search.loading .results li.state.loading { - display: block; -} -#tsd-search.failure .results li.state.failure { - display: block; -} - -.tsd-signature { - margin: 0 0 1em 0; - padding: 10px; - border: 1px solid var(--color-panel-divider); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; -} -.tsd-signature.tsd-kind-icon { - padding-left: 30px; -} -.tsd-signature.tsd-kind-icon:before { - top: 10px; - left: 10px; -} -.tsd-panel > .tsd-signature { - margin-left: -20px; - margin-right: -20px; - border-width: 1px 0; -} -.tsd-panel > .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signature.tsd-kind-icon:before { - left: 20px; -} - -.tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; -} - -.tsd-signature-type { - font-style: italic; - font-weight: normal; -} - -.tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - border: 1px solid var(--color-panel-divider); -} -.tsd-signatures .tsd-signature { - margin: 0; - border-width: 1px 0 0 0; - transition: background-color 0.1s; -} -.tsd-signatures .tsd-signature:first-child { - border-top-width: 0; -} -.tsd-signatures .tsd-signature.current { - background-color: var(--color-panel-divider); -} -.tsd-signatures.active > .tsd-signature { - cursor: pointer; -} -.tsd-panel > .tsd-signatures { - margin-left: -20px; - margin-right: -20px; - border-width: 1px 0; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon:before { - left: 20px; -} -.tsd-panel > a.anchor + .tsd-signatures { - border-top-width: 0; - margin-top: -20px; -} - -ul.tsd-descriptions { - position: relative; - overflow: hidden; - padding: 0; - list-style: none; -} -ul.tsd-descriptions.active > .tsd-description { - display: none; -} -ul.tsd-descriptions.active > .tsd-description.current { - display: block; -} -ul.tsd-descriptions.active > .tsd-description.fade-in { - animation: fade-in-delayed 0.3s; -} -ul.tsd-descriptions.active > .tsd-description.fade-out { - animation: fade-out-delayed 0.3s; - position: absolute; - display: block; - top: 0; - left: 0; - right: 0; - opacity: 0; - visibility: hidden; -} -ul.tsd-descriptions h4, -ul.tsd-descriptions .tsd-index-panel h3, -.tsd-index-panel ul.tsd-descriptions h3 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} - -ul.tsd-parameters, -ul.tsd-type-parameters { - list-style: square; - margin: 0; - padding-left: 20px; -} -ul.tsd-parameters > li.tsd-parameter-signature, -ul.tsd-type-parameters > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; -} -ul.tsd-parameters h5, -ul.tsd-type-parameters h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} -ul.tsd-parameters .tsd-comment, -ul.tsd-type-parameters .tsd-comment { - margin-top: -0.5em; -} - -.tsd-sources { - font-size: 14px; - color: var(--color-text-aside); - margin: 0 0 1em 0; -} -.tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; -} -.tsd-sources ul, -.tsd-sources p { - margin: 0 !important; -} -.tsd-sources ul { - list-style: none; - padding: 0; -} - -.tsd-page-toolbar { - position: fixed; - z-index: 1; - top: 0; - left: 0; - width: 100%; - height: 40px; - color: var(--color-toolbar-text); - background: var(--color-toolbar); - border-bottom: 1px solid var(--color-panel-divider); - transition: transform 0.3s linear; -} -.tsd-page-toolbar a { - color: var(--color-toolbar-text); - text-decoration: none; -} -.tsd-page-toolbar a.title { - font-weight: bold; -} -.tsd-page-toolbar a.title:hover { - text-decoration: underline; -} -.tsd-page-toolbar .table-wrap { - display: table; - width: 100%; - height: 40px; -} -.tsd-page-toolbar .table-cell { - display: table-cell; - position: relative; - white-space: nowrap; - line-height: 40px; -} -.tsd-page-toolbar .table-cell:first-child { - width: 100%; -} - -.tsd-page-toolbar--hide { - transform: translateY(-100%); -} - -.tsd-select .tsd-select-list li:before, -.tsd-select .tsd-select-label:before, -.tsd-widget:before { - content: ""; - display: inline-block; - width: 40px; - height: 40px; - margin: 0 -8px 0 0; - background-image: url(./widgets.png); - background-repeat: no-repeat; - text-indent: -1024px; - vertical-align: bottom; - filter: var(--icon-filter); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-select .tsd-select-list li:before, - .tsd-select .tsd-select-label:before, - .tsd-widget:before { - background-image: url(./widgets@2x.png); - background-size: 320px 40px; - } -} - -.tsd-widget { - display: inline-block; - overflow: hidden; - opacity: 0.8; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-widget:hover { - opacity: 0.9; -} -.tsd-widget.active { - opacity: 1; - background-color: var(--color-panel-divider); -} -.tsd-widget.no-caption { - width: 40px; -} -.tsd-widget.no-caption:before { - margin: 0; -} -.tsd-widget.search:before { - background-position: 0 0; -} -.tsd-widget.menu:before { - background-position: -40px 0; -} -.tsd-widget.options:before { - background-position: -80px 0; -} -.tsd-widget.options, -.tsd-widget.menu { - display: none; -} -@media (max-width: 900px) { - .tsd-widget.options, - .tsd-widget.menu { - display: inline-block; - } -} -input[type="checkbox"] + .tsd-widget:before { - background-position: -120px 0; -} -input[type="checkbox"]:checked + .tsd-widget:before { - background-position: -160px 0; -} - -.tsd-select { - position: relative; - display: inline-block; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-select .tsd-select-label { - opacity: 0.6; - transition: opacity 0.2s; -} -.tsd-select .tsd-select-label:before { - background-position: -240px 0; -} -.tsd-select.active .tsd-select-label { - opacity: 0.8; -} -.tsd-select.active .tsd-select-list { - visibility: visible; - opacity: 1; - transition-delay: 0s; -} -.tsd-select .tsd-select-list { - position: absolute; - visibility: hidden; - top: 40px; - left: 0; - margin: 0; - padding: 0; - opacity: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); - transition: visibility 0s 0.2s, opacity 0.2s; -} -.tsd-select .tsd-select-list li { - padding: 0 20px 0 0; - background-color: var(--color-background); -} -.tsd-select .tsd-select-list li:before { - background-position: 40px 0; -} -.tsd-select .tsd-select-list li:nth-child(even) { - background-color: var(--color-panel); -} -.tsd-select .tsd-select-list li:hover { - background-color: var(--color-panel-divider); -} -.tsd-select .tsd-select-list li.selected:before { - background-position: -200px 0; -} -@media (max-width: 900px) { - .tsd-select .tsd-select-list { - top: 0; - left: auto; - right: 100%; - margin-right: -5px; - } - .tsd-select .tsd-select-label:before { - background-position: -280px 0; - } -} - -img { - max-width: 100%; -} - -.tsd-anchor-icon { - margin-left: 10px; - vertical-align: middle; - color: var(--color-text); -} - -.tsd-anchor-icon svg { - width: 1em; - height: 1em; - visibility: hidden; -} - -.tsd-anchor-link:hover > .tsd-anchor-icon svg { - visibility: visible; -} diff --git a/docs/assets/widgets.png b/docs/assets/widgets.png deleted file mode 100644 index c738053..0000000 Binary files a/docs/assets/widgets.png and /dev/null differ diff --git a/docs/assets/widgets@2x.png b/docs/assets/widgets@2x.png deleted file mode 100644 index 4bbbd57..0000000 Binary files a/docs/assets/widgets@2x.png and /dev/null differ diff --git a/docs/classes/SAS9ApiClient.SAS9ApiClient-1.html b/docs/classes/SAS9ApiClient.SAS9ApiClient-1.html deleted file mode 100644 index 46d1cc2..0000000 --- a/docs/classes/SAS9ApiClient.SAS9ApiClient-1.html +++ /dev/null @@ -1,17 +0,0 @@ -SAS9ApiClient | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu
-

A client for interfacing with the SAS9 REST API.

-

Hierarchy

  • SAS9ApiClient

Index

Constructors

  • new SAS9ApiClient(serverUrl: string, jobsPath: string, httpsAgentOptions?: AgentOptions): SAS9ApiClient

Methods

  • executeScript(linesOfCode: string[], userName: string, password: string): Promise<string>
  • -

    Executes code on a SAS9 server.

    -

    Parameters

    • linesOfCode: string[]
      -

      an array of code lines to execute.

      -
    • userName: string
      -

      the user name to log into the current SAS server.

      -
    • password: string
      -

      the password to log into the current SAS server.

      -

    Returns Promise<string>

  • getConfig(): { serverUrl: string }
  • -

    Returns an object containing server URL.

    -

    Returns { serverUrl: string }

    • serverUrl: string
  • setConfig(serverUrl: string): void
  • -

    Updates server URL which is not null.

    -

    Parameters

    • serverUrl: string
      -

      URL of the server to be set.

      -

    Returns void

Legend

  • Class
  • Constructor
  • Method
  • Function
  • Enumeration
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/SASViyaApiClient.SASViyaApiClient-1.html b/docs/classes/SASViyaApiClient.SASViyaApiClient-1.html deleted file mode 100644 index b196501..0000000 --- a/docs/classes/SASViyaApiClient.SASViyaApiClient-1.html +++ /dev/null @@ -1,239 +0,0 @@ -SASViyaApiClient | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu
-

A client for interfacing with the SAS Viya REST API.

-

Hierarchy

  • SASViyaApiClient

Index

Constructors

  • new SASViyaApiClient(serverUrl: string, rootFolderName: string, contextName: string, requestClient: RequestClient): SASViyaApiClient

Accessors

  • get debug(): boolean
  • set debug(value: boolean): void

Methods

  • appendRequest(response: any, program: string, debug: boolean): void
  • -

    A helper method used to call appendRequest method of RequestClient

    -

    Parameters

    • response: any
      -

      response from sasjs request

      -
    • program: string
      -

      name of program

      -
    • debug: boolean
      -

      a boolean that indicates whether debug was enabled or not

      -

    Returns void

  • createComputeContext(contextName: string, launchContextName: string, sharedAccountId: string, autoExecLines: string[], accessToken?: string, authorizedUsers?: string[]): Promise<Context>
  • -

    Creates a compute context on the given server.

    -

    Parameters

    • contextName: string
      -

      the name of the context to be created.

      -
    • launchContextName: string
      -

      the name of the launcher context used by the compute service.

      -
    • sharedAccountId: string
      -

      the ID of the account to run the servers for this context.

      -
    • autoExecLines: string[]
      -

      the lines of code to execute during session initialization.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -
    • Optional authorizedUsers: string[]
      -

      an optional list of authorized user IDs.

      -

    Returns Promise<Context>

  • createFile(fileName: string, contentBuffer: Buffer, parentFolderPath?: string, parentFolderUri?: string, accessToken?: string): Promise<File>
  • -

    Creates a file. Path to or URI of the parent folder is required.

    -

    Parameters

    • fileName: string
      -

      the name of the new file.

      -
    • contentBuffer: Buffer
      -

      the content of the new file in Buffer.

      -
    • Optional parentFolderPath: string
      -

      the full path to the parent folder. If not - provided, the parentFolderUri must be provided.

      -
    • Optional parentFolderUri: string
      -

      the URI (eg /folders/folders/UUID) of the parent - folder. If not provided, the parentFolderPath must be provided.

      -
    • Optional accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<File>

  • createFolder(folderName: string, parentFolderPath?: string, parentFolderUri?: string, accessToken?: string, isForced?: boolean): Promise<Folder>
  • -

    Creates a folder. Path to or URI of the parent folder is required.

    -

    Parameters

    • folderName: string
      -

      the name of the new folder.

      -
    • Optional parentFolderPath: string
      -

      the full path to the parent folder. If not - provided, the parentFolderUri must be provided.

      -
    • Optional parentFolderUri: string
      -

      the URI (eg /folders/folders/UUID) of the parent - folder. If not provided, the parentFolderPath must be provided.

      -
    • Optional accessToken: string
      -

      an access token for authorizing the request.

      -
    • Optional isForced: boolean
      -

      flag that indicates if target folder already exists, it and all subfolders have to be deleted.

      -

    Returns Promise<Folder>

  • createJobDefinition(jobName: string, code: string, parentFolderPath?: string, parentFolderUri?: string, accessToken?: string): Promise<{ etag: string; result: Job }>
  • -

    Creates a Job in the specified folder (or folder uri).

    -

    Parameters

    • jobName: string
      -

      the name of the new job to be created.

      -
    • code: string
      -

      the SAS code for the new job.

      -
    • Optional parentFolderPath: string
      -

      the location of the new job.

      -
    • Optional parentFolderUri: string
      -

      the URI location of the new job. The function is a -little faster if the folder URI is supplied instead of the path.

      -
    • Optional accessToken: string

    Returns Promise<{ etag: string; result: Job }>

  • createLauncherContext(contextName: string, description: string, launchType?: string, accessToken?: string): Promise<Context>
  • -

    Creates a launcher context on the given server.

    -

    Parameters

    • contextName: string
      -

      the name of the context to be created.

      -
    • description: string
      -

      the description of the context to be created.

      -
    • launchType: string = 'direct'
      -

      launch type of the context to be created.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<Context>

  • createSession(contextName: string, accessToken?: string): Promise<Session>
  • -

    Creates a session on the given context.

    -

    Parameters

    • contextName: string
      -

      the name of the context to create a session on.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<Session>

  • deleteClient(clientId: string, accessToken?: string): Promise<unknown>
  • -

    Deletes the client representing the supplied ID.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -
    • Optional accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<unknown>

  • deleteComputeContext(contextName: string, accessToken?: string): Promise<{ etag: string; result: Context }>
  • -

    Deletes a compute context on the given server.

    -

    Parameters

    • contextName: string
      -

      the name of the context to be deleted.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ etag: string; result: Context }>

  • deleteFolder(folderPath: string, accessToken: string): Promise<undefined | Folder>
  • -

    For performance (and in case of accidental error) the deleteFolder function does not actually delete the folder (and all its content and subfolder content). Instead the folder is simply moved to the recycle bin. Deletion time will be added to the folder name.

    -

    Parameters

    • folderPath: string
      -

      the full path (eg /Public/example/deleteThis) of the folder to be deleted.

      -
    • accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<undefined | Folder>

  • editComputeContext(contextName: string, editedContext: EditContextInput, accessToken?: string): Promise<{ etag: string; result: Context }>
  • -

    Updates a compute context on the given server.

    -

    Parameters

    • contextName: string
      -

      the original name of the context to be updated.

      -
    • editedContext: EditContextInput
      -

      an object with the properties to be updated.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ etag: string; result: Context }>

  • executeComputeJob(sasJob: string, contextName: string, debug?: boolean, data?: any, authConfig?: AuthConfig, waitForResult?: boolean, expectWebout?: boolean, pollOptions?: PollOptions, printPid?: boolean, variables?: MacroVar): Promise<any>
  • -

    Executes a job via the SAS Viya Compute API.

    -

    Parameters

    • sasJob: string
      -

      the relative path to the job.

      -
    • contextName: string
      -

      the name of the context where the job is to be executed.

      -
    • Optional debug: boolean
      -

      sets the _debug flag in the job arguments.

      -
    • Optional data: any
      -

      any data to be passed in as input to the job.

      -
    • Optional authConfig: AuthConfig
    • waitForResult: boolean = true
      -

      a boolean indicating if the function should wait for a result.

      -
    • expectWebout: boolean = false
      -

      a boolean indicating whether to expect a _webout response.

      -
    • Optional pollOptions: PollOptions
      -

      an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.

      -
    • printPid: boolean = false
      -

      a boolean that indicates whether the function should print (PID) of the started job.

      -
    • Optional variables: MacroVar
      -

      an object that represents macro variables.

      -

    Returns Promise<any>

  • executeJob(sasJob: string, contextName: string, debug: boolean, data?: any, authConfig?: AuthConfig): Promise<{ log: any; result: any }>
  • -

    Executes a job via the SAS Viya Job Execution API

    -

    Parameters

    • sasJob: string
      -

      the relative or absolute path to the job.

      -
    • contextName: string
      -

      the name of the context where the job is to be executed.

      -
    • debug: boolean
      -

      sets the _debug flag in the job arguments.

      -
    • Optional data: any
      -

      any data to be passed in as input to the job.

      -
    • Optional authConfig: AuthConfig

    Returns Promise<{ log: any; result: any }>

  • executeScript(jobPath: string, linesOfCode: string[], contextName: string, authConfig?: AuthConfig, data?: null, debug?: boolean, expectWebout?: boolean, waitForResult?: boolean, pollOptions?: PollOptions, printPid?: boolean, variables?: MacroVar): Promise<any>
  • -

    Executes code on the current SAS Viya server.

    -

    Parameters

    • jobPath: string
      -

      the path to the file being submitted for execution.

      -
    • linesOfCode: string[]
      -

      an array of code lines to execute.

      -
    • contextName: string
      -

      the context to execute the code in.

      -
    • Optional authConfig: AuthConfig
      -

      an object containing an access token, refresh token, client ID and secret.

      -
    • data: null = null
      -

      execution data.

      -
    • debug: boolean = false
      -

      when set to true, the log will be returned.

      -
    • expectWebout: boolean = false
      -

      when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code).

      -
    • waitForResult: boolean = true
      -

      when set to true, function will return the session

      -
    • Optional pollOptions: PollOptions
      -

      an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.

      -
    • printPid: boolean = false
      -

      a boolean that indicates whether the function should print (PID) of the started job.

      -
    • Optional variables: MacroVar
      -

      an object that represents macro variables.

      -

    Returns Promise<any>

  • getAccessToken(clientId: string, clientSecret: string, authCode: string): Promise<SasAuthResponse>
  • -

    Exchanges the auth code for an access token for the given client.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -
    • clientSecret: string
      -

      the client secret to authenticate with.

      -
    • authCode: string
      -

      the auth code received from the server.

      -

    Returns Promise<SasAuthResponse>

  • getAuthCode(clientId: string): Promise<null | string>
  • -

    Performs a login redirect and returns an auth code for the given client.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -

    Returns Promise<null | string>

  • -

    Returns a JSON representation of a compute context.

    -

    Parameters

    • contextId: string
      -

      an id of the context to return.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<ContextAllAttributes>

  • getComputeContextByName(contextName: string, accessToken?: string): Promise<Context>
  • -

    Returns a JSON representation of a compute context.

    -
    example:

    { "createdBy": "admin", "links": [...], "id": "ID", "version": 2, "name": "context1" }

    -

    Parameters

    • contextName: string
      -

      the name of the context to return.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<Context>

  • getComputeContexts(accessToken?: string): Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>
  • -

    Returns all available compute contexts on this server.

    -

    Parameters

    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>

  • getConfig(): { rootFolderName: string; serverUrl: string }
  • -

    Returns an object containing the server URL and root folder name.

    -

    Returns { rootFolderName: string; serverUrl: string }

    • rootFolderName: string
    • serverUrl: string
  • getDefaultComputeContexts(): string[]
  • getExecutableContexts(authConfig?: AuthConfig): Promise<any[]>
  • -

    Returns all compute contexts on this server that the user has access to.

    -

    Parameters

    • Optional authConfig: AuthConfig
      -

      an access token, refresh token, client and secret for an authorized user.

      -

    Returns Promise<any[]>

  • getFolder(folderPath: string, accessToken?: string): Promise<unknown>
  • -

    Fetches a folder. Path to the folder is required.

    -

    Parameters

    • folderPath: string
      -

      the absolute path to the folder.

      -
    • Optional accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<unknown>

  • getJobsInFolder(folderPath: string): Promise<undefined | Job[]>
  • -

    Returns a list of jobs in the currently set root folder.

    -

    Parameters

    • folderPath: string

    Returns Promise<undefined | Job[]>

  • getLauncherContexts(accessToken?: string): Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>
  • -

    Returns all available launcher contexts on this server.

    -

    Parameters

    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>

  • listFolder(sourceFolder: string, accessToken?: string, limit?: number): Promise<any[]>
  • -

    Lists children folders for given Viya folder.

    -

    Parameters

    • sourceFolder: string
      -

      the full path (eg /Public/example/myFolder) or URI of the source folder listed. Providing URI instead of path will save one extra request.

      -
    • Optional accessToken: string
      -

      an access token for authorizing the request.

      -
    • limit: number = 20

    Returns Promise<any[]>

  • moveFolder(sourceFolder: string, targetParentFolder: string, targetFolderName: string, accessToken: string): Promise<undefined | Folder>
  • -

    Moves Viya folder to a new location. The folder may be renamed at the same time.

    -

    Parameters

    • sourceFolder: string
      -

      the full path (eg /Public/example/myFolder) or URI of the source folder to be moved. Providing URI instead of path will save one extra request.

      -
    • targetParentFolder: string
      -

      the full path or URI of the parent folder to which the sourceFolder will be moved (eg /Public/newDestination). To move a folder, a user has to have write permissions in targetParentFolder. Providing URI instead of the path will save one extra request.

      -
    • targetFolderName: string
      -

      the name of the "moved" folder. If left blank, the original folder name will be used (eg myFolder in /Public/newDestination/myFolder for the example above). Optional field.

      -
    • accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<undefined | Folder>

  • refreshTokens(clientId: string, clientSecret: string, refreshToken: string): Promise<SasAuthResponse>
  • -

    Exchanges the refresh token for an access token for the given client.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -
    • clientSecret: string
      -

      the client secret to authenticate with.

      -
    • refreshToken: string
      -

      the refresh token received from the server.

      -

    Returns Promise<SasAuthResponse>

  • setConfig(serverUrl: string, rootFolderName: string): void
  • -

    Updates server URL and root folder name, if it was not set.

    -

    Parameters

    • serverUrl: string
      -

      the URL of the server.

      -
    • rootFolderName: string
      -

      the name for root folder.

      -

    Returns void

Legend

  • Class
  • Constructor
  • Method
  • Accessor
  • Function
  • Enumeration
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/SASjs.SASjs-1.html b/docs/classes/SASjs.SASjs-1.html deleted file mode 100644 index 430d163..0000000 --- a/docs/classes/SASjs.SASjs-1.html +++ /dev/null @@ -1,292 +0,0 @@ -SASjs | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu
-

SASjs is a JavaScript adapter for SAS.

-

Hierarchy

  • SASjs

Index

Constructors

Methods

  • checkSession(): Promise<{ isLoggedIn: boolean; loginForm?: any; userName: string }>
  • -

    Checks whether a session is active, or login is required.

    -

    Returns Promise<{ isLoggedIn: boolean; loginForm?: any; userName: string }>

      -
    • a promise which resolves with an object containing two values - a boolean isLoggedIn, and a string userName.
    • -
    -
  • clearSasRequests(): void
  • createComputeContext(contextName: string, launchContextName: string, sharedAccountId: string, autoExecLines: string[], accessToken: string, authorizedUsers?: string[]): Promise<Context>
  • -

    Creates a compute context on the given server.

    -

    Parameters

    • contextName: string
      -

      the name of the context to be created.

      -
    • launchContextName: string
      -

      the name of the launcher context used by the compute service.

      -
    • sharedAccountId: string
      -

      the ID of the account to run the servers for this context as.

      -
    • autoExecLines: string[]
      -

      the lines of code to execute during session initialization.

      -
    • accessToken: string
      -

      an access token for an authorized user.

      -
    • Optional authorizedUsers: string[]
      -

      an optional list of authorized user IDs.

      -

    Returns Promise<Context>

  • createFile(fileName: string, content: Buffer, parentFolderPath: string, parentFolderUri?: string, accessToken?: string, sasApiClient?: SASViyaApiClient): Promise<File>
  • -

    Creates a file in the logical SAS folder tree

    -

    Parameters

    • fileName: string
      -

      name of the file to be created.

      -
    • content: Buffer
      -

      content of the file to be created.

      -
    • parentFolderPath: string
      -

      the full path (eg /Public/example/myFolder) of the parent folder.

      -
    • Optional parentFolderUri: string
      -

      the URI of the parent folder.

      -
    • Optional accessToken: string
      -

      the access token to authorizing the request.

      -
    • Optional sasApiClient: SASViyaApiClient
      -

      a client for interfacing with SAS API.

      -

    Returns Promise<File>

  • createFolder(folderName: string, parentFolderPath: string, parentFolderUri?: string, accessToken?: string, sasApiClient?: SASViyaApiClient, isForced?: boolean): Promise<Folder>
  • -

    Creates a folder in the logical SAS folder tree

    -

    Parameters

    • folderName: string
      -

      name of the folder to be created.

      -
    • parentFolderPath: string
      -

      the full path (eg /Public/example/myFolder) of the parent folder.

      -
    • Optional parentFolderUri: string
      -

      the URI of the parent folder.

      -
    • Optional accessToken: string
      -

      the access token to authorizing the request.

      -
    • Optional sasApiClient: SASViyaApiClient
      -

      a client for interfacing with SAS API.

      -
    • Optional isForced: boolean
      -

      flag that indicates if target folder already exists, it and all subfolders have to be deleted. Applicable for SAS VIYA only.

      -

    Returns Promise<Folder>

  • createJobDefinition(jobName: string, code: string, parentFolderPath?: string, parentFolderUri?: string, accessToken?: string, sasApiClient?: SASViyaApiClient): Promise<{ etag: string; result: Job }>
  • Parameters

    • jobName: string
    • code: string
    • Optional parentFolderPath: string
    • Optional parentFolderUri: string
    • Optional accessToken: string
    • Optional sasApiClient: SASViyaApiClient

    Returns Promise<{ etag: string; result: Job }>

  • createLauncherContext(contextName: string, description: string, launchType: string, accessToken: string): Promise<Context>
  • -

    Creates a launcher context on the given server.

    -

    Parameters

    • contextName: string
      -

      the name of the context to be created.

      -
    • description: string
      -

      the description of the context to be created.

      -
    • launchType: string
      -

      launch type of the context to be created.

      -
    • accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<Context>

  • createSession(contextName: string, accessToken: string): Promise<Session>
  • Parameters

    • contextName: string
    • accessToken: string

    Returns Promise<Session>

  • deleteClient(clientId: string, accessToken: string): Promise<unknown>
  • Parameters

    • clientId: string
    • accessToken: string

    Returns Promise<unknown>

  • deleteComputeContext(contextName: string, accessToken?: string): Promise<{ etag: string; result: Context }>
  • -

    Deletes a compute context on the given server.

    -

    Parameters

    • contextName: string
      -

      the name of the context to be deleted.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ etag: string; result: Context }>

  • deleteFolder(folderPath: string, accessToken: string): Promise<undefined | Folder>
  • -

    For performance (and in case of accidental error) the deleteFolder function does not actually delete the folder (and all its content and subfolder content). Instead the folder is simply moved to the recycle bin. Deletion time will be added to the folder name.

    -

    Parameters

    • folderPath: string
      -

      the full path (eg /Public/example/deleteThis) of the folder to be deleted.

      -
    • accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<undefined | Folder>

  • deployServicePack(serviceJson: any, appLoc?: string, serverUrl?: string, accessToken?: string, isForced?: boolean): Promise<void>
  • -

    Creates the folders and services at the given location appLoc on the given server serverUrl.

    -

    Parameters

    • serviceJson: any
      -

      the JSON specifying the folders and services to be created.

      -
    • Optional appLoc: string
      -

      the base folder in which to create the new folders and -services. If not provided, is taken from SASjsConfig.

      -
    • Optional serverUrl: string
      -

      the server on which to deploy the folders and services. -If not provided, is taken from SASjsConfig.

      -
    • Optional accessToken: string
      -

      an optional access token to be passed in when -using this function from the command line.

      -
    • isForced: boolean = false
      -

      flag that indicates if target folder already exists, it and all subfolders have to be deleted.

      -

    Returns Promise<void>

  • deployToSASjs(members: FileTree, appLoc?: string, authConfig?: AuthConfig): Promise<undefined | { example?: {}; message: string; status: string }>
  • -

    Creates the folders and services at the given location appLoc on the given server serverUrl.

    -

    Parameters

    • members: FileTree
      -

      the JSON specifying the folders and services to be created.

      -
    • Optional appLoc: string
      -

      the base folder in which to create the new folders and -services. If not provided, is taken from SASjsConfig.

      -
    • Optional authConfig: AuthConfig
      -

      a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.

      -

    Returns Promise<undefined | { example?: {}; message: string; status: string }>

  • editComputeContext(contextName: string, editedContext: EditContextInput, accessToken?: string): Promise<{ etag: string; result: Context }>
  • -

    Updates a compute context on the given server.

    -

    Parameters

    • contextName: string
      -

      the original name of the context to be deleted.

      -
    • editedContext: EditContextInput
      -

      an object with the properties to be updated.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ etag: string; result: Context }>

  • executeJobSASjs(query: ExecutionQuery): Promise<undefined | { _webout?: string; error?: {}; log?: string; logPath?: string; message: string; status: string }>
  • Parameters

    Returns Promise<undefined | { _webout?: string; error?: {}; log?: string; logPath?: string; message: string; status: string }>

  • executeScriptSAS9(linesOfCode: string[], userName: string, password: string): Promise<undefined | string>
  • -

    Executes code against a SAS 9 server. Requires a runner to be present in -the users home directory in metadata.

    -

    Parameters

    • linesOfCode: string[]
      -

      lines of sas code from the file to run.

      -
    • userName: string
    • password: string
      -

      a string representing the password.

      -

    Returns Promise<undefined | string>

  • executeScriptSASViya(fileName: string, linesOfCode: string[], contextName: string, authConfig?: AuthConfig, debug?: boolean): Promise<any>
  • -

    Executes sas code in a SAS Viya compute session.

    -

    Parameters

    • fileName: string
      -

      name of the file to run. It will be converted to path to the file being submitted for execution.

      -
    • linesOfCode: string[]
      -

      lines of sas code from the file to run.

      -
    • contextName: string
      -

      context name on which code will be run on the server.

      -
    • Optional authConfig: AuthConfig
      -

      (optional) the access token, refresh token, client and secret for authorizing the request.

      -
    • Optional debug: boolean
      -

      (optional) if true, global debug config will be overriden

      -

    Returns Promise<any>

  • fetchLogFileContent(logUrl: string, accessToken?: string): Promise<string>
  • -

    Fetches content of the log file

    -

    Parameters

    • logUrl: string
      -

      url of the log file.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<string>

  • getAccessToken(clientId: string, clientSecret: string, authCode: string): Promise<SasAuthResponse | SASjsAuthResponse>
  • -

    Exchanges the auth code for an access token for the given client.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -
    • clientSecret: string
      -

      the client secret to authenticate with.

      -
    • authCode: string
      -

      the auth code received from the server.

      -

    Returns Promise<SasAuthResponse | SASjsAuthResponse>

  • getAuthCode(clientId: string): Promise<null | string>
  • Parameters

    • clientId: string

    Returns Promise<null | string>

  • -

    Returns a JSON representation of a compute context.

    -

    Parameters

    • contextId: string
      -

      an id of the context to return.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<ContextAllAttributes>

  • getComputeContextByName(contextName: string, accessToken?: string): Promise<Context>
  • -

    Returns a JSON representation of a compute context.

    -
    example:

    { "createdBy": "admin", "links": [...], "id": "ID", "version": 2, "name": "context1" }

    -

    Parameters

    • contextName: string
      -

      the name of the context to return.

      -
    • Optional accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<Context>

  • getComputeContexts(accessToken: string): Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>
  • -

    Gets compute contexts.

    -

    Parameters

    • accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>

  • getCsrfToken(type?: "general" | "file"): undefined | CsrfToken
  • getDefaultComputeContexts(): string[]
  • -

    Gets default(system) launcher contexts.

    -

    Returns string[]

  • getExecutableContexts(authConfig: AuthConfig): Promise<any[]>
  • -

    Gets executable compute contexts.

    -

    Parameters

    • authConfig: AuthConfig
      -

      an access token, refresh token, client and secret for an authorized user.

      -

    Returns Promise<any[]>

  • getFolder(folderPath: string, accessToken?: string): Promise<unknown>
  • -

    Fetches a folder from the SAS file system.

    -

    Parameters

    • folderPath: string
      -

      path of the folder to be fetched.

      -
    • Optional accessToken: string
      -

      the access token to authorize the request.

      -

    Returns Promise<unknown>

  • getLauncherContexts(accessToken: string): Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>
  • -

    Gets launcher contexts.

    -

    Parameters

    • accessToken: string
      -

      an access token for an authorized user.

      -

    Returns Promise<{ attributes: {}; createdBy: any; id: any; name: any; version: any }[]>

  • getUserName(): string
  • -

    Returns the username of the user currently logged in.

    -

    Returns string

  • listFolder(sourceFolder: string, accessToken?: string, limit?: number): Promise<undefined | any[]>
  • -

    Lists children folders for given Viya folder.

    -

    Parameters

    • sourceFolder: string
      -

      the full path (eg /Public/example/myFolder) or URI of the source folder listed. Providing URI instead of path will save one extra request.

      -
    • Optional accessToken: string
      -

      an access token for authorizing the request.

      -
    • Optional limit: number

    Returns Promise<undefined | any[]>

  • logIn(username?: string, password?: string, clientId?: string, options?: LoginOptions): Promise<LoginResult>
  • -

    Logs into the SAS server with the supplied credentials.

    -

    Parameters

    • Optional username: string
      -

      a string representing the username.

      -
    • Optional password: string
      -

      a string representing the password.

      -
    • Optional clientId: string
      -

      a string representing the client ID.

      -
    • options: LoginOptions = {}

    Returns Promise<LoginResult>

  • logOut(): Promise<boolean | { etag: string; result: unknown }>
  • -

    Logs out of the configured SAS server.

    -

    Returns Promise<boolean | { etag: string; result: unknown }>

  • moveFolder(sourceFolder: string, targetParentFolder: string, targetFolderName: string, accessToken: string): Promise<undefined | Folder>
  • -

    Moves folder to a new location. The folder may be renamed at the same time.

    -

    Parameters

    • sourceFolder: string
      -

      the full path (eg /Public/example/myFolder) or URI of the source folder to be moved. Providing URI instead of path will save one extra request.

      -
    • targetParentFolder: string
      -

      the full path or URI of the parent folder to which the sourceFolder will be moved (eg /Public/newDestination). To move a folder, a user has to have write permissions in targetParentFolder. Providing URI instead of path will save one extra request.

      -
    • targetFolderName: string
      -

      the name of the "moved" folder. If left blank, the original folder name will be used (eg myFolder in /Public/newDestination/myFolder for the example above). Optional field.

      -
    • accessToken: string
      -

      an access token for authorizing the request.

      -

    Returns Promise<undefined | Folder>

  • refreshTokens(clientId: string, clientSecret: string, refreshToken: string): Promise<SasAuthResponse | SASjsAuthResponse>
  • -

    Exchanges the refresh token for an access token for the given client.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -
    • clientSecret: string
      -

      the client secret to authenticate with.

      -
    • refreshToken: string
      -

      the refresh token received from the server.

      -

    Returns Promise<SasAuthResponse | SASjsAuthResponse>

  • request(sasJob: string, data: null | {}, config?: {}, loginRequiredCallback?: () => any, authConfig?: AuthConfig, extraResponseAttributes?: ExtraResponseAttributes[]): Promise<any>
  • -

    Makes a request to program specified in SASjob (could be a Viya Job, a -SAS 9 Stored Process, or a SASjs Server Stored Program). The response -object will always contain table names in lowercase, and column names in -uppercase. Values are returned formatted by default, unformatted -values can be configured as an option in the %webout macro.

    -

    Parameters

    • sasJob: string
      -

      the path to the SAS program (ultimately resolves to - the SAS _program parameter to run a Job Definition or SAS 9 Stored - Process). Is prepended at runtime with the value of appLoc.

      -
    • data: null | {}
      -

      a JSON object containing one or more tables to be sent to -SAS. For an example of the table structure, see the project README. This -value can be null if no inputs are required.

      -
    • config: {} = {}
      -

      provide any changes to the config here, for instance to -enable/disable debug. Any change provided will override the global config, -for that particular function call.

      -
      • [key: string]: any
    • Optional loginRequiredCallback: () => any
      -

      a function that is called if the -user is not logged in (eg to display a login form). The request will be -resubmitted after successful login. -When using a loginRequiredCallback, the call to the request will look, for example, like so: -await request(sasJobPath, data, config, () => setIsLoggedIn(false)) -If you are not passing in any data and configuration, it will look like so: -await request(sasJobPath, {}, {}, () => setIsLoggedIn(false))

      -
        • (): any
        • Returns any

    • Optional authConfig: AuthConfig
    • extraResponseAttributes: ExtraResponseAttributes[] = []
      -

      a array of predefined values that are used -to provide extra attributes (same names as those values) to be added in response -Supported values are declared in ExtraResponseAttributes type.

      -

    Returns Promise<any>

  • setDebugState(value: boolean): void
  • -

    Sets the debug state. Turning this on will enable additional logging in the adapter.

    -

    Parameters

    • value: boolean
      -

      boolean indicating debug state (on/off).

      -

    Returns void

  • -

    Sets the SASjs configuration.

    -

    Parameters

    Returns Promise<void>

  • startComputeJob(sasJob: string, data: any, config?: any, authConfig?: AuthConfig, waitForResult?: boolean, pollOptions?: PollOptions, printPid?: boolean, variables?: MacroVar): Promise<any>
  • -

    Kicks off execution of the given job via the compute API.

    -

    Parameters

    • sasJob: string
      -

      the path to the SAS program (ultimately resolves to - the SAS _program parameter to run a Job Definition or SAS 9 Stored - Process). Is prepended at runtime with the value of appLoc.

      -
    • data: any
      -

      a JSON object containing one or more tables to be sent to -SAS. Can be null if no inputs required.

      -
    • config: any = {}
      -

      provide any changes to the config here, for instance to -enable/disable debug. Any change provided will override the global config, -for that particular function call.

      -
    • Optional authConfig: AuthConfig
      -

      a valid client, secret, refresh and access tokens that are authorised to execute compute jobs. -The access token is not required when the user is authenticated via the browser.

      -
    • Optional waitForResult: boolean
      -

      a boolean that indicates whether the function needs to wait for execution to complete.

      -
    • Optional pollOptions: PollOptions
      -

      an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.

      -
    • printPid: boolean = false
      -

      a boolean that indicates whether the function should print (PID) of the started job.

      -
    • Optional variables: MacroVar
      -

      an object that represents macro variables.

      -

    Returns Promise<any>

    an object representing the compute session created for the given job.

    -
  • uploadFile(sasJob: string, files: UploadFile[], params: null | {}, config?: {}, loginRequiredCallback?: () => any): Promise<unknown>
  • -

    Uploads a file to the given service.

    -

    Parameters

    • sasJob: string
      -

      the path to the SAS program (ultimately resolves to - the SAS _program parameter to run a Job Definition or SAS 9 Stored - Process). Is prepended at runtime with the value of appLoc.

      -
    • files: UploadFile[]
      -

      array of files to be uploaded, including File object and file name.

      -
    • params: null | {}
      -

      request URL parameters.

      -
    • config: {} = {}
      -

      provide any changes to the config here, for instance to -enable/disable debug. Any change provided will override the global config, -for that particular function call.

      -
      • [key: string]: any
    • Optional loginRequiredCallback: () => any
      -

      a function that is called if the -user is not logged in (eg to display a login form). The request will be -resubmitted after successful login.

      -
        • (): any
        • Returns any

    Returns Promise<unknown>

Legend

  • Class
  • Constructor
  • Method
  • Function
  • Enumeration
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/SASjsApiClient.SASjsApiClient-1.html b/docs/classes/SASjsApiClient.SASjsApiClient-1.html deleted file mode 100644 index 4154ff1..0000000 --- a/docs/classes/SASjsApiClient.SASjsApiClient-1.html +++ /dev/null @@ -1,19 +0,0 @@ -SASjsApiClient | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • SASjsApiClient

Index

Constructors

  • new SASjsApiClient(serverUrl: string, requestClient: RequestClient): SASjsApiClient

Methods

  • deploy(members: FileTree, appLoc: string, authConfig?: AuthConfig): Promise<{ example?: {}; message: string; status: string }>
  • Parameters

    • members: FileTree
    • appLoc: string
    • Optional authConfig: AuthConfig

    Returns Promise<{ example?: {}; message: string; status: string }>

  • executeJob(query: ExecutionQuery): Promise<{ _webout?: string; error?: {}; log?: string; logPath?: string; message: string; status: string }>
  • Parameters

    Returns Promise<{ _webout?: string; error?: {}; log?: string; logPath?: string; message: string; status: string }>

  • -

    Exchanges the auth code for an access token for the given client.

    -

    Parameters

    • clientId: string
      -

      the client ID to authenticate with.

      -
    • authCode: string
      -

      the auth code received from the server.

      -

    Returns Promise<SASjsAuthResponse>

  • getAuthCode(username: string, password: string, clientId: string): Promise<string>
  • -

    Performs a login authenticate and returns an auth code for the given client.

    -

    Parameters

    • username: string
      -

      a string representing the username.

      -
    • password: string
      -

      a string representing the password.

      -
    • clientId: string
      -

      the client ID to authenticate with.

      -

    Returns Promise<string>

  • -

    Exchanges the refresh token for an access token.

    -

    Parameters

    • refreshToken: string
      -

      the refresh token received from the server.

      -

    Returns Promise<SASjsAuthResponse>

  • setConfig(serverUrl: string): void

Legend

  • Class
  • Constructor
  • Method
  • Function
  • Enumeration
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/classes/types.SASjsConfig.html b/docs/classes/types.SASjsConfig.html deleted file mode 100644 index 6873039..0000000 --- a/docs/classes/types.SASjsConfig.html +++ /dev/null @@ -1,48 +0,0 @@ -SASjsConfig | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu
-

Specifies the configuration for the SASjs instance - eg where and how to -connect to SAS.

-

Hierarchy

  • SASjsConfig

Index

Constructors

Properties

appLoc: string = ''
-

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 request function should be -without a leading slash (/).

-
contextName: string = ''
-

The name of the compute context to use when calling the Viya services directly. -Example value: 'SAS Job Execution compute context'

-
debug: boolean = true
-

Set to true to enable additional debugging.

-
httpsAgentOptions?: AgentOptions
-

Optional setting to configure HTTPS Agent. -By providing key, cert, ca to connect with server -Other options can be set rejectUnauthorized and requestCert

-
loginMechanism: LoginMechanism = LoginMechanism.Default
-

Supported login mechanisms are - Redirected and Default

-
pathSAS9: string = ''
-

The location of the Stored Process Web Application. By default the adapter -will use '/SASStoredProcess/do' on SAS 9.

-
pathSASJS: string = ''
-

The location of the STP Process Web Application. By default the adapter -will use '/SASjsApi/stp/execute' on SAS JS.

-
pathSASViya: string = ''
-

The location of the Job Execution Web Application. By default the adapter -will use '/SASJobExecution' on SAS Viya.

-
requestHistoryLimit?: number = 10
-

Optional setting to configure request history limit. Increasing this limit -may affect browser performance, especially with debug (logs) enabled.

-
serverType: null | ServerType = null
-

Can be SAS9 or SASVIYA.

-
serverUrl: string = ''
-

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.

-
useComputeApi: null | boolean = null
-

If it's false adapter will use the JES API as connection approach. To enhance VIYA -performance, set to true and provide a contextName 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 useComputeApi is null or undefined, the service will run as a Job, except -triggered using the APIs instead of the Job Execution Web Service broker.

-

Legend

  • Class
  • Constructor
  • Property
  • Function
  • Enumeration
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/enums/types.LoginMechanism.html b/docs/enums/types.LoginMechanism.html deleted file mode 100644 index 724b339..0000000 --- a/docs/enums/types.LoginMechanism.html +++ /dev/null @@ -1 +0,0 @@ -LoginMechanism | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Enumeration LoginMechanism

Index

Enumeration members

Enumeration members

Default = "Default"
Redirected = "Redirected"

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/enums/types.MemberType.html b/docs/enums/types.MemberType.html deleted file mode 100644 index d938259..0000000 --- a/docs/enums/types.MemberType.html +++ /dev/null @@ -1 +0,0 @@ -MemberType | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Enumeration MemberType

Index

Enumeration members

Enumeration members

folder = "folder"
service = "service"

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 06d5f0f..0000000 --- a/docs/index.html +++ /dev/null @@ -1,292 +0,0 @@ -@sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

@sasjs/adapter

- -

@sasjs/adapter

-
-

npm package -Github Workflow -Dependency Status -npm -Snyk Vulnerabilities for npm package -License -GitHub top language -GitHub issues -Gitpod ready-to-code

-

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:

-

1 - npm install @sasjs/adapter - for use in a nodeJS project (recommended)

-

2 - Download and use a copy of the latest JS file

-

3 - Reference directly from the CDN - in which case click here and select "SRI" to get the script tag with the integrity hash.

-

If you are short on time and just need to build an app quickly, then check out this video and the react-seed-app which provides some boilerplate.

-

For more information on building web apps with SAS, check out sasjs.io

- - -

None of this makes sense. How do I build an app with it?

-
-

Ok ok. Deploy this example.html file to your web server, and update servertype to SAS9 or SASVIYA depending on your backend.

-

The backend part can be deployed as follows:

-
%let appLoc=/Public/app/readme;  /* Metadata or Viya Folder per SASjs config */
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; /* compile macros (can also be downloaded & compiled seperately) */
filename ft15f001 temp;
parmcards4;
%webout(FETCH) /* receive all data as SAS datasets */
proc sql;
create table areas as select make,mean(invoice) as avprice
from sashelp.cars
where type in (select type from work.fromjs)
group by 1;
%webout(OPEN)
%webout(OBJ,areas)
%webout(CLOSE)
;;;;
%mp_createwebservice(path=&appLoc/common,name=getdata) -
-

You now have a simple web app with a backend service!

- - -

Detailed Overview

-
-

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.

-

There are three parts to consider:

-
    -
  1. JS request / response
  2. -
  3. SAS inputs / outputs
  4. -
  5. Configuration
  6. -
- - -

JS Request / Response

-
-

To install the library you can simply run npm i @sasjs/adapter or include a <script> tag with a reference to our CDN.

-

Full technical documentation is available here. The main parts are:

- - -

Instantiation

-
-

The following code will instantiate an instance of the adapter:

-
let sasJs = new SASjs.default(
{
appLoc: "/Your/SAS/Folder",
serverType:"SAS9"
}
); -
-

If you've installed it via NPM, you can import it as a default import like so:

-
  import SASjs from '@sasjs/adapter';
-
-

You can then instantiate it with:

-
const sasJs = new SASjs({your config})
-
-

More on the config later.

- - -

SAS Logon

-
-

All authentication from the adapter is done against SASLogon. There are two approaches that can be taken, which are configured using the LoginMechanism attribute of the sasJs config object (above):

-
    -
  • LoginMechanism:'Redirected' - 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.
  • -
  • LoginMechanism:'Default' - this approach requires that the username and password are captured, and used within the .login() method. This can be helpful for development, or automated testing.
  • -
-

Sample code for logging in with the Default approach:

-
sasJs.logIn('USERNAME','PASSWORD'
).then((response) => {
if (response.isLoggedIn === true) {
console.log('do stuff')
} else {
console.log('do other stuff')
}
} -
-

More examples of using authentication, and more, can be found in the SASjs Seed Apps on github.

- - -

Request / Response

-
-

A simple request can be sent to SAS in the following fashion:

-
sasJs.request("/path/to/my/service", dataObject)
.then((response) => {
// all tables are in the response object, eg:
console.log(response.tablewith2cols1row[0].COL1.value)
}) -
-

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:

-
let dataObject={
"tablewith2cols1row": [{
"col1": "val1",
"col2": 42
}],
"tablewith1col2rows": [{
"col": "row1"
}, {
"col": "row2"
}]
}; -
-

There are optional parameters such as a config object and a callback login function.

-

The response object will contain returned tables and columns. Table names are always lowercase, and column names uppercase.

-

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.

- - -

Variable Types

-
-

The SAS type (char/numeric) of the values is determined according to a set of rules:

-
    -
  • 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).
  • -
  • 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:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
JS ValuesSAS Format
'a', 'a'$char1.
0, '_'best.
'Z', 0best.
'a', 'aaa'$char3.
null, 'a', 'aaa'$char3.
null, 'a', 0best.
null, nullbest.
null, ''$char1.
null, 'a'$char1.
'a'$char1.
'a', null$char1.
'a', null, 0best.
-

Validation is also performed on the values. The following combinations will throw errors:

- - - - - - - - - - - - - - - - - - - -
JS ValuesSAS Format
null, 'aaaa', 0Error: mixed types. 'aaaa' is not a special missing value.
0, 'a', '!'Error: mixed types. '!' is not a special missing value
1.1, '.', 0Error: mixed types. For regular nulls, use null
- - -

Variable Format Override

-
-

The auto-detect functionality above is thwarted in the following scenarios:

-
    -
  • A character column containing only null values (is considered numeric)
  • -
  • A numeric column containing only special missing values (is considered character)
  • -
-

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.

-

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 infile statement in disguise. It looks a bit like this:

-
CHARVAR1:$char4. CHARVAR2:$char1. NUMVAR:best.
-LOAD,,0
-ABCD,X,.
-
-

To provide overrides to this header row, the tables object can be constructed as follows (with a leading '$' in the table name):

-
let specialData={
"tablewith2cols2rows": [
{"col1": "val1","specialMissingsCol": "A"},
{"col1": "val2","specialMissingsCol": "_"}
],
"$tablewith2cols2rows":{"formats":{"specialMissingsCol":"best."}
}
}; -
-

It is not necessary to provide formats for ALL the columns, only the ones that need to be overridden.

- - -

SAS Inputs / Outputs

-
-

The SAS side is handled by a number of macros in the macro core library.

-

The following snippet shows the process of SAS tables arriving / leaving:

-
/* fetch all input tables sent from frontend - they arrive as work tables */
%webout(FETCH)

/* some sas code */
data a b c;
set from js;
run;

%webout(OPEN) /* Open the JSON to be returned */
%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(CLOSE) /* Close the JSON and add default variables */ -
-

By default, special SAS numeric missings (_a-Z) are converted to null in the JSON. If you'd like to preserve these, use the missing=STRING option as follows:

-
%webout(OBJ,a,missing=STRING)
-
-

In this case, special missings (such as .a, .b) are converted to javascript string values ('A', 'B').

-

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 showmeta param to the webout() macro as follows:

-
%webout(OBJ,a,missing=STRING,showmeta=YES)
-
- - -

Configuration

-
-

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 here. The main config items are:

-
    -
  • appLoc - this is the folder (eg in metadata or SAS Drive) under which the SAS services are created.
  • -
  • serverType - either SAS9, SASVIYA or SASJS. The SASJS server type is for use with sasjs/server.
  • -
  • serverUrl - 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.
  • -
  • debug - if true then SAS Logs and extra debug information is returned.
  • -
  • LoginMechanism - either Default or Redirected. See SAS Logon section.
  • -
  • useComputeApi - Only relevant when the serverType is SASVIYA. If true the Compute API is used. If false the JES API is used. If null or undefined the Web approach is used.
  • -
  • contextName - Compute context on which the requests will be called. If missing or not provided, defaults to Job Execution Compute context.
  • -
  • requestHistoryLimit - Request history limit. Increasing this limit may affect browser performance, especially with debug (logs) enabled. Default is 10.
  • -
-

The adapter supports a number of approaches for interfacing with Viya (serverType is SASVIYA). For maximum performance, be sure to configure your compute context with reuseServerProcesses as true and a system account in runServerAs. This functionality is available since Viya 3.5. This configuration is supported when creating contexts using the CLI.

- - -

Using JES Web App

-
-

In this setup, all requests are routed through the JES web app, at YOURSERVER/SASJobExecution?_program=/your/program. 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.

-
{
appLoc:"/Your/Path",
serverType:"SASVIYA",
contextName: 'yourComputeContext'
} -
-

Note - to use the web approach, the useComputeApi property must be undefined or null.

- - -

Using the JES API

-
-

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 __job parameter. Depending on your network bandwidth, it may or may not be faster than the JES Web approach.

-

This approach (useComputeApi: false) also ensures that jobs are displayed in Environment Manager.

-
{
appLoc:"/Your/Path",
serverType:"SASVIYA",
useComputeApi: false,
contextName: 'yourComputeContext'
} -
- - -

Using the Compute API

-
-

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 'hot' session, if it exists. Sessions are always deleted after every use, which actually makes this less resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes.

-

With this approach (useComputeApi: true), the requests/logs will not appear in the list in Environment manager.

-
{
appLoc:"/Your/Path",
serverType:"SASVIYA",
useComputeApi: true,
contextName: "yourComputeContext"
} -
- - -

More resources

-
-

For more information and examples specific to this adapter you can check out the user guide or the technical documentation.

-

For more information on building web apps in general, check out these resources or contact the author directly.

-

If you are a SAS 9 or SAS Viya customer you can also request a copy of Data Controller - free for up to 5 users, this tool makes use of all parts of the SASjs framework.

- - -

Star Gazing

-
-

If you find this library useful, help us grow our star graph!

-

- - -

Contributors โœจ

-
- -

All Contributors

- - -

Thanks goes to these wonderful people (emoji key):

- - - - - - - - - - - - - -

Krishna Acondy

๐Ÿ’ป ๐Ÿš‡ ๐Ÿ“ ๐Ÿ–‹ ๐Ÿค” ๐Ÿ“น

Yury Shkoda

๐Ÿ’ป ๐Ÿš‡ ๐Ÿค” โš ๏ธ ๐Ÿ“น

Mihajlo Medjedovic

๐Ÿ’ป ๐Ÿš‡ โš ๏ธ ๐Ÿ‘€

Allan Bowe

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿง‘โ€๐Ÿซ ๐Ÿšง

Muhammad Saad

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿง‘โ€๐Ÿซ ๐Ÿš‡

Sabir Hassan

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ ๐Ÿค”

VladislavParhomchik

โš ๏ธ ๐Ÿ‘€
- - - - - - -

This project follows the all-contributors specification. Contributions of any kind welcome!

-

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/SASjsApiClient.SASjsAuthResponse.html b/docs/interfaces/SASjsApiClient.SASjsAuthResponse.html deleted file mode 100644 index 0c8c67c..0000000 --- a/docs/interfaces/SASjsApiClient.SASjsAuthResponse.html +++ /dev/null @@ -1 +0,0 @@ -SASjsAuthResponse | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • SASjsAuthResponse

Index

Properties

access_token: string
refresh_token: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.Context.html b/docs/interfaces/types.Context.html deleted file mode 100644 index c5d09ce..0000000 --- a/docs/interfaces/types.Context.html +++ /dev/null @@ -1 +0,0 @@ -Context | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Context

Index

Properties

attributes?: any
createdBy: string
id: string
name: string
version: number

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.ContextAllAttributes.html b/docs/interfaces/types.ContextAllAttributes.html deleted file mode 100644 index b7dadaf..0000000 --- a/docs/interfaces/types.ContextAllAttributes.html +++ /dev/null @@ -1 +0,0 @@ -ContextAllAttributes | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface ContextAllAttributes

Hierarchy

  • ContextAllAttributes

Index

Properties

attributes: { reuseServerProcesses: boolean; runServerAs: string }

Type declaration

  • reuseServerProcesses: boolean
  • runServerAs: string
createdBy: string
creationTimeStamp: string
environment: { autoExecLines: [string] }

Type declaration

  • autoExecLines: [string]
id: string
launchContext: { contextName: string }

Type declaration

  • contextName: string
launchType: string
modifiedBy: string
modifiedTimeStamp: string
name: string
version: number

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.CsrfToken.html b/docs/interfaces/types.CsrfToken.html deleted file mode 100644 index b0245ac..0000000 --- a/docs/interfaces/types.CsrfToken.html +++ /dev/null @@ -1 +0,0 @@ -CsrfToken | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface CsrfToken

Hierarchy

  • CsrfToken

Index

Properties

Properties

headerName: string
value: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.EditContextInput.html b/docs/interfaces/types.EditContextInput.html deleted file mode 100644 index f556c4d..0000000 --- a/docs/interfaces/types.EditContextInput.html +++ /dev/null @@ -1 +0,0 @@ -EditContextInput | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface EditContextInput

Hierarchy

  • EditContextInput

Index

Properties

attributes?: any
authorizeAllAuthenticatedUsers?: boolean
authorizedUsers?: string[]
description?: string
environment?: { autoExecLines?: string[]; options?: string[] }

Type declaration

  • Optional autoExecLines?: string[]
  • Optional options?: string[]
id?: string
launchContext?: { name: string }

Type declaration

  • name: string
name?: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.ExecutionQuery.html b/docs/interfaces/types.ExecutionQuery.html deleted file mode 100644 index 05bd387..0000000 --- a/docs/interfaces/types.ExecutionQuery.html +++ /dev/null @@ -1 +0,0 @@ -ExecutionQuery | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface ExecutionQuery

Hierarchy

  • ExecutionQuery

Index

Properties

Properties

_debug?: number
_program: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.File.html b/docs/interfaces/types.File.html deleted file mode 100644 index 2ab365b..0000000 --- a/docs/interfaces/types.File.html +++ /dev/null @@ -1 +0,0 @@ -File | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • File

Index

Properties

id: string
links: Link[]
name: string
parentUri: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.FileTree.html b/docs/interfaces/types.FileTree.html deleted file mode 100644 index 6f38ad0..0000000 --- a/docs/interfaces/types.FileTree.html +++ /dev/null @@ -1 +0,0 @@ -FileTree | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FileTree

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.Folder.html b/docs/interfaces/types.Folder.html deleted file mode 100644 index 63da61c..0000000 --- a/docs/interfaces/types.Folder.html +++ /dev/null @@ -1 +0,0 @@ -Folder | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Folder

Index

Properties

id: string
links: Link[]
memberCount: number
uri: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.FolderMember.html b/docs/interfaces/types.FolderMember.html deleted file mode 100644 index 2a7ec3a..0000000 --- a/docs/interfaces/types.FolderMember.html +++ /dev/null @@ -1 +0,0 @@ -FolderMember | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FolderMember

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.Job.html b/docs/interfaces/types.Job.html deleted file mode 100644 index d11ea98..0000000 --- a/docs/interfaces/types.Job.html +++ /dev/null @@ -1 +0,0 @@ -Job | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Job

Index

Properties

code?: string
createdBy: string
error?: any
id: string
links: Link[]
logStatistics: LogStatistics
name: string
results: JobResult
uri: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.JobDefinition.html b/docs/interfaces/types.JobDefinition.html deleted file mode 100644 index e451aa0..0000000 --- a/docs/interfaces/types.JobDefinition.html +++ /dev/null @@ -1 +0,0 @@ -JobDefinition | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface JobDefinition

Hierarchy

  • JobDefinition

Index

Properties

Properties

code: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.JobResult.html b/docs/interfaces/types.JobResult.html deleted file mode 100644 index c8371ad..0000000 --- a/docs/interfaces/types.JobResult.html +++ /dev/null @@ -1 +0,0 @@ -JobResult | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface JobResult

Hierarchy

  • JobResult

Index

Properties

Properties

_webout.json: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.Link.html b/docs/interfaces/types.Link.html deleted file mode 100644 index 6f01e2c..0000000 --- a/docs/interfaces/types.Link.html +++ /dev/null @@ -1 +0,0 @@ -Link | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Link

Index

Properties

href: string
method: string
rel: string
type: string
uri: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.PollOptions.html b/docs/interfaces/types.PollOptions.html deleted file mode 100644 index d46175c..0000000 --- a/docs/interfaces/types.PollOptions.html +++ /dev/null @@ -1 +0,0 @@ -PollOptions | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface PollOptions

Hierarchy

  • PollOptions

Index

Properties

logFolderPath?: string
maxPollCount: number
pollInterval: number
streamLog: boolean

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.SASjsRequest.html b/docs/interfaces/types.SASjsRequest.html deleted file mode 100644 index d84011b..0000000 --- a/docs/interfaces/types.SASjsRequest.html +++ /dev/null @@ -1,3 +0,0 @@ -SASjsRequest | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface SASjsRequest

-

Represents a SASjs request, its response and logs.

-

Hierarchy

  • SASjsRequest

Index

Properties

SASWORK: any
generatedCode: string
logFile: string
serviceLink: string
sourceCode: string
timestamp: Date

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.ServiceMember.html b/docs/interfaces/types.ServiceMember.html deleted file mode 100644 index b41cdf7..0000000 --- a/docs/interfaces/types.ServiceMember.html +++ /dev/null @@ -1 +0,0 @@ -ServiceMember | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface ServiceMember

Hierarchy

  • ServiceMember

Index

Properties

Properties

code: string
name: string
type: service

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.Session.html b/docs/interfaces/types.Session.html deleted file mode 100644 index c5b1b78..0000000 --- a/docs/interfaces/types.Session.html +++ /dev/null @@ -1 +0,0 @@ -Session | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • Session

Index

Properties

attributes: { sessionInactiveTimeout: number }

Type declaration

  • sessionInactiveTimeout: number
creationTimeStamp: string
id: string
links: Link[]
state: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.SessionVariable.html b/docs/interfaces/types.SessionVariable.html deleted file mode 100644 index 1bd4341..0000000 --- a/docs/interfaces/types.SessionVariable.html +++ /dev/null @@ -1 +0,0 @@ -SessionVariable | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface SessionVariable

Hierarchy

  • SessionVariable

Index

Properties

Properties

value: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.UploadFile.html b/docs/interfaces/types.UploadFile.html deleted file mode 100644 index 7191888..0000000 --- a/docs/interfaces/types.UploadFile.html +++ /dev/null @@ -1,3 +0,0 @@ -UploadFile | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface UploadFile

-

Represents an object that is passed to the file uploader.

-

Hierarchy

  • UploadFile

Index

Properties

Properties

file: File
fileName: string

Legend

  • Interface
  • Property
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/interfaces/types.WriteStream.html b/docs/interfaces/types.WriteStream.html deleted file mode 100644 index 2f01ebe..0000000 --- a/docs/interfaces/types.WriteStream.html +++ /dev/null @@ -1 +0,0 @@ -WriteStream | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface WriteStream

Hierarchy

  • WriteStream

Index

Properties

Methods

Properties

path: string

Methods

  • write(content: string, callback: (err?: Error) => any): void
  • Parameters

    • content: string
    • callback: (err?: Error) => any
        • (err?: Error): any
        • Parameters

          • Optional err: Error

          Returns any

    Returns void

Legend

  • Interface
  • Property
  • Method
  • Function
  • Enumeration
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules.html b/docs/modules.html deleted file mode 100644 index 57a1ccf..0000000 --- a/docs/modules.html +++ /dev/null @@ -1 +0,0 @@ -@sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

@sasjs/adapter

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules/SAS9ApiClient.html b/docs/modules/SAS9ApiClient.html deleted file mode 100644 index ab53ddd..0000000 --- a/docs/modules/SAS9ApiClient.html +++ /dev/null @@ -1 +0,0 @@ -SAS9ApiClient | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module SAS9ApiClient

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules/SASViyaApiClient.html b/docs/modules/SASViyaApiClient.html deleted file mode 100644 index 7d9c023..0000000 --- a/docs/modules/SASViyaApiClient.html +++ /dev/null @@ -1 +0,0 @@ -SASViyaApiClient | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module SASViyaApiClient

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules/SASjs.html b/docs/modules/SASjs.html deleted file mode 100644 index 9432043..0000000 --- a/docs/modules/SASjs.html +++ /dev/null @@ -1 +0,0 @@ -SASjs | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules/SASjsApiClient.html b/docs/modules/SASjsApiClient.html deleted file mode 100644 index 8a7fbb2..0000000 --- a/docs/modules/SASjsApiClient.html +++ /dev/null @@ -1 +0,0 @@ -SASjsApiClient | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module SASjsApiClient

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/modules/types.html b/docs/modules/types.html deleted file mode 100644 index 683b1a8..0000000 --- a/docs/modules/types.html +++ /dev/null @@ -1 +0,0 @@ -types | @sasjs/adapter
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Function
  • Enumeration
  • Interface
  • Class

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/example.html b/example.html index c8e21d0..0ccd0a5 100644 --- a/example.html +++ b/example.html @@ -1,7 +1,7 @@ - + + + $treeview $search $mathjax + + + $extrastylesheet + + +
+ + + +
+ + + + + + + + + + + + + + +
+
$projectname
+
$projectbrief
+
$searchbox
+
+ + +
+ + diff --git a/sasjs-tests/sasjs/doxy/new_stylesheet.css b/sasjs-tests/sasjs/doxy/new_stylesheet.css new file mode 100644 index 0000000..b7ec9a3 --- /dev/null +++ b/sasjs-tests/sasjs/doxy/new_stylesheet.css @@ -0,0 +1,4 @@ +#projectlogo img { + border: 0px none; + max-height: 70px; +} diff --git a/sasjs-tests/sasjs/sasjsconfig.json b/sasjs-tests/sasjs/sasjsconfig.json new file mode 100644 index 0000000..fe3a16c --- /dev/null +++ b/sasjs-tests/sasjs/sasjsconfig.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://cli.sasjs.io/sasjsconfig-schema.json", + "serviceConfig": { + "serviceFolders": ["sasjs/common"] + }, + "defaultTarget": "4gl", + "targets": [ + { + "name": "4gl", + "serverUrl": "https://sas9.4gl.io", + "serverType": "SASJS", + "httpsAgentOptions": { + "allowInsecureRequests": false + }, + "appLoc": "/Public/app/adapter-tests", + "deployConfig": { + "deployServicePack": true, + "deployScripts": [] + }, + "streamConfig": { + "streamWeb": true, + "streamWebFolder": "webv", + "webSourcePath": "build", + "streamServiceName": "adapter-tests", + "assetPaths": [] + } + } + ] +} diff --git a/sasjs-tests/src/testSuites/Compute.ts b/sasjs-tests/src/testSuites/Compute.ts index a3c7024..8c0e962 100644 --- a/sasjs-tests/src/testSuites/Compute.ts +++ b/sasjs-tests/src/testSuites/Compute.ts @@ -48,7 +48,7 @@ export const computeTests = (adapter: SASjs, appLoc: string): TestSuite => ({ test: () => { const data: any = { table1: [{ col1: 'first col value' }] } return adapter.startComputeJob( - '/Public/app/common/sendArr', + '/Public/app/adapter-tests/services/common/sendArr', data, {}, undefined, @@ -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) => { diff --git a/screenshots/session-manager-first-request.png b/screenshots/session-manager-first-request.png new file mode 100644 index 0000000..0556504 Binary files /dev/null and b/screenshots/session-manager-first-request.png differ diff --git a/screenshots/subsequent-session-request.png b/screenshots/subsequent-session-request.png new file mode 100644 index 0000000..57055d5 Binary files /dev/null and b/screenshots/subsequent-session-request.png differ diff --git a/src/SASViyaApiClient.spec.ts b/src/SASViyaApiClient.spec.ts index 516c5bb..13c5199 100644 --- a/src/SASViyaApiClient.spec.ts +++ b/src/SASViyaApiClient.spec.ts @@ -29,11 +29,33 @@ describe('SASViyaApiClient', () => { jest .spyOn(requestClient, 'get') .mockImplementation(() => Promise.reject('Not Found')) + const error = await sasViyaApiClient .createFolder('test', '/foo') .catch((e: any) => e) + expect(error).toBeInstanceOf(RootFolderNotFoundError) }) + + it('should throw an error when ', async () => { + const testMessage1 = 'test message 1' + const testMessage2 = 'test message 2.' + + jest.spyOn(requestClient, 'post').mockImplementation(() => { + return Promise.reject({ + message: testMessage1, + response: { data: { message: testMessage2 }, status: 409 } + }) + }) + + const error = await sasViyaApiClient + .createFolder('test', '/foo') + .catch((e: any) => e) + + const expectedError = `${testMessage1}. ${testMessage2} To override, please set "isForced" to "true".` + + expect(error).toEqual(expectedError) + }) }) const setupMocks = () => { diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 643bf74..a78d130 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -29,6 +29,12 @@ import { executeScript } from './api/viya/executeScript' import { getAccessTokenForViya } from './auth/getAccessTokenForViya' import { refreshTokensForViya } from './auth/refreshTokensForViya' +interface JobExecutionResult { + result?: { result: object } + log?: string + error?: object +} + /** * A client for interfacing with the SAS Viya REST API. * @@ -270,7 +276,7 @@ export class SASViyaApiClient { * @param debug - when set to true, the log will be returned. * @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code). * @param waitForResult - when set to true, function will return the session - * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }. + * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { maxPollCount: 24 * 60 * 60, pollInterval: 1000 }. More information available at src/api/viya/pollJobState.ts. * @param printPid - a boolean that indicates whether the function should print (PID) of the started job. * @param variables - an object that represents macro variables. */ @@ -378,12 +384,14 @@ export class SASViyaApiClient { isForced?: boolean ): Promise { const logger = process.logger || console + if (!parentFolderPath && !parentFolderUri) { throw new Error('Path or URI of the parent folder is required.') } if (!parentFolderUri && parentFolderPath) { parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken) + if (!parentFolderUri) { logger.info( `Parent folder at path '${parentFolderPath}' is not present.` @@ -394,6 +402,7 @@ export class SASViyaApiClient { parentFolderPath.lastIndexOf('/') ) const newFolderName = `${parentFolderPath.split('/').pop()}` + if (newParentFolderPath === '') { throw new RootFolderNotFoundError( parentFolderPath, @@ -401,20 +410,24 @@ export class SASViyaApiClient { accessToken ) } + logger.info( `Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'` ) + const parentFolder = await this.createFolder( newFolderName, newParentFolderPath, undefined, accessToken ) + logger.info( `Parent folder '${newFolderName}' has been successfully created.` ) + parentFolderUri = `/folders/folders/${parentFolder.id}` - } else if (isForced && accessToken) { + } else if (isForced) { const folderPath = parentFolderPath + '/' + folderName const folderUri = await this.getFolderUri(folderPath, accessToken) @@ -427,8 +440,8 @@ export class SASViyaApiClient { } } - const { result: createFolderResponse } = - await this.requestClient.post( + const { result: createFolderResponse } = await this.requestClient + .post( `/folders/folders?parentFolderUri=${parentFolderUri}`, { name: folderName, @@ -436,12 +449,34 @@ export class SASViyaApiClient { }, accessToken ) + .catch((err) => { + const { message, response } = err + + if (message && response && response.data && response.data.message) { + const { status } = response + const { message: responseMessage } = response.data + const messages = [message, responseMessage].map((mes: string) => + /\.$/.test(mes) ? mes : `${mes}.` + ) + + if (!isForced && status === 409) { + messages.push(`To override, please set "isForced" to "true".`) + } + + const errMessage = messages.join(' ') + + throw errMessage + } + + throw err + }) // update folder map with newly created folder. await this.populateFolderMap( `${parentFolderPath}/${folderName}`, accessToken ) + return createFolderResponse } @@ -548,6 +583,7 @@ export class SASViyaApiClient { /** * Exchanges the refresh token for an access token for the given client. + * This method can only be used by Node. * @param clientId - the client ID to authenticate with. * @param clientSecret - the client secret to authenticate with. * @param refreshToken - the refresh token received from the server. @@ -591,7 +627,7 @@ export class SASViyaApiClient { * @param accessToken - an optional access token for an authorized user. * @param waitForResult - a boolean indicating if the function should wait for a result. * @param expectWebout - a boolean indicating whether to expect a _webout response. - * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }. + * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { maxPollCount: 24 * 60 * 60, pollInterval: 1000 }. More information available at src/api/viya/pollJobState.ts. * @param printPid - a boolean that indicates whether the function should print (PID) of the started job. * @param variables - an object that represents macro variables. */ @@ -702,11 +738,13 @@ export class SASViyaApiClient { debug: boolean, data?: any, authConfig?: AuthConfig - ) { + ): Promise { let access_token = (authConfig || {}).access_token + if (authConfig) { ;({ access_token } = await getTokens(this.requestClient, authConfig)) } + if (isRelativePath(sasJob) && !this.rootFolderName) { throw new Error( 'Relative paths cannot be used without specifying a root folder name.' @@ -719,6 +757,7 @@ export class SASViyaApiClient { const fullFolderPath = isRelativePath(sasJob) ? `${this.rootFolderName}/${folderPath}` : folderPath + await this.populateFolderMap(fullFolderPath, access_token) const jobFolder = this.folderMap.get(fullFolderPath) @@ -735,9 +774,8 @@ export class SASViyaApiClient { files = await this.uploadTables(data, access_token) } - if (!jobToExecute) { - throw new Error(`Job was not found.`) - } + if (!jobToExecute) throw new Error(`Job was not found.`) + const jobDefinitionLink = jobToExecute?.links.find( (l) => l.rel === 'getResource' )?.href @@ -777,16 +815,19 @@ export class SASViyaApiClient { jobDefinition, arguments: jobArguments } + const { result: postedJob } = await this.requestClient.post( `${this.serverUrl}/jobExecution/jobs?_action=wait`, postJobRequestBody, access_token ) + const jobStatus = await this.pollJobState(postedJob, authConfig).catch( (err) => { throw prefixMessage(err, 'Error while polling job status. ') } ) + const { result: currentJob } = await this.requestClient.get( `${this.serverUrl}/jobExecution/jobs/${postedJob.id}`, access_token @@ -797,6 +838,7 @@ export class SASViyaApiClient { const resultLink = currentJob.results['_webout.json'] const logLink = currentJob.links.find((l) => l.rel === 'log') + if (resultLink) { jobResult = await this.requestClient.get( `${this.serverUrl}${resultLink}/content`, @@ -804,11 +846,13 @@ export class SASViyaApiClient { 'text/plain' ) } + if (debug && logLink) { log = await this.requestClient .get(`${this.serverUrl}${logLink.href}/content`, access_token) .then((res: any) => res.result.items.map((i: any) => i.line).join('\n')) } + if (jobStatus === 'failed') { throw new JobExecutionError( currentJob.error?.errorCode, @@ -816,7 +860,16 @@ export class SASViyaApiClient { log ) } - return { result: jobResult?.result, log } + + const executionResult: JobExecutionResult = { + result: jobResult?.result, + log + } + + const { error } = currentJob + if (error) executionResult.error = error + + return executionResult } private async populateFolderMap(folderPath: string, accessToken?: string) { @@ -899,7 +952,7 @@ export class SASViyaApiClient { return `/folders/folders/${folderDetails.id}` } - private async getRecycleBinUri(accessToken: string) { + private async getRecycleBinUri(accessToken?: string) { const url = '/folders/folders/@myRecycleBin' const { result: folder } = await this.requestClient @@ -983,7 +1036,7 @@ export class SASViyaApiClient { sourceFolder: string, targetParentFolder: string, targetFolderName: string, - accessToken: string + accessToken?: string ) { // If target path is an existing folder, than keep source folder name, othervise rename it with given target folder name const sourceFolderName = sourceFolder.split('/').pop() as string @@ -1050,7 +1103,7 @@ export class SASViyaApiClient { * @param folderPath - the full path (eg `/Public/example/deleteThis`) of the folder to be deleted. * @param accessToken - an access token for authorizing the request. */ - public async deleteFolder(folderPath: string, accessToken: string) { + public async deleteFolder(folderPath: string, accessToken?: string) { const recycleBinUri = await this.getRecycleBinUri(accessToken) const folderName = folderPath.split('/').pop() || '' const date = new Date() diff --git a/src/SASjs.ts b/src/SASjs.ts index 40a3d4e..e3c7677 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -4,8 +4,7 @@ import { UploadFile, EditContextInput, PollOptions, - LoginMechanism, - ExecutionQuery + LoginMechanism } from './types' import { SASViyaApiClient } from './SASViyaApiClient' import { SAS9ApiClient } from './SAS9ApiClient' @@ -17,7 +16,7 @@ import { AuthConfig, ExtraResponseAttributes, SasAuthResponse, - ServicePackSASjs + AuthConfigSas9 } from '@sasjs/utils/types' import { RequestClient } from './request/RequestClient' import { SasjsRequestClient } from './request/SasjsRequestClient' @@ -33,6 +32,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', @@ -79,74 +88,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, - runTime?: string, - authConfig?: AuthConfig - ) { - this.isMethodSupported('executeScriptSASJS', [ServerType.Sasjs]) - - return await this.sasJSApiClient?.executeScript(code, runTime, 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 + ) + } } /** @@ -329,13 +337,16 @@ export default class SASjs { sasApiClient?: SASViyaApiClient, isForced?: boolean ) { - if (sasApiClient) + if (sasApiClient) { return await sasApiClient.createFolder( folderName, parentFolderPath, parentFolderUri, - accessToken + accessToken, + isForced ) + } + return await this.sasViyaApiClient!.createFolder( folderName, parentFolderPath, @@ -775,13 +786,11 @@ export default class SASjs { this.isMethodSupported('deployServicePack', [ServerType.SasViya]) let sasApiClient: any = null + if (serverUrl || appLoc) { - if (!serverUrl) { - serverUrl = this.sasjsConfig.serverUrl - } - if (!appLoc) { - appLoc = this.sasjsConfig.appLoc - } + if (!serverUrl) serverUrl = this.sasjsConfig.serverUrl + if (!appLoc) appLoc = this.sasjsConfig.appLoc + if (this.sasjsConfig.serverType === ServerType.SasViya) { sasApiClient = new SASViyaApiClient( serverUrl, @@ -799,11 +808,13 @@ export default class SASjs { } } else { let sasClientConfig: any = null + if (this.sasjsConfig.serverType === ServerType.SasViya) { sasClientConfig = this.sasViyaApiClient!.getConfig() } else if (this.sasjsConfig.serverType === ServerType.Sas9) { sasClientConfig = this.sas9ApiClient!.getConfig() } + serverUrl = sasClientConfig.serverUrl appLoc = sasClientConfig.rootFolderName as string } @@ -826,29 +837,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. @@ -863,7 +851,7 @@ export default class SASjs { * @param authConfig - a valid client, secret, refresh and access tokens that are authorised to execute compute jobs. * The access token is not required when the user is authenticated via the browser. * @param waitForResult - a boolean that indicates whether the function needs to wait for execution to complete. - * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }. + * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { maxPollCount: 24 * 60 * 60, pollInterval: 1000 }. More information available at src/api/viya/pollJobState.ts. * @param printPid - a boolean that indicates whether the function should print (PID) of the started job. * @param variables - an object that represents macro variables. */ @@ -908,6 +896,7 @@ export default class SASjs { await this.computeJobExecutor?.resendWaitingRequests() await this.jesJobExecutor?.resendWaitingRequests() await this.fileUploader?.resendWaitingRequests() + await this.sasjsJobExecutor?.resendWaitingRequests() } /** diff --git a/src/SASjsApiClient.ts b/src/SASjsApiClient.ts index ab255fe..e9bd8e8 100644 --- a/src/SASjsApiClient.ts +++ b/src/SASjsApiClient.ts @@ -1,28 +1,52 @@ +import * as NodeFormData from 'form-data' import { AuthConfig, ServerType, ServicePackSASjs } from '@sasjs/utils/types' +import { prefixMessage } from '@sasjs/utils/error' import { ExecutionQuery } from './types' import { RequestClient } from './request/RequestClient' import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs' import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs' -import { parseWeboutResponse, SASJS_LOGS_SEPARATOR } from './utils' import { getTokens } from './auth/getTokens' +// TODO: move to sasjs/utils +export interface SASjsAuthResponse { + access_token: string + refresh_token: string +} + +export interface ScriptExecutionResult { + log: string + webout?: string + printOutput?: string +} + 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,25 +66,61 @@ 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. @@ -71,25 +131,28 @@ export class SASjsApiClient { code: string, runTime: string = 'sas', authConfig?: AuthConfig - ) { - let access_token = (authConfig || {}).access_token - if (authConfig) { - ;({ access_token } = await getTokens( - this.requestClient, - authConfig, - ServerType.Sasjs - )) - } - - let parsedSasjsServerLog = '' + ): Promise { + const access_token = await this.getAccessTokenForRequest(authConfig) + const executionResult: ScriptExecutionResult = { log: '' } await this.requestClient .post('SASjsApi/code/execute', { code, runTime }, access_token) .then((res: any) => { - if (res.log) parsedSasjsServerLog = res.log + const { log, printOutput, result: webout } = res + + executionResult.log = log + + if (printOutput) executionResult.printOutput = printOutput + if (webout) executionResult.webout = webout + }) + .catch((err) => { + throw prefixMessage( + err, + 'Error while sending POST request to execute code. ' + ) }) - return parsedSasjsServerLog + return executionResult } /** @@ -112,9 +175,3 @@ export class SASjsApiClient { return refreshTokensForSasjs(this.requestClient, refreshToken) } } - -// todo move to sasjs/utils -export interface SASjsAuthResponse { - access_token: string - refresh_token: string -} diff --git a/src/SessionManager.ts b/src/SessionManager.ts index 1f3ff5f..bd7c535 100644 --- a/src/SessionManager.ts +++ b/src/SessionManager.ts @@ -6,6 +6,10 @@ import { RequestClient } from './request/RequestClient' const MAX_SESSION_COUNT = 1 +interface ApiErrorResponse { + response: { status: number | string; data: { message: string } } +} + export class SessionManager { private loggedErrors: NoSessionStateError[] = [] @@ -17,8 +21,10 @@ export class SessionManager { if (serverUrl) isUrl(serverUrl) } + // INFO: session pool private sessions: Session[] = [] private currentContext: Context | null = null + private settingContext: boolean = false private _debug: boolean = false private printedSessionState = { printed: false, @@ -33,71 +39,230 @@ export class SessionManager { this._debug = value } - async getSession(accessToken?: string) { - await this.createSessions(accessToken) - await this.createAndWaitForSession(accessToken) - const session = this.sessions.pop() + /** + * Checks if session is valid. Session is considered valid if time since it's creation is less than 'sessionInactiveTimeout' attribute. + * @param session - session object. + * @returns - boolean indicating if session is valid. + */ + private isSessionValid(session: Session): boolean { + if (!session) return false + const secondsSinceSessionCreation = - (new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) / + (new Date().getTime() - new Date(session.creationTimeStamp).getTime()) / 1000 if ( !session!.attributes || secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout ) { - await this.createSessions(accessToken) - const freshSession = this.sessions.pop() - - return freshSession + return false + } else { + return true } - - return session } - async clearSession(id: string, accessToken?: string) { + /** + * Removes session from pool of hot sessions. + * @param session - session object. + * @returns - void. + */ + private removeSessionFromPool(session: Session): void { + this.sessions = this.sessions.filter((ses) => ses.id !== session.id) + } + + /** + * Filters session pool to keep only valid sessions. + * @param session - session object. + * @returns - void. + */ + private removeExpiredSessions(): void { + this.sessions = this.sessions.filter((session) => + this.isSessionValid(session) + ) + } + + /** + * Throws set of errors as a single error. + * @param errors - array of errors or string. + * @param prefix - an optional final error prefix. + * @returns - never. + */ + private throwErrors(errors: (Error | string)[], prefix?: string): never { + throw prefix + ? prefixMessage(new Error(errors.join('. ')), prefix) + : new Error( + errors + .map((err) => + (err as Error).message ? (err as Error).message : err + ) + .join('. ') + ) + } + + /** + * Returns session. + * If there is a hot session available, it will be returned immediately and an asynchronous request to create new hot session will be submitted. + * If there is no available session, 2 session creation requests will be submitted. The session is returned once it is created and ready. + * @param accessToken - an optional access token. + * @returns - a promise which resolves with a session. + */ + async getSession(accessToken?: string) { + const errors: (Error | string)[] = [] + let isErrorThrown = false + + const throwIfError = () => { + if (errors.length && !isErrorThrown) { + isErrorThrown = true + + this.throwErrors(errors) + } + } + + this.removeExpiredSessions() + + if (this.sessions.length) { + const session = this.sessions[0] + + this.removeSessionFromPool(session) + + this.createSessions(accessToken).catch((err) => { + errors.push(err) + }) + + this.createAndWaitForSession(accessToken).catch((err) => { + errors.push(err) + }) + + throwIfError() + + return session + } else { + this.createSessions(accessToken).catch((err) => { + errors.push(err) + }) + + await this.createAndWaitForSession(accessToken).catch((err) => { + errors.push(err) + }) + + this.removeExpiredSessions() + + const session = this.sessions.pop()! + + this.removeSessionFromPool(session) + + throwIfError() + + return session + } + } + + /** + * Returns error message based on the response from SAS API. + * @param err - an optional access token. + * @param accessToken - an optional access token. + * @returns - an error message. + */ + private getErrorMessage( + err: ApiErrorResponse, + url: string, + method: 'GET' | 'POST' | 'DELETE' + ) { + return ( + `${method} request to ${url} failed with status code ${ + err.response.status || 'unknown' + }. ` + err.response.data.message || '' + ) + } + + /** + * Deletes session. + * @param id - a session id. + * @param accessToken - an optional access token. + * @returns - a promise which resolves when session is deleted. + */ + async clearSession(id: string, accessToken?: string): Promise { + const url = `/compute/sessions/${id}` + return await this.requestClient - .delete(`/compute/sessions/${id}`, accessToken) + .delete(url, accessToken) .then(() => { this.sessions = this.sessions.filter((s) => s.id !== id) }) - .catch((err) => { - throw prefixMessage(err, 'Error while deleting session. ') + .catch((err: ApiErrorResponse) => { + throw prefixMessage( + this.getErrorMessage(err, url, 'DELETE'), + 'Error while deleting session. ' + ) }) } - private async createSessions(accessToken?: string) { + /** + * Creates sessions in amount equal to MAX_SESSION_COUNT. + * @param accessToken - an optional access token. + * @returns - a promise which resolves when required amount of sessions is created. + */ + private async createSessions(accessToken?: string): Promise { + const errors: (Error | string)[] = [] + if (!this.sessions.length) { - if (!this.currentContext) { - await this.setCurrentContext(accessToken).catch((err) => { - throw err - }) - } - await asyncForEach(new Array(MAX_SESSION_COUNT), async () => { - const createdSession = await this.createAndWaitForSession( - accessToken - ).catch((err) => { - throw err + await this.createAndWaitForSession(accessToken).catch((err) => { + errors.push(err) }) - - this.sessions.push(createdSession) - }).catch((err) => { - throw err }) } + + if (errors.length) { + this.throwErrors(errors, 'Error while creating session. ') + } } - private async createAndWaitForSession(accessToken?: string) { + /** + * Waits for the current context to be set. + * @returns - a promise which resolves when current context is set. + */ + private async waitForCurrentContext(): Promise { + return new Promise((resolve) => { + const timer = setInterval(() => { + if (this.currentContext) { + this.settingContext = false + + clearInterval(timer) + + resolve() + } + }, 100) + }) + } + + /** + * Creates and waits for session to be ready. + * @param accessToken - an optional access token. + * @returns - a promise which resolves with a session. + */ + private async createAndWaitForSession( + accessToken?: string + ): Promise { + if (!this.currentContext) { + if (!this.settingContext) { + await this.setCurrentContext(accessToken) + } else { + await this.waitForCurrentContext() + } + } + + const url = `${this.serverUrl}/compute/contexts/${ + this.currentContext!.id + }/sessions` + const { result: createdSession, etag } = await this.requestClient - .post( - `${this.serverUrl}/compute/contexts/${ - this.currentContext!.id - }/sessions`, - {}, - accessToken - ) - .catch((err) => { - throw err + .post(url, {}, accessToken) + .catch((err: ApiErrorResponse) => { + throw prefixMessage( + this.getErrorMessage(err, url, 'POST'), + `Error while creating session. ` + ) }) await this.waitForSession(createdSession, etag, accessToken) @@ -107,14 +272,26 @@ export class SessionManager { return createdSession } - private async setCurrentContext(accessToken?: string) { + /** + * Sets current context. + * @param accessToken - an optional access token. + * @returns - a promise which resolves when current context is set. + */ + private async setCurrentContext(accessToken?: string): Promise { if (!this.currentContext) { + const url = `${this.serverUrl}/compute/contexts?limit=10000` + + this.settingContext = true + const { result: contexts } = await this.requestClient .get<{ items: Context[] - }>(`${this.serverUrl}/compute/contexts?limit=10000`, accessToken) - .catch((err) => { - throw err + }>(url, accessToken) + .catch((err: ApiErrorResponse) => { + throw prefixMessage( + this.getErrorMessage(err, url, 'GET'), + `Error while getting list of contexts. ` + ) }) const contextsList = @@ -138,18 +315,13 @@ export class SessionManager { } } - private getHeaders(accessToken?: string) { - const headers: any = { - 'Content-Type': 'application/json' - } - - if (accessToken) { - headers.Authorization = `Bearer ${accessToken}` - } - - return headers - } - + /** + * Waits for session to be ready. + * @param session - a session object. + * @param etag - an etag that can be a string or null. + * @param accessToken - an optional access token. + * @returns - a promise which resolves with a session state. + */ private async waitForSession( session: Session, etag: string | null, @@ -173,13 +345,11 @@ export class SessionManager { this.printedSessionState.printed = true } + const url = `${this.serverUrl}${stateLink.href}?wait=30` + const { result: state, responseStatus: responseStatus } = - await this.getSessionState( - `${this.serverUrl}${stateLink.href}?wait=30`, - etag!, - accessToken - ).catch((err) => { - throw prefixMessage(err, 'Error while getting session state.') + await this.getSessionState(url, etag!, accessToken).catch((err) => { + throw prefixMessage(err, 'Error while waiting for session. ') }) sessionState = state.trim() @@ -216,7 +386,7 @@ export class SessionManager { return sessionState } else { - throw 'Error while getting session state link.' + throw 'Error while getting session state link. ' } } else { this.loggedErrors = [] @@ -225,11 +395,21 @@ export class SessionManager { } } + /** + * Gets session state. + * @param url - a URL to get session state. + * @param etag - an etag string. + * @param accessToken - an optional access token. + * @returns - a promise which resolves with a result string and response status. + */ private async getSessionState( url: string, etag: string, accessToken?: string - ) { + ): Promise<{ + result: string + responseStatus: number + }> { return await this.requestClient .get(url, accessToken, 'text/plain', { 'If-None-Match': etag }) .then((res) => ({ @@ -237,20 +417,37 @@ export class SessionManager { responseStatus: res.status })) .catch((err) => { - throw err + throw prefixMessage( + this.getErrorMessage(err, url, 'GET'), + 'Error while getting session state. ' + ) }) } - async getVariable(sessionId: string, variable: string, accessToken?: string) { + /** + * Gets variable. + * @param sessionId - a session id. + * @param variable - a variable string. + * @param accessToken - an optional access token. + * @returns - a promise which resolves with a result that confirms to SessionVariable interface, etag string and status code. + */ + async getVariable( + sessionId: string, + variable: string, + accessToken?: string + ): Promise<{ + result: SessionVariable + etag: string + status: number + }> { + const url = `${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}` + return await this.requestClient - .get( - `${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}`, - accessToken - ) + .get(url, accessToken) .catch((err) => { throw prefixMessage( - err, - `Error while fetching session variable '${variable}'.` + this.getErrorMessage(err, url, 'GET'), + `Error while fetching session variable '${variable}'. ` ) }) } diff --git a/src/api/viya/executeScript.ts b/src/api/viya/executeScript.ts index 9ae65a5..af08394 100644 --- a/src/api/viya/executeScript.ts +++ b/src/api/viya/executeScript.ts @@ -12,7 +12,7 @@ import { RequestClient } from '../../request/RequestClient' import { SessionManager } from '../../SessionManager' import { isRelativePath, fetchLogByChunks } from '../../utils' import { formatDataForRequest } from '../../utils/formatDataForRequest' -import { pollJobState } from './pollJobState' +import { pollJobState, JobState } from './pollJobState' import { uploadTables } from './uploadTables' /** @@ -25,7 +25,7 @@ import { uploadTables } from './uploadTables' * @param debug - when set to true, the log will be returned. * @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code). * @param waitForResult - when set to true, function will return the session - * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }. + * @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { maxPollCount: 24 * 60 * 60, pollInterval: 1000 }. More information available at src/api/viya/pollJobState.ts. * @param printPid - a boolean that indicates whether the function should print (PID) of the started job. * @param variables - an object that represents macro variables. */ @@ -228,7 +228,7 @@ export async function executeScript( ) } - if (jobStatus === 'failed' || jobStatus === 'error') { + if (jobStatus === JobState.Failed || jobStatus === JobState.Error) { throw new ComputeJobExecutionError(currentJob, log) } diff --git a/src/api/viya/pollJobState.ts b/src/api/viya/pollJobState.ts index 0ac2597..8ea7c65 100644 --- a/src/api/viya/pollJobState.ts +++ b/src/api/viya/pollJobState.ts @@ -1,29 +1,88 @@ import { AuthConfig } from '@sasjs/utils/types' -import { Job, PollOptions } from '../..' +import { Job, PollOptions, PollStrategy } from '../..' import { getTokens } from '../../auth/getTokens' import { RequestClient } from '../../request/RequestClient' import { JobStatePollError } from '../../types/errors' import { Link, WriteStream } from '../../types' import { delay, isNode } from '../../utils' +export enum JobState { + Completed = 'completed', + Running = 'running', + Pending = 'pending', + Unavailable = 'unavailable', + NoState = '', + Failed = 'failed', + Error = 'error' +} + +/** + * Polls job status using default or provided poll options. + * @param requestClient - the pre-configured HTTP request client. + * @param postedJob - the relative or absolute path to the job. + * @param debug - sets the _debug flag in the job arguments. + * @param authConfig - an access token, refresh token, client and secret for an authorized user. + * @param pollOptions - an object containing maxPollCount, pollInterval, streamLog and logFolderPath. It will override the first default poll options in poll strategy if provided. + * Example pollOptions: + * { + * maxPollCount: 200, + * pollInterval: 300, + * streamLog: true, // optional, equals to false by default. + * pollStrategy?: // optional array of poll options that should be applied after 'maxPollCount' of the provided poll options is reached. If not provided the default (see example below) poll strategy will be used. + * } + * Example pollStrategy (values used from default poll strategy): + * [ + * { maxPollCount: 200, pollInterval: 300 }, // approximately ~2 mins (including time to get response (~300ms)) + * { maxPollCount: 300, pollInterval: 3000 }, // approximately ~5.5 mins (including time to get response (~300ms)) + * { maxPollCount: 500, pollInterval: 30000 }, // approximately ~50.5 mins (including time to get response (~300ms)) + * { maxPollCount: 3400, pollInterval: 60000 } // approximately ~3015 mins (~125 hours) (including time to get response (~300ms)) + * ] + * @returns - a promise which resolves with a job state + */ export async function pollJobState( requestClient: RequestClient, postedJob: Job, debug: boolean, authConfig?: AuthConfig, pollOptions?: PollOptions -) { +): Promise { const logger = process.logger || console - let pollInterval = 300 - let maxPollCount = 1000 + const streamLog = pollOptions?.streamLog || false - const defaultPollOptions: PollOptions = { - maxPollCount, - pollInterval, - streamLog: false + const defaultPollStrategy: PollStrategy = [ + { maxPollCount: 200, pollInterval: 300 }, + { maxPollCount: 300, pollInterval: 3000 }, + { maxPollCount: 500, pollInterval: 30000 }, + { maxPollCount: 3400, pollInterval: 60000 } + ] + + let pollStrategy: PollStrategy + + if (pollOptions !== undefined) { + pollStrategy = [pollOptions] + + let { pollStrategy: providedPollStrategy } = pollOptions + + if (providedPollStrategy !== undefined) { + validatePollStrategies(providedPollStrategy) + + // INFO: sort by 'maxPollCount' + providedPollStrategy = providedPollStrategy.sort( + (strategyA: PollOptions, strategyB: PollOptions) => + strategyA.maxPollCount - strategyB.maxPollCount + ) + + pollStrategy = [...pollStrategy, ...providedPollStrategy] + } else { + pollStrategy = [...pollStrategy, ...defaultPollStrategy] + } + } else { + pollStrategy = defaultPollStrategy } + let defaultPollOptions: PollOptions = pollStrategy.splice(0, 1)[0] + pollOptions = { ...defaultPollOptions, ...(pollOptions || {}) } const stateLink = postedJob.links.find((l: any) => l.rel === 'state') @@ -31,10 +90,10 @@ export async function pollJobState( throw new Error(`Job state link was not found.`) } - let currentState = await getJobState( + let currentState: JobState = await getJobState( requestClient, postedJob, - '', + JobState.NoState, debug, authConfig ).catch((err) => { @@ -42,73 +101,71 @@ export async function pollJobState( `Error fetching job state from ${stateLink.href}. Starting poll, assuming job to be running.`, err ) - return 'unavailable' + + return JobState.Unavailable }) let pollCount = 0 - if (currentState === 'completed') { + if (currentState === JobState.Completed) { return Promise.resolve(currentState) } let logFileStream - if (pollOptions.streamLog && isNode()) { + if (streamLog && isNode()) { const { getFileStream } = require('./getFileStream') + logFileStream = await getFileStream(postedJob, pollOptions.logFolderPath) } - // Poll up to the first 100 times with the specified poll interval let result = await doPoll( requestClient, postedJob, currentState, debug, pollCount, + pollOptions, authConfig, - { - ...pollOptions, - maxPollCount: - pollOptions.maxPollCount <= 100 ? pollOptions.maxPollCount : 100 - }, + streamLog, logFileStream ) currentState = result.state pollCount = result.pollCount - if (!needsRetry(currentState) || pollCount >= pollOptions.maxPollCount) { + if ( + !needsRetry(currentState) || + (pollCount >= pollOptions.maxPollCount && !pollStrategy.length) + ) { return currentState } - // If we get to this point, this is a long-running job that needs longer polling. - // We will resume polling with a bigger interval of 1 minute - let longJobPollOptions: PollOptions = { - maxPollCount: 24 * 60, - pollInterval: 60000, - streamLog: false - } - if (pollOptions) { - longJobPollOptions.streamLog = pollOptions.streamLog - longJobPollOptions.logFolderPath = pollOptions.logFolderPath + // INFO: If we get to this point, this is a long-running job that needs longer polling. + // We will resume polling with a bigger interval according to the next polling strategy + while (pollStrategy.length && needsRetry(currentState)) { + defaultPollOptions = pollStrategy.splice(0, 1)[0] + + if (pollOptions) { + defaultPollOptions.logFolderPath = pollOptions.logFolderPath + } + + result = await doPoll( + requestClient, + postedJob, + currentState, + debug, + pollCount, + defaultPollOptions, + authConfig, + streamLog, + logFileStream + ) + + currentState = result.state + pollCount = result.pollCount } - result = await doPoll( - requestClient, - postedJob, - currentState, - debug, - pollCount, - authConfig, - longJobPollOptions, - logFileStream - ) - - currentState = result.state - pollCount = result.pollCount - - if (logFileStream) { - logFileStream.end() - } + if (logFileStream) logFileStream.end() return currentState } @@ -119,17 +176,13 @@ const getJobState = async ( currentState: string, debug: boolean, authConfig?: AuthConfig -) => { - const stateLink = job.links.find((l: any) => l.rel === 'state') - if (!stateLink) { - throw new Error(`Job state link was not found.`) - } +): Promise => { + const stateLink = job.links.find((l: any) => l.rel === 'state')! if (needsRetry(currentState)) { let tokens - if (authConfig) { - tokens = await getTokens(requestClient, authConfig) - } + + if (authConfig) tokens = await getTokens(requestClient, authConfig) const { result: jobState } = await requestClient .get( @@ -143,48 +196,38 @@ const getJobState = async ( throw new JobStatePollError(job.id, err) }) - return jobState.trim() + return jobState.trim() as JobState } else { - return currentState + return currentState as JobState } } const needsRetry = (state: string) => - state === 'running' || - state === '' || - state === 'pending' || - state === 'unavailable' + state === JobState.Running || + state === JobState.NoState || + state === JobState.Pending || + state === JobState.Unavailable const doPoll = async ( requestClient: RequestClient, postedJob: Job, - currentState: string, + currentState: JobState, debug: boolean, pollCount: number, + pollOptions: PollOptions, authConfig?: AuthConfig, - pollOptions?: PollOptions, + streamLog?: boolean, logStream?: WriteStream -): Promise<{ state: string; pollCount: number }> => { - let pollInterval = 300 - let maxPollCount = 1000 +): Promise<{ state: JobState; pollCount: number }> => { + const { maxPollCount, pollInterval } = pollOptions + const logger = process.logger || console + const stateLink = postedJob.links.find((l: Link) => l.rel === 'state')! let maxErrorCount = 5 let errorCount = 0 let state = currentState - let printedState = '' + let printedState = JobState.NoState let startLogLine = 0 - const logger = process.logger || console - - if (pollOptions) { - pollInterval = pollOptions.pollInterval || pollInterval - maxPollCount = pollOptions.maxPollCount || maxPollCount - } - - const stateLink = postedJob.links.find((l: Link) => l.rel === 'state') - if (!stateLink) { - throw new Error(`Job state link was not found.`) - } - while (needsRetry(state) && pollCount <= maxPollCount) { state = await getJobState( requestClient, @@ -194,21 +237,24 @@ const doPoll = async ( authConfig ).catch((err) => { errorCount++ + if (pollCount >= maxPollCount || errorCount >= maxErrorCount) { throw err } + logger.error( `Error fetching job state from ${stateLink.href}. Resuming poll, assuming job to be running.`, err ) - return 'unavailable' + + return JobState.Unavailable }) pollCount++ const jobHref = postedJob.links.find((l: Link) => l.rel === 'self')!.href - if (pollOptions?.streamLog) { + if (streamLog) { const { result: job } = await requestClient.get( jobHref, authConfig?.access_token @@ -238,12 +284,45 @@ const doPoll = async ( printedState = state } - if (state != 'unavailable' && errorCount > 0) { + if (state !== JobState.Unavailable && errorCount > 0) { errorCount = 0 } - await delay(pollInterval) + if (state !== JobState.Completed) { + await delay(pollInterval) + } } return { state, pollCount } } + +const validatePollStrategies = (strategy: PollStrategy) => { + const throwError = (message?: string, pollOptions?: PollOptions) => { + throw new Error( + `Poll strategies are not valid.${message ? ` ${message}` : ''}${ + pollOptions + ? ` Invalid poll strategy: \n${JSON.stringify(pollOptions, null, 2)}` + : '' + }` + ) + } + + strategy.forEach((pollOptions: PollOptions, i: number) => { + const { maxPollCount, pollInterval } = pollOptions + + if (maxPollCount < 1) { + throwError(`'maxPollCount' has to be greater than 0.`, pollOptions) + } else if (i !== 0) { + const previousPollOptions = strategy[i - 1] + + if (maxPollCount <= previousPollOptions.maxPollCount) { + throwError( + `'maxPollCount' has to be greater than 'maxPollCount' in previous poll strategy.`, + pollOptions + ) + } + } else if (pollInterval < 1) { + throwError(`'pollInterval' has to be greater than 0.`, pollOptions) + } + }) +} diff --git a/src/api/viya/spec/executeScript.spec.ts b/src/api/viya/spec/executeScript.spec.ts index 0df1512..39a08d8 100644 --- a/src/api/viya/spec/executeScript.spec.ts +++ b/src/api/viya/spec/executeScript.spec.ts @@ -9,14 +9,13 @@ import * as formatDataModule from '../../../utils/formatDataForRequest' import * as fetchLogsModule from '../../../utils/fetchLogByChunks' import { PollOptions } from '../../../types' import { ComputeJobExecutionError, NotFoundError } from '../../../types/errors' -import { Logger, LogLevel } from '@sasjs/utils' +import { Logger, LogLevel } from '@sasjs/utils/logger' const sessionManager = new (>SessionManager)() const requestClient = new (>RequestClient)() const defaultPollOptions: PollOptions = { maxPollCount: 100, - pollInterval: 500, - streamLog: false + pollInterval: 500 } describe('executeScript', () => { @@ -452,7 +451,9 @@ describe('executeScript', () => { it('should throw a ComputeJobExecutionError if the job has failed', async () => { jest .spyOn(pollJobStateModule, 'pollJobState') - .mockImplementation(() => Promise.resolve('failed')) + .mockImplementation(() => + Promise.resolve(pollJobStateModule.JobState.Failed) + ) const error: ComputeJobExecutionError = await executeScript( requestClient, @@ -485,7 +486,9 @@ describe('executeScript', () => { it('should throw a ComputeJobExecutionError if the job has errored out', async () => { jest .spyOn(pollJobStateModule, 'pollJobState') - .mockImplementation(() => Promise.resolve('error')) + .mockImplementation(() => + Promise.resolve(pollJobStateModule.JobState.Error) + ) const error: ComputeJobExecutionError = await executeScript( requestClient, @@ -654,7 +657,9 @@ const setupMocks = () => { .mockImplementation(() => Promise.resolve(mockAuthConfig)) jest .spyOn(pollJobStateModule, 'pollJobState') - .mockImplementation(() => Promise.resolve('completed')) + .mockImplementation(() => + Promise.resolve(pollJobStateModule.JobState.Completed) + ) jest .spyOn(sessionManager, 'getVariable') .mockImplementation(() => diff --git a/src/api/viya/spec/pollJobState.spec.ts b/src/api/viya/spec/pollJobState.spec.ts index 9a07741..4dfb07e 100644 --- a/src/api/viya/spec/pollJobState.spec.ts +++ b/src/api/viya/spec/pollJobState.spec.ts @@ -1,4 +1,4 @@ -import { Logger, LogLevel } from '@sasjs/utils' +import { Logger, LogLevel } from '@sasjs/utils/logger' import { RequestClient } from '../../../request/RequestClient' import { mockAuthConfig, mockJob } from './mockResponses' import { pollJobState } from '../pollJobState' @@ -6,17 +6,18 @@ import * as getTokensModule from '../../../auth/getTokens' import * as saveLogModule from '../saveLog' import * as getFileStreamModule from '../getFileStream' import * as isNodeModule from '../../../utils/isNode' -import { PollOptions } from '../../../types' +import * as delayModule from '../../../utils/delay' +import { PollOptions, PollStrategy } from '../../../types' import { WriteStream } from 'fs' const baseUrl = 'http://localhost' const requestClient = new (>RequestClient)() requestClient['httpClient'].defaults.baseURL = baseUrl -const defaultPollOptions: PollOptions = { +const defaultStreamLog = false +const defaultPollStrategy: PollOptions = { maxPollCount: 100, - pollInterval: 500, - streamLog: false + pollInterval: 500 } describe('pollJobState', () => { @@ -26,13 +27,10 @@ describe('pollJobState', () => { }) it('should get valid tokens if the authConfig has been provided', async () => { - await pollJobState( - requestClient, - mockJob, - false, - mockAuthConfig, - defaultPollOptions - ) + await pollJobState(requestClient, mockJob, false, mockAuthConfig, { + ...defaultPollStrategy, + streamLog: defaultStreamLog + }) expect(getTokensModule.getTokens).toHaveBeenCalledWith( requestClient, @@ -46,7 +44,7 @@ describe('pollJobState', () => { mockJob, false, undefined, - defaultPollOptions + defaultPollStrategy ) expect(getTokensModule.getTokens).not.toHaveBeenCalled() @@ -58,7 +56,7 @@ describe('pollJobState', () => { { ...mockJob, links: mockJob.links.filter((l) => l.rel !== 'state') }, false, undefined, - defaultPollOptions + defaultPollStrategy ).catch((e: any) => e) expect((error as Error).message).toContain('Job state link was not found.') @@ -72,7 +70,7 @@ describe('pollJobState', () => { mockJob, false, mockAuthConfig, - defaultPollOptions + defaultPollStrategy ) expect(getTokensModule.getTokens).toHaveBeenCalledTimes(3) @@ -83,7 +81,7 @@ describe('pollJobState', () => { const { saveLog } = require('../saveLog') await pollJobState(requestClient, mockJob, false, mockAuthConfig, { - ...defaultPollOptions, + ...defaultPollStrategy, streamLog: true }) @@ -96,7 +94,7 @@ describe('pollJobState', () => { const { saveLog } = require('../saveLog') await pollJobState(requestClient, mockJob, false, mockAuthConfig, { - ...defaultPollOptions, + ...defaultPollStrategy, streamLog: true }) @@ -111,7 +109,7 @@ describe('pollJobState', () => { const { getFileStream } = require('../getFileStream') await pollJobState(requestClient, mockJob, false, mockAuthConfig, { - ...defaultPollOptions, + ...defaultPollStrategy, streamLog: true }) @@ -127,7 +125,7 @@ describe('pollJobState', () => { mockJob, false, mockAuthConfig, - defaultPollOptions + defaultPollStrategy ) expect(saveLogModule.saveLog).not.toHaveBeenCalled() @@ -136,15 +134,18 @@ describe('pollJobState', () => { it('should return the current status when the max poll count is reached', async () => { mockRunningPoll() + const pollOptions: PollOptions = { + ...defaultPollStrategy, + maxPollCount: 1, + pollStrategy: [] + } + const state = await pollJobState( requestClient, mockJob, false, mockAuthConfig, - { - ...defaultPollOptions, - maxPollCount: 1 - } + pollOptions ) expect(state).toEqual('running') @@ -159,7 +160,7 @@ describe('pollJobState', () => { false, mockAuthConfig, { - ...defaultPollOptions, + ...defaultPollStrategy, maxPollCount: 200, pollInterval: 10 } @@ -176,7 +177,7 @@ describe('pollJobState', () => { mockJob, false, undefined, - defaultPollOptions + defaultPollStrategy ) expect(requestClient.get).toHaveBeenCalledTimes(2) @@ -192,7 +193,7 @@ describe('pollJobState', () => { mockJob, true, undefined, - defaultPollOptions + defaultPollStrategy ) expect((process as any).logger.info).toHaveBeenCalledTimes(4) @@ -222,7 +223,7 @@ describe('pollJobState', () => { mockJob, false, undefined, - defaultPollOptions + defaultPollStrategy ) expect(requestClient.get).toHaveBeenCalledTimes(2) @@ -237,13 +238,119 @@ describe('pollJobState', () => { mockJob, false, undefined, - defaultPollOptions + defaultPollStrategy ).catch((e: any) => e) expect(error.message).toEqual( 'Error while polling job state for job j0b: Status Error' ) }) + + it('should change poll strategies', async () => { + mockSimplePoll(6) + + const delays: number[] = [] + + jest.spyOn(delayModule, 'delay').mockImplementation((ms: number) => { + delays.push(ms) + + return Promise.resolve() + }) + + const pollIntervals = [3, 4, 5, 6] + + const pollStrategy = [ + { maxPollCount: 2, pollInterval: pollIntervals[1] }, + { maxPollCount: 3, pollInterval: pollIntervals[2] }, + { maxPollCount: 4, pollInterval: pollIntervals[3] } + ] + + const pollOptions: PollOptions = { + maxPollCount: 1, + pollInterval: pollIntervals[0], + pollStrategy: pollStrategy + } + + await pollJobState(requestClient, mockJob, false, undefined, pollOptions) + + expect(delays).toEqual([pollIntervals[0], ...pollIntervals]) + }) + + it('should throw an error if not valid poll strategies provided', async () => { + // INFO: 'maxPollCount' has to be > 0 + let invalidPollStrategy = { + maxPollCount: 0, + pollInterval: 3 + } + + let pollStrategy: PollStrategy = [invalidPollStrategy] + + let expectedError = new Error( + `Poll strategies are not valid. 'maxPollCount' has to be greater than 0. Invalid poll strategy: \n${JSON.stringify( + invalidPollStrategy, + null, + 2 + )}` + ) + + await expect( + pollJobState(requestClient, mockJob, false, undefined, { + ...defaultPollStrategy, + pollStrategy: pollStrategy + }) + ).rejects.toThrow(expectedError) + + // INFO: 'maxPollCount' has to be > than 'maxPollCount' of the previous strategy + const validPollStrategy = { + maxPollCount: 5, + pollInterval: 2 + } + + invalidPollStrategy = { + maxPollCount: validPollStrategy.maxPollCount, + pollInterval: 3 + } + + pollStrategy = [validPollStrategy, invalidPollStrategy] + + expectedError = new Error( + `Poll strategies are not valid. 'maxPollCount' has to be greater than 'maxPollCount' in previous poll strategy. Invalid poll strategy: \n${JSON.stringify( + invalidPollStrategy, + null, + 2 + )}` + ) + + await expect( + pollJobState(requestClient, mockJob, false, undefined, { + ...defaultPollStrategy, + pollStrategy: pollStrategy + }) + ).rejects.toThrow(expectedError) + + // INFO: invalid 'pollInterval' + invalidPollStrategy = { + maxPollCount: 1, + pollInterval: 0 + } + + pollStrategy = [invalidPollStrategy] + + expectedError = new Error( + `Poll strategies are not valid. 'pollInterval' has to be greater than 0. Invalid poll strategy: \n${JSON.stringify( + invalidPollStrategy, + null, + 2 + )}` + ) + + await expect( + pollJobState(requestClient, mockJob, false, undefined, { + ...defaultPollStrategy, + pollStrategy: pollStrategy + }) + ).rejects.toThrow(expectedError) + }) }) const setupMocks = () => { @@ -273,11 +380,14 @@ const setupMocks = () => { const mockSimplePoll = (runningCount = 2) => { let count = 0 + jest.spyOn(requestClient, 'get').mockImplementation((url) => { count++ + if (url.includes('job')) { return Promise.resolve({ result: mockJob, etag: '', status: 200 }) } + return Promise.resolve({ result: count === 0 @@ -293,11 +403,14 @@ const mockSimplePoll = (runningCount = 2) => { const mockRunningPoll = () => { let count = 0 + jest.spyOn(requestClient, 'get').mockImplementation((url) => { count++ + if (url.includes('job')) { return Promise.resolve({ result: mockJob, etag: '', status: 200 }) } + return Promise.resolve({ result: count === 0 ? 'pending' : 'running', etag: '', @@ -308,11 +421,14 @@ const mockRunningPoll = () => { const mockLongPoll = () => { let count = 0 + jest.spyOn(requestClient, 'get').mockImplementation((url) => { count++ + if (url.includes('job')) { return Promise.resolve({ result: mockJob, etag: '', status: 200 }) } + return Promise.resolve({ result: count <= 102 ? 'running' : 'completed', etag: '', @@ -323,14 +439,18 @@ const mockLongPoll = () => { const mockPollWithSingleError = () => { let count = 0 + jest.spyOn(requestClient, 'get').mockImplementation((url) => { count++ + if (url.includes('job')) { return Promise.resolve({ result: mockJob, etag: '', status: 200 }) } + if (count === 1) { return Promise.reject('Status Error') } + return Promise.resolve({ result: count === 0 ? 'pending' : 'completed', etag: '', @@ -344,6 +464,7 @@ const mockErroredPoll = () => { if (url.includes('job')) { return Promise.resolve({ result: mockJob, etag: '', status: 200 }) } + return Promise.reject('Status Error') }) } diff --git a/src/api/viya/spec/saveLog.spec.ts b/src/api/viya/spec/saveLog.spec.ts index ad24f89..bd89306 100644 --- a/src/api/viya/spec/saveLog.spec.ts +++ b/src/api/viya/spec/saveLog.spec.ts @@ -1,4 +1,4 @@ -import { Logger, LogLevel } from '@sasjs/utils' +import { Logger, LogLevel } from '@sasjs/utils/logger' import { RequestClient } from '../../../request/RequestClient' import * as fetchLogsModule from '../../../utils/fetchLogByChunks' import * as writeStreamModule from '../writeStream' diff --git a/src/api/viya/spec/writeStream.spec.ts b/src/api/viya/spec/writeStream.spec.ts index 4185e8c..95c817e 100644 --- a/src/api/viya/spec/writeStream.spec.ts +++ b/src/api/viya/spec/writeStream.spec.ts @@ -5,7 +5,7 @@ import { fileExists, readFile, deleteFile -} from '@sasjs/utils' +} from '@sasjs/utils/file' describe('writeStream', () => { const filename = 'test.txt' diff --git a/src/auth/AuthManager.ts b/src/auth/AuthManager.ts index 77fab8b..1686dc6 100644 --- a/src/auth/AuthManager.ts +++ b/src/auth/AuthManager.ts @@ -1,16 +1,19 @@ import { ServerType } from '@sasjs/utils/types' import { RequestClient } from '../request/RequestClient' -import { LoginOptions, LoginResult } from '../types/Login' +import { NotFoundError } from '../types/errors' +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` + private redirectedLoginUrl = `/SASLogon` //SAS 9 M8 no longer redirects from `/SASLogon/home` to the login page. `/SASLogon` seems to be stable enough across SAS versions constructor( private serverUrl: string, private serverType: ServerType, @@ -33,15 +36,22 @@ export class AuthManager { public async redirectedLogIn({ onLoggedOut }: LoginOptions): Promise { - const { isLoggedIn: isLoggedInAlready, userName: currentSessionUsername } = - await this.fetchUserName() + const { + isLoggedIn: isLoggedInAlready, + userName: currentSessionUserName, + userLongName: currentSessionUserLongName + } = await this.fetchUserName() if (isLoggedInAlready) { + const logger = process.logger || console + logger.log('login was not attempted as a valid session already exists') + await this.loginCallback() return { isLoggedIn: true, - userName: currentSessionUsername + userName: currentSessionUserName, + userLongName: currentSessionUserLongName } } @@ -56,7 +66,7 @@ export class AuthManager { ) if (!loginPopup) { - return { isLoggedIn: false, userName: '' } + return { isLoggedIn: false, userName: '', userLongName: '' } } const { isLoggedIn } = @@ -71,14 +81,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 +103,29 @@ 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() + const logger = process.logger || console + logger.log('login was not attempted as a valid session already exists') - this.userName = currentSessionUsername! - return { - isLoggedIn: true, - userName: this.userName - } - } else { - await this.logOut() - loginForm = await this.getNewLoginForm() + await this.loginCallback() + + 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) @@ -126,12 +138,13 @@ export class AuthManager { loginResponse = await this.sendLoginRequest(newLoginForm, loginParams) } + // Sometimes due to redirection on SAS9 and SASViya we don't get the login response that says + // You have signed in. Therefore, we have to make an extra request for checking session to + // ensure either user is logged in or not. + 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) { @@ -150,21 +163,25 @@ export class AuthManager { } this.loginCallback() - } else this.userName = '' + this.userName = loginParams.username + } return { isLoggedIn, - userName: this.userName + userName: this.userName, + userLongName: this.userLongName } } private async performCASSecurityCheck() { const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check` - - return await this.requestClient.get( - `/SASLogon/login?service=${casAuthenticationUrl}`, - undefined - ) + + return await this.requestClient + .get(`/SASLogon/login?service=${casAuthenticationUrl}`, undefined) + .catch((err) => { + // ignore if resource not found error + if (!(err instanceof NotFoundError)) throw err + }) } private async sendLoginRequest( @@ -205,15 +222,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 { + const { isLoggedIn, userName, userLongName } = await this.fetchUserName() let loginForm = null if (!isLoggedIn) { @@ -226,7 +240,8 @@ export class AuthManager { return Promise.resolve({ isLoggedIn, - userName: userName.toLowerCase(), + userName, + userLongName, loginForm }) } @@ -247,7 +262,7 @@ export class AuthManager { } const { result: formResponse } = await this.requestClient.get( - this.loginUrl.replace('.do', ''), + this.loginUrl.replace('/SASLogon/login.do', '/SASLogon/login'), undefined, 'text/plain' ) @@ -255,10 +270,7 @@ export class AuthManager { return await this.getLoginForm(formResponse) } - private async fetchUserName(): Promise<{ - isLoggedIn: boolean - userName: string - }> { + private async fetchUserName(): Promise { const url = this.serverType === ServerType.SasViya ? `${this.serverUrl}/identities/users/@currentUser` @@ -273,15 +285,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 => { @@ -290,15 +306,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 @@ -309,13 +317,31 @@ 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 = // + const pattern: RegExp = // const matches = pattern.exec(response) const formInputs: any = {} if (matches && matches.length) { this.setLoginUrl(matches) + response = response.replace(/]*>/g) if (inputs) { @@ -346,7 +372,7 @@ export class AuthManager { this.loginUrl = this.serverType === ServerType.SasViya ? tempLoginLink - : loginUrl.replace('.do', '') + : loginUrl.replace('/SASLogon/login.do', '/SASLogon/login') } } diff --git a/src/auth/getAccessTokenForSasjs.ts b/src/auth/getAccessTokenForSasjs.ts index 7b080bf..9f78dff 100644 --- a/src/auth/getAccessTokenForSasjs.ts +++ b/src/auth/getAccessTokenForSasjs.ts @@ -1,5 +1,7 @@ import { prefixMessage } from '@sasjs/utils/error' import { RequestClient } from '../request/RequestClient' +import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix' +import { ServerType } from '@sasjs/utils/types' /** * Exchanges the auth code for an access token for the given client. @@ -31,6 +33,16 @@ export async function getAccessTokenForSasjs( } }) .catch((err) => { - throw prefixMessage(err, 'Error while getting access token. ') + throw prefixMessage( + err, + getTokenRequestErrorPrefix( + 'fetching access token', + 'getAccessTokenForSasjs', + ServerType.Sasjs, + url, + data, + clientId + ) + ) }) } diff --git a/src/auth/getAccessTokenForViya.ts b/src/auth/getAccessTokenForViya.ts index 508c767..2c2748e 100644 --- a/src/auth/getAccessTokenForViya.ts +++ b/src/auth/getAccessTokenForViya.ts @@ -1,11 +1,12 @@ -import { SasAuthResponse } from '@sasjs/utils/types' +import { SasAuthResponse, ServerType } from '@sasjs/utils/types' import { prefixMessage } from '@sasjs/utils/error' import { RequestClient } from '../request/RequestClient' import { CertificateError } from '../types/errors' +import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix' /** - * Exchanges the auth code for an access token for the given client. - * @param requestClient - the pre-configured HTTP request client + * Exchange the auth code for access / refresh tokens for the given client / secret pair. + * @param requestClient - the pre-configured HTTP request client. * @param clientId - the client ID to authenticate with. * @param clientSecret - the client secret to authenticate with. * @param authCode - the auth code received from the server. @@ -16,29 +17,44 @@ export async function getAccessTokenForViya( clientSecret: string, authCode: string ): Promise { - const url = '/SASLogon/oauth/token' let token + if (typeof Buffer === 'undefined') { token = btoa(clientId + ':' + clientSecret) } else { token = Buffer.from(clientId + ':' + clientSecret).toString('base64') } + + const url = '/SASLogon/oauth/token' const headers = { Authorization: 'Basic ' + token, Accept: 'application/json' } - const data = new URLSearchParams({ + const dataJson = new URLSearchParams({ grant_type: 'authorization_code', code: authCode }) + const data = new URLSearchParams(dataJson) const authResponse = await requestClient .post(url, data, undefined, 'application/x-www-form-urlencoded', headers) .then((res) => res.result as SasAuthResponse) .catch((err) => { if (err instanceof CertificateError) throw err - throw prefixMessage(err, 'Error while getting access token. ') + throw prefixMessage( + err, + getTokenRequestErrorPrefix( + 'fetching access token', + 'getAccessTokenForViya', + ServerType.SasViya, + url, + dataJson, + headers, + clientId, + clientSecret + ) + ) }) return authResponse diff --git a/src/auth/getTokenRequestErrorPrefix.ts b/src/auth/getTokenRequestErrorPrefix.ts new file mode 100644 index 0000000..bf2b313 --- /dev/null +++ b/src/auth/getTokenRequestErrorPrefix.ts @@ -0,0 +1,88 @@ +import { ServerType } from '@sasjs/utils/types' + +type Server = ServerType.SasViya | ServerType.Sasjs +type Operation = 'fetching access token' | 'refreshing tokens' + +const getServerName = (server: Server) => + server === ServerType.SasViya ? 'Viya' : 'Sasjs' + +const getResponseTitle = (server: Server) => + `Response from ${getServerName(server)} is below.` + +/** + * Forms error prefix for requests related to token operations. + * @param operation - string describing operation ('fetching access token' or 'refreshing tokens'). + * @param funcName - name of the function sent the request. + * @param server - server type (SASVIYA or SASJS). + * @param url - endpoint used to send the request. + * @param data - request payload. + * @param headers - request headers. + * @param clientId - client ID to authenticate with. + * @param clientSecret - client secret to authenticate with. + * @returns - string containing request information. Example: + * Error while fetching access token from /SASLogon/oauth/token + * Thrown by the @sasjs/adapter getAccessTokenForViya function. + * Payload: + * { + * "grant_type": "authorization_code", + * "code": "example_code" + * } + * Headers: + * { + * "Authorization": "Basic NEdMQXBwOjRHTEFwcDE=", + * "Accept": "application/json" + * } + * ClientId: exampleClientId + * ClientSecret: exampleClientSecret + * + * Response from Viya is below. + * Auth error: { + * "error": "invalid_token", + * "error_description": "No scopes were granted" + * } + */ +export const getTokenRequestErrorPrefix = ( + operation: Operation, + funcName: string, + server: Server, + url: string, + data?: {}, + headers?: {}, + clientId?: string, + clientSecret?: string +) => { + const stringify = (obj: {}) => JSON.stringify(obj, null, 2) + + const lines = [ + `Error while ${operation} from ${url}`, + `Thrown by the @sasjs/adapter ${funcName} function.` + ] + + if (data) { + lines.push('Payload:') + lines.push(stringify(data)) + } + if (headers) { + lines.push('Headers:') + lines.push(stringify(headers)) + } + if (clientId) lines.push(`ClientId: ${clientId}`) + if (clientSecret) lines.push(`ClientSecret: ${clientSecret}`) + + lines.push('') + lines.push(`${getResponseTitle(server)}`) + lines.push('') + + return lines.join(`\n`) +} + +/** + * Parse error prefix to get response payload. + * @param prefix - error prefix generated by getTokenRequestErrorPrefix function. + * @param server - server type (SASVIYA or SASJS). + * @returns - response payload. + */ +export const getTokenRequestErrorPrefixResponse = ( + prefix: string, + server: ServerType.SasViya | ServerType.Sasjs +) => prefix.split(`${getResponseTitle(server)}\n`).pop() as string diff --git a/src/auth/getTokens.ts b/src/auth/getTokens.ts index 9dc0c1c..a655817 100644 --- a/src/auth/getTokens.ts +++ b/src/auth/getTokens.ts @@ -10,6 +10,7 @@ import { refreshTokensForSasjs } from './refreshTokensForSasjs' /** * Returns the auth configuration, refreshing the tokens if necessary. + * This function can only be used by Node, if a server type is SASVIYA. * @param requestClient - the pre-configured HTTP request client * @param authConfig - an object containing a client ID, secret, access token and refresh token * @param serverType - server type for which refreshing the tokens, defaults to SASVIYA @@ -21,6 +22,7 @@ export async function getTokens( ): Promise { const logger = process.logger || console let { access_token, refresh_token, client, secret } = authConfig + if ( isAccessTokenExpiring(access_token) || isRefreshTokenExpiring(refresh_token) @@ -28,10 +30,14 @@ export async function getTokens( if (hasTokenExpired(refresh_token)) { const error = 'Unable to obtain new access token. Your refresh token has expired.' + logger.error(error) + throw new Error(error) } + logger.info('Refreshing access and refresh tokens.') + const tokens = serverType === ServerType.SasViya ? await refreshTokensForViya( @@ -43,5 +49,6 @@ export async function getTokens( : await refreshTokensForSasjs(requestClient, refresh_token) ;({ access_token, refresh_token } = tokens) } + return { access_token, refresh_token, client, secret } } diff --git a/src/auth/isLoginRequired.ts b/src/auth/isLoginRequired.ts index b6d484a..7bd0886 100644 --- a/src/auth/isLoginRequired.ts +++ b/src/auth/isLoginRequired.ts @@ -1,5 +1,5 @@ export const isLogInRequired = (response: string): boolean => { - const pattern: RegExp = //gm + const pattern: RegExp = //gm const matches = pattern.test(response) return matches } diff --git a/src/auth/refreshTokensForSasjs.ts b/src/auth/refreshTokensForSasjs.ts index bf4818c..807d58e 100644 --- a/src/auth/refreshTokensForSasjs.ts +++ b/src/auth/refreshTokensForSasjs.ts @@ -1,5 +1,7 @@ import { prefixMessage } from '@sasjs/utils/error' import { RequestClient } from '../request/RequestClient' +import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix' +import { ServerType } from '@sasjs/utils/types' /** * Exchanges the refresh token for an access token for the given client. @@ -28,7 +30,15 @@ export async function refreshTokensForSasjs( } }) .catch((err) => { - throw prefixMessage(err, 'Error while refreshing tokens: ') + throw prefixMessage( + err, + getTokenRequestErrorPrefix( + 'refreshing tokens', + 'refreshTokensForSasjs', + ServerType.Sasjs, + url + ) + ) }) return authResponse diff --git a/src/auth/refreshTokensForViya.ts b/src/auth/refreshTokensForViya.ts index e10d15c..2ed0c3a 100644 --- a/src/auth/refreshTokensForViya.ts +++ b/src/auth/refreshTokensForViya.ts @@ -1,10 +1,13 @@ -import { SasAuthResponse } from '@sasjs/utils/types' +import { SasAuthResponse, ServerType } from '@sasjs/utils/types' import { prefixMessage } from '@sasjs/utils/error' import * as NodeFormData from 'form-data' import { RequestClient } from '../request/RequestClient' +import { isNode } from '../utils' +import { getTokenRequestErrorPrefix } from './getTokenRequestErrorPrefix' /** * Exchanges the refresh token for an access token for the given client. + * This function can only be used by Node. * @param requestClient - the pre-configured HTTP request client * @param clientId - the client ID to authenticate with. * @param clientSecret - the client secret to authenticate with. @@ -16,9 +19,12 @@ export async function refreshTokensForViya( clientSecret: string, refreshToken: string ) { + if (!isNode()) { + throw new Error(`Method 'refreshTokensForViya' can only be used by Node.`) + } + const url = '/SASLogon/oauth/token' - let token - token = + const token = typeof Buffer === 'undefined' ? btoa(clientId + ':' + clientSecret) : Buffer.from(clientId + ':' + clientSecret).toString('base64') @@ -27,8 +33,7 @@ export async function refreshTokensForViya( Authorization: 'Basic ' + token } - const formData = - typeof FormData === 'undefined' ? new NodeFormData() : new FormData() + const formData = new NodeFormData() formData.append('grant_type', 'refresh_token') formData.append('refresh_token', refreshToken) @@ -42,7 +47,19 @@ export async function refreshTokensForViya( ) .then((res) => res.result) .catch((err) => { - throw prefixMessage(err, 'Error while refreshing tokens: ') + throw prefixMessage( + err, + getTokenRequestErrorPrefix( + 'refreshing tokens', + 'refreshTokensForViya', + ServerType.SasViya, + url, + formData, + headers, + clientId, + clientSecret + ) + ) }) return authResponse diff --git a/src/auth/spec/AuthManager.spec.ts b/src/auth/spec/AuthManager.spec.ts index b17a352..1eb7392 100644 --- a/src/auth/spec/AuthManager.spec.ts +++ b/src/auth/spec/AuthManager.spec.ts @@ -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(authManager, 'getNewLoginForm') - .mockImplementation(() => - Promise.resolve({ - name: 'test' - }) - ) - mockedAxios.post.mockImplementation(() => - Promise.resolve({ data: mockLoginSuccessResponse }) - ) + jest.spyOn(authManager, 'getNewLoginForm') + jest.spyOn(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' } }) ) @@ -384,7 +365,7 @@ describe('AuthManager', () => { expect(loginResponse.userName).toEqual(userName) expect(openWebPageModule.openWebPage).toHaveBeenCalledWith( - `/SASLogon/home`, + `/SASLogon`, 'SASLogon', { width: 500, @@ -428,7 +409,7 @@ describe('AuthManager', () => { expect(loginResponse.userName).toEqual(userName) expect(openWebPageModule.openWebPage).toHaveBeenCalledWith( - `/SASLogon/home`, + `/SASLogon`, 'SASLogon', { width: 500, @@ -472,7 +453,7 @@ describe('AuthManager', () => { expect(loginResponse.userName).toEqual('') expect(openWebPageModule.openWebPage).toHaveBeenCalledWith( - `/SASLogon/home`, + `/SASLogon`, 'SASLogon', { width: 500, @@ -516,7 +497,7 @@ describe('AuthManager', () => { expect(loginResponse.userName).toEqual('') expect(openWebPageModule.openWebPage).toHaveBeenCalledWith( - `/SASLogon/home`, + `/SASLogon`, 'SASLogon', { width: 500, @@ -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`, diff --git a/src/auth/spec/getAccessTokenForSasjs.spec.ts b/src/auth/spec/getAccessTokenForSasjs.spec.ts index e70ab39..a9f594e 100644 --- a/src/auth/spec/getAccessTokenForSasjs.spec.ts +++ b/src/auth/spec/getAccessTokenForSasjs.spec.ts @@ -1,4 +1,4 @@ -import { AuthConfig } from '@sasjs/utils' +import { AuthConfig } from '@sasjs/utils/types' import { generateToken, mockSasjsAuthResponse } from './mockResponses' import { RequestClient } from '../../request/RequestClient' import { getAccessTokenForSasjs } from '../getAccessTokenForSasjs' @@ -55,7 +55,7 @@ describe('getAccessTokenForSasjs', () => { authConfig.refresh_token ).catch((e: any) => e) - expect(error).toContain('Error while getting access token') + expect(error).toContain('Error while fetching access token') }) }) diff --git a/src/auth/spec/getAccessTokenForViya.spec.ts b/src/auth/spec/getAccessTokenForViya.spec.ts index 01ea181..475a6d7 100644 --- a/src/auth/spec/getAccessTokenForViya.spec.ts +++ b/src/auth/spec/getAccessTokenForViya.spec.ts @@ -1,4 +1,4 @@ -import { AuthConfig } from '@sasjs/utils' +import { AuthConfig } from '@sasjs/utils/types' import * as NodeFormData from 'form-data' import { generateToken, mockAuthResponse } from './mockResponses' import { RequestClient } from '../../request/RequestClient' @@ -66,7 +66,7 @@ describe('getAccessTokenForViya', () => { authConfig.refresh_token ).catch((e: any) => e) - expect(error).toContain('Error while getting access token') + expect(error).toContain('Error while fetching access token') }) }) diff --git a/src/auth/spec/getTokenRequestErrorPrefix.spec.ts b/src/auth/spec/getTokenRequestErrorPrefix.spec.ts new file mode 100644 index 0000000..6c0ccd9 --- /dev/null +++ b/src/auth/spec/getTokenRequestErrorPrefix.spec.ts @@ -0,0 +1,81 @@ +import { ServerType } from '@sasjs/utils/types' +import { getTokenRequestErrorPrefix } from '../getTokenRequestErrorPrefix' + +describe('getTokenRequestErrorPrefix', () => { + it('should return error prefix', () => { + // INFO: Viya with only required attributes + let operation: 'fetching access token' = 'fetching access token' + const funcName = 'testFunc' + const url = '/SASjsApi/auth/token' + + let expectedPrefix = `Error while ${operation} from ${url} +Thrown by the @sasjs/adapter ${funcName} function. + +Response from Viya is below. +` + + expect( + getTokenRequestErrorPrefix(operation, funcName, ServerType.SasViya, url) + ).toEqual(expectedPrefix) + + // INFO: Sasjs with data and headers + const data = { + grant_type: 'authorization_code', + code: 'testCode' + } + const headers = { + Authorization: 'Basic test=', + Accept: 'application/json' + } + + expectedPrefix = `Error while ${operation} from ${url} +Thrown by the @sasjs/adapter ${funcName} function. +Payload: +${JSON.stringify(data, null, 2)} +Headers: +${JSON.stringify(headers, null, 2)} + +Response from Sasjs is below. +` + + expect( + getTokenRequestErrorPrefix( + operation, + funcName, + ServerType.Sasjs, + url, + data, + headers + ) + ).toEqual(expectedPrefix) + + // INFO: Viya with all attributes + const clientId = 'testId' + const clientSecret = 'testSecret' + + expectedPrefix = `Error while ${operation} from ${url} +Thrown by the @sasjs/adapter ${funcName} function. +Payload: +${JSON.stringify(data, null, 2)} +Headers: +${JSON.stringify(headers, null, 2)} +ClientId: ${clientId} +ClientSecret: ${clientSecret} + +Response from Viya is below. +` + + expect( + getTokenRequestErrorPrefix( + operation, + funcName, + ServerType.SasViya, + url, + data, + headers, + clientId, + clientSecret + ) + ).toEqual(expectedPrefix) + }) +}) diff --git a/src/auth/spec/getTokens.spec.ts b/src/auth/spec/getTokens.spec.ts index 12ca977..9de93f2 100644 --- a/src/auth/spec/getTokens.spec.ts +++ b/src/auth/spec/getTokens.spec.ts @@ -1,4 +1,4 @@ -import { AuthConfig } from '@sasjs/utils' +import { AuthConfig } from '@sasjs/utils/types' import * as refreshTokensModule from '../refreshTokensForViya' import { generateToken, mockAuthResponse } from './mockResponses' import { getTokens } from '../getTokens' diff --git a/src/auth/spec/refreshTokensForSasjs.spec.ts b/src/auth/spec/refreshTokensForSasjs.spec.ts index 0a8a49f..75823aa 100644 --- a/src/auth/spec/refreshTokensForSasjs.spec.ts +++ b/src/auth/spec/refreshTokensForSasjs.spec.ts @@ -1,6 +1,8 @@ +import { ServerType } from '@sasjs/utils/types' import { generateToken, mockAuthResponse } from './mockResponses' import { RequestClient } from '../../request/RequestClient' import { refreshTokensForSasjs } from '../refreshTokensForSasjs' +import { getTokenRequestErrorPrefixResponse } from '../getTokenRequestErrorPrefix' const requestClient = new (>RequestClient)() @@ -38,9 +40,9 @@ describe('refreshTokensForSasjs', () => { const error = await refreshTokensForSasjs( requestClient, refresh_token - ).catch((e: any) => e) + ).catch((e: any) => getTokenRequestErrorPrefixResponse(e, ServerType.Sasjs)) - expect(error).toEqual(`Error while refreshing tokens: ${tokenError}`) + expect(error).toEqual(tokenError) }) }) diff --git a/src/auth/spec/refreshTokensForViya.spec.ts b/src/auth/spec/refreshTokensForViya.spec.ts index 49b8f19..09492bd 100644 --- a/src/auth/spec/refreshTokensForViya.spec.ts +++ b/src/auth/spec/refreshTokensForViya.spec.ts @@ -1,8 +1,10 @@ -import { AuthConfig } from '@sasjs/utils' +import { AuthConfig, ServerType } from '@sasjs/utils/types' import * as NodeFormData from 'form-data' import { generateToken, mockAuthResponse } from './mockResponses' import { RequestClient } from '../../request/RequestClient' import { refreshTokensForViya } from '../refreshTokensForViya' +import * as IsNodeModule from '../../utils/isNode' +import { getTokenRequestErrorPrefixResponse } from '../getTokenRequestErrorPrefix' const requestClient = new (>RequestClient)() @@ -66,9 +68,23 @@ describe('refreshTokensForViya', () => { authConfig.client, authConfig.secret, authConfig.refresh_token - ).catch((e: any) => e) + ).catch((e: any) => + getTokenRequestErrorPrefixResponse(e, ServerType.SasViya) + ) - expect(error).toEqual(`Error while refreshing tokens: ${tokenError}`) + expect(error).toEqual(tokenError) + }) + + it('should throw an error if environment is not Node', async () => { + jest.spyOn(IsNodeModule, 'isNode').mockImplementation(() => false) + + const expectedError = new Error( + `Method 'refreshTokensForViya' can only be used by Node.` + ) + + expect( + refreshTokensForViya(requestClient, 'client', 'secret', 'token') + ).rejects.toEqual(expectedError) }) }) diff --git a/src/auth/spec/verifySas9Login.spec.ts b/src/auth/spec/verifySas9Login.spec.ts index 78f84fc..c71f595 100644 --- a/src/auth/spec/verifySas9Login.spec.ts +++ b/src/auth/spec/verifySas9Login.spec.ts @@ -17,7 +17,7 @@ describe('verifySas9Login', () => { it('should return isLoggedIn true by checking state of popup', async () => { const popup = { window: { - location: { href: serverUrl + `/SASLogon/home` }, + location: { href: serverUrl + `/SASLogon` }, document: { body: { innerText: '

You have signed in.

' } } } } as unknown as Window diff --git a/src/auth/spec/verifySasViyaLogin.spec.ts b/src/auth/spec/verifySasViyaLogin.spec.ts index f0d1d7d..dc99e32 100644 --- a/src/auth/spec/verifySasViyaLogin.spec.ts +++ b/src/auth/spec/verifySasViyaLogin.spec.ts @@ -18,7 +18,7 @@ describe('verifySasViyaLogin', () => { it('should return isLoggedIn true by checking state of popup', async () => { const popup = { window: { - location: { href: serverUrl + `/SASLogon/home` }, + location: { href: serverUrl + `/SASLogon` }, document: { body: { innerText: '

You have signed in.

' } } } } as unknown as Window diff --git a/src/file/generateFileUploadForm.ts b/src/file/generateFileUploadForm.ts index d88f13d..75da2c6 100644 --- a/src/file/generateFileUploadForm.ts +++ b/src/file/generateFileUploadForm.ts @@ -1,5 +1,6 @@ import * as NodeFormData from 'form-data' import { convertToCSV } from '../utils/convertToCsv' +import { isNode } from '../utils' /** * One of the approaches SASjs takes to send tables-formatted JSON (see README) @@ -26,12 +27,15 @@ export const generateFileUploadForm = ( ) } - if (typeof FormData === 'undefined' && formData instanceof NodeFormData) { - formData.append(name, csv, { + // INFO: unfortunately it is not possible to check if formData is instance of NodeFormData or FormData because it will return true for both + if (isNode()) { + // INFO: environment is Node and formData is instance of NodeFormData + ;(formData as NodeFormData).append(name, csv, { filename: `${name}.csv`, contentType: 'application/csv' }) } else { + // INFO: environment is Browser and formData is instance of FormData const file = new Blob([csv], { type: 'application/csv' }) diff --git a/src/file/spec/generateFileUploadForm.spec.ts b/src/file/spec/generateFileUploadForm.spec.ts index fb97fb9..1dc9db9 100644 --- a/src/file/spec/generateFileUploadForm.spec.ts +++ b/src/file/spec/generateFileUploadForm.spec.ts @@ -1,4 +1,7 @@ import { generateFileUploadForm } from '../generateFileUploadForm' +import { convertToCSV } from '../../utils/convertToCsv' +import * as NodeFormData from 'form-data' +import * as isNodeModule from '../../utils/isNode' describe('generateFileUploadForm', () => { beforeAll(() => { @@ -11,44 +14,94 @@ describe('generateFileUploadForm', () => { ;(global as any).Blob = BlobMock }) - it('should generate file upload form from data', () => { - const formData = new FormData() - const testTable = 'sometable' - const testTableWithNullVars: { [key: string]: any } = { - [testTable]: [ - { var1: 'string', var2: 232, nullvar: 'A' }, - { var1: 'string', var2: 232, nullvar: 'B' }, - { var1: 'string', var2: 232, nullvar: '_' }, - { var1: 'string', var2: 232, nullvar: 0 }, - { var1: 'string', var2: 232, nullvar: 'z' }, - { var1: 'string', var2: 232, nullvar: null } - ], - [`$${testTable}`]: { formats: { var1: '$char12.', nullvar: 'best.' } } - } - const tableName = Object.keys(testTableWithNullVars).filter((key: string) => - Array.isArray(testTableWithNullVars[key]) - )[0] + describe('browser', () => { + afterAll(() => { + jest.restoreAllMocks() + }) - jest.spyOn(formData, 'append').mockImplementation(() => {}) + it('should generate file upload form from data', () => { + const formData = new FormData() + const testTable = 'sometable' + const testTableWithNullVars: { [key: string]: any } = { + [testTable]: [ + { var1: 'string', var2: 232, nullvar: 'A' }, + { var1: 'string', var2: 232, nullvar: 'B' }, + { var1: 'string', var2: 232, nullvar: '_' }, + { var1: 'string', var2: 232, nullvar: 0 }, + { var1: 'string', var2: 232, nullvar: 'z' }, + { var1: 'string', var2: 232, nullvar: null } + ], + [`$${testTable}`]: { formats: { var1: '$char12.', nullvar: 'best.' } } + } + const tableName = Object.keys(testTableWithNullVars).filter( + (key: string) => Array.isArray(testTableWithNullVars[key]) + )[0] - generateFileUploadForm(formData, testTableWithNullVars) + jest.spyOn(formData, 'append').mockImplementation(() => {}) + jest.spyOn(isNodeModule, 'isNode').mockImplementation(() => false) - expect(formData.append).toHaveBeenCalledOnce() - expect(formData.append).toHaveBeenCalledWith( - tableName, - {}, - `${tableName}.csv` - ) + generateFileUploadForm(formData, testTableWithNullVars) + + expect(formData.append).toHaveBeenCalledOnce() + expect(formData.append).toHaveBeenCalledWith( + tableName, + {}, + `${tableName}.csv` + ) + }) + + it('should throw an error if too large string was provided', () => { + const formData = new FormData() + const data = { testTable: [{ var1: 'z'.repeat(32765 + 1) }] } + + expect(() => generateFileUploadForm(formData, data)).toThrow( + new Error( + 'The max length of a string value in SASjs is 32765 characters.' + ) + ) + }) }) - it('should throw an error if too large string was provided', () => { - const formData = new FormData() - const data = { testTable: [{ var1: 'z'.repeat(32765 + 1) }] } + describe('node', () => { + it('should generate file upload form from data', () => { + const formData = new NodeFormData() + const testTable = 'sometable' + const testTableWithNullVars: { [key: string]: any } = { + [testTable]: [ + { var1: 'string', var2: 232, nullvar: 'A' }, + { var1: 'string', var2: 232, nullvar: 'B' }, + { var1: 'string', var2: 232, nullvar: '_' }, + { var1: 'string', var2: 232, nullvar: 0 }, + { var1: 'string', var2: 232, nullvar: 'z' }, + { var1: 'string', var2: 232, nullvar: null } + ], + [`$${testTable}`]: { formats: { var1: '$char12.', nullvar: 'best.' } } + } + const tableName = Object.keys(testTableWithNullVars).filter( + (key: string) => Array.isArray(testTableWithNullVars[key]) + )[0] + const csv = convertToCSV(testTableWithNullVars, tableName) - expect(() => generateFileUploadForm(formData, data)).toThrow( - new Error( - 'The max length of a string value in SASjs is 32765 characters.' + jest.spyOn(formData, 'append').mockImplementation(() => {}) + + generateFileUploadForm(formData, testTableWithNullVars) + + expect(formData.append).toHaveBeenCalledOnce() + expect(formData.append).toHaveBeenCalledWith(tableName, csv, { + contentType: 'application/csv', + filename: `${tableName}.csv` + }) + }) + + it('should throw an error if too large string was provided', () => { + const formData = new NodeFormData() + const data = { testTable: [{ var1: 'z'.repeat(32765 + 1) }] } + + expect(() => generateFileUploadForm(formData, data)).toThrow( + new Error( + 'The max length of a string value in SASjs is 32765 characters.' + ) ) - ) + }) }) }) diff --git a/src/index.ts b/src/index.ts index 251fe50..e0b156d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 diff --git a/src/job-execution/FileUploader.ts b/src/job-execution/FileUploader.ts index 535c82a..ee3f2c2 100644 --- a/src/job-execution/FileUploader.ts +++ b/src/job-execution/FileUploader.ts @@ -93,15 +93,24 @@ export class FileUploader extends BaseJobExecutor { this.requestClient, config.serverUrl ) + break case ServerType.Sas9: jsonResponse = typeof res.result === 'string' ? parseWeboutResponse(res.result, uploadUrl) : res.result + + break + case ServerType.Sasjs: + jsonResponse = + typeof res.result === 'string' + ? getValidJson(res.result) + : res.result + break } - } else if (this.serverType !== ServerType.Sasjs) { + } else { jsonResponse = typeof res.result === 'string' ? getValidJson(res.result) diff --git a/src/job-execution/SasjsJobExecutor.ts b/src/job-execution/SasjsJobExecutor.ts index d9fd230..0aecbf4 100644 --- a/src/job-execution/SasjsJobExecutor.ts +++ b/src/job-execution/SasjsJobExecutor.ts @@ -10,10 +10,14 @@ import { LoginRequiredError } from '../types/errors' import { generateFileUploadForm } from '../file/generateFileUploadForm' - import { RequestClient } from '../request/RequestClient' +import { getFormData } from '../utils' -import { isRelativePath, appendExtraResponseAttributes } from '../utils' +import { + isRelativePath, + appendExtraResponseAttributes, + getValidJson +} from '../utils' import { BaseJobExecutor } from './JobExecutor' export class SasjsJobExecutor extends BaseJobExecutor { @@ -49,8 +53,7 @@ export class SasjsJobExecutor extends BaseJobExecutor { * Use the available form data object (FormData in Browser, NodeFormData in * Node) */ - let formData = - typeof FormData === 'undefined' ? new NodeFormData() : new FormData() + let formData = getFormData() if (data) { // file upload approach @@ -89,12 +92,18 @@ export class SasjsJobExecutor extends BaseJobExecutor { ) } + const { result } = res + + if (result && typeof result === 'string' && result.trim()) + res.result = getValidJson(result) + this.requestClient!.appendRequest(res, sasJob, config.debug) const responseObject = appendExtraResponseAttributes( res, extraResponseAttributes ) + resolve(responseObject) }) .catch(async (e: Error) => { diff --git a/src/job-execution/WebJobExecutor.ts b/src/job-execution/WebJobExecutor.ts index 24af9a3..2da6b36 100644 --- a/src/job-execution/WebJobExecutor.ts +++ b/src/job-execution/WebJobExecutor.ts @@ -16,10 +16,11 @@ import { SASViyaApiClient } from '../SASViyaApiClient' import { isRelativePath, parseSasViyaDebugResponse, - appendExtraResponseAttributes + appendExtraResponseAttributes, + parseWeboutResponse, + getFormData } from '../utils' import { BaseJobExecutor } from './JobExecutor' -import { parseWeboutResponse } from '../utils/parseWeboutResponse' export interface WaitingRequstPromise { promise: Promise | null @@ -112,8 +113,7 @@ export class WebJobExecutor extends BaseJobExecutor { * Use the available form data object (FormData in Browser, NodeFormData in * Node) */ - let formData = - typeof FormData === 'undefined' ? new NodeFormData() : new FormData() + let formData = getFormData() if (data) { const stringifiedData = JSON.stringify(data) diff --git a/src/minified/sas9/SASjs.ts b/src/minified/sas9/SASjs.ts new file mode 100644 index 0000000..5832cb6 --- /dev/null +++ b/src/minified/sas9/SASjs.ts @@ -0,0 +1,273 @@ +import { validateInput, compareTimestamps } from '../../utils' +import { SASjsConfig, UploadFile, LoginMechanism } from '../../types' +import { AuthManager } from '../../auth' +import { + ServerType, + AuthConfig, + ExtraResponseAttributes +} from '@sasjs/utils/types' +import { RequestClient } from '../../request/RequestClient' +import { FileUploader } from '../../job-execution/FileUploader' +import { WebJobExecutor } from './WebJobExecutor' +import { ErrorResponse } from '../../types/errors/ErrorResponse' +import { LoginOptions, LoginResult } from '../../types/Login' + +const defaultConfig: SASjsConfig = { + serverUrl: '', + pathSASJS: '/SASjsApi/stp/execute', + pathSAS9: '/SASStoredProcess/do', + pathSASViya: '/SASJobExecution', + appLoc: '/Public/seedapp', + serverType: ServerType.Sas9, + debug: false, + contextName: 'SAS Job Execution compute context', + useComputeApi: null, + loginMechanism: LoginMechanism.Default +} + +/** + * SASjs is a JavaScript adapter for SAS. + * + */ +export default class SASjs { + private sasjsConfig: SASjsConfig = new SASjsConfig() + private jobsPath: string = '' + private fileUploader: FileUploader | null = null + private authManager: AuthManager | null = null + private requestClient: RequestClient | null = null + private webJobExecutor: WebJobExecutor | null = null + + constructor(config?: Partial) { + this.sasjsConfig = { + ...defaultConfig, + ...config + } + + this.setupConfiguration() + } + + /** + * Logs into the SAS server with the supplied credentials. + * @param username - a string representing the username. + * @param password - a string representing the password. + * @param clientId - a string representing the client ID. + */ + public async logIn( + username?: string, + password?: string, + clientId?: string, + options: LoginOptions = {} + ): Promise { + if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) { + if (!username || !password) + throw new Error( + 'A username and password are required when using the default login mechanism.' + ) + + return this.authManager!.logIn(username, password) + } + + if (typeof window === typeof undefined) { + throw new Error( + 'The redirected login mechanism is only available for use in the browser.' + ) + } + + return this.authManager!.redirectedLogIn(options) + } + + /** + * Logs out of the configured SAS server. + */ + public logOut() { + return this.authManager!.logOut() + } + + /** + * Returns the current SASjs configuration. + * + */ + public getSasjsConfig() { + return this.sasjsConfig + } + + /** + * this method returns an array of SASjsRequest + * @returns SASjsRequest[] + */ + public getSasRequests() { + const requests = [...this.requestClient!.getRequests()] + const sortedRequests = requests.sort(compareTimestamps) + return sortedRequests + } + + /** + * Sets the debug state. Turning this on will enable additional logging in the adapter. + * @param value - boolean indicating debug state (on/off). + */ + public setDebugState(value: boolean) { + this.sasjsConfig.debug = value + } + + /** + * Uploads a file to the given service. + * @param sasJob - the path to the SAS program (ultimately resolves to + * the SAS `_program` parameter to run a Job Definition or SAS 9 Stored + * Process). Is prepended at runtime with the value of `appLoc`. + * @param files - array of files to be uploaded, including File object and file name. + * @param params - request URL parameters. + * @param config - provide any changes to the config here, for instance to + * enable/disable `debug`. Any change provided will override the global config, + * for that particular function call. + * @param loginRequiredCallback - a function that is called if the + * user is not logged in (eg to display a login form). The request will be + * resubmitted after successful login. + */ + public async uploadFile( + sasJob: string, + files: UploadFile[], + params: { [key: string]: any } | null, + config: { [key: string]: any } = {}, + loginRequiredCallback?: () => any + ) { + config = { + ...this.sasjsConfig, + ...config + } + const data = { files, params } + + return await this.fileUploader!.execute( + sasJob, + data, + config, + loginRequiredCallback + ) + } + + /** + * Makes a request to program specified in `SASjob` (could be a Viya Job, a + * SAS 9 Stored Process, or a SASjs Server Stored Program). The response + * object will always contain table names in lowercase, and column names in + * uppercase. Values are returned formatted by default, unformatted + * values can be configured as an option in the `%webout` macro. + * + * @param sasJob - the path to the SAS program (ultimately resolves to + * the SAS `_program` parameter to run a Job Definition or SAS 9 Stored + * Process). Is prepended at runtime with the value of `appLoc`. + * @param data - a JSON object containing one or more tables to be sent to + * SAS. For an example of the table structure, see the project README. This + * value can be `null` if no inputs are required. + * @param config - provide any changes to the config here, for instance to + * enable/disable `debug`. Any change provided will override the global config, + * for that particular function call. + * @param loginRequiredCallback - a function that is called if the + * user is not logged in (eg to display a login form). The request will be + * resubmitted after successful login. + * When using a `loginRequiredCallback`, the call to the request will look, for example, like so: + * `await request(sasJobPath, data, config, () => setIsLoggedIn(false))` + * If you are not passing in any data and configuration, it will look like so: + * `await request(sasJobPath, {}, {}, () => setIsLoggedIn(false))` + * @param extraResponseAttributes - a array of predefined values that are used + * to provide extra attributes (same names as those values) to be added in response + * Supported values are declared in ExtraResponseAttributes type. + */ + public async request( + sasJob: string, + data: { [key: string]: any } | null, + config: { [key: string]: any } = {}, + loginRequiredCallback?: () => any, + authConfig?: AuthConfig, + extraResponseAttributes: ExtraResponseAttributes[] = [] + ) { + config = { + ...this.sasjsConfig, + ...config + } + + const validationResult = validateInput(data) + + // status is true if the data passes validation checks above + if (validationResult.status) { + return await this.webJobExecutor!.execute( + sasJob, + data, + config, + loginRequiredCallback, + authConfig, + extraResponseAttributes + ) + } else { + return Promise.reject(new ErrorResponse(validationResult.msg)) + } + } + + /** + * Checks whether a session is active, or login is required. + * @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`. + */ + public async checkSession() { + return this.authManager!.checkSession() + } + + private setupConfiguration() { + if ( + this.sasjsConfig.serverUrl === undefined || + this.sasjsConfig.serverUrl === '' + ) { + if (typeof location !== 'undefined') { + let url = `${location.protocol}//${location.hostname}` + + if (location.port) url = `${url}:${location.port}` + + this.sasjsConfig.serverUrl = url + } else { + this.sasjsConfig.serverUrl = '' + } + } + + if (this.sasjsConfig.serverUrl.slice(-1) === '/') { + this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1) + } + + if (!this.requestClient) { + this.requestClient = new RequestClient( + this.sasjsConfig.serverUrl, + this.sasjsConfig.httpsAgentOptions, + this.sasjsConfig.requestHistoryLimit + ) + } else { + this.requestClient.setConfig( + this.sasjsConfig.serverUrl, + this.sasjsConfig.httpsAgentOptions + ) + } + + this.jobsPath = this.sasjsConfig.pathSAS9 + + this.authManager = new AuthManager( + this.sasjsConfig.serverUrl, + this.sasjsConfig.serverType!, + this.requestClient, + this.resendWaitingRequests + ) + + this.fileUploader = new FileUploader( + this.sasjsConfig.serverUrl, + this.sasjsConfig.serverType!, + this.jobsPath, + this.requestClient + ) + + this.webJobExecutor = new WebJobExecutor( + this.sasjsConfig.serverUrl, + this.sasjsConfig.serverType!, + this.jobsPath, + this.requestClient + ) + } + + private resendWaitingRequests = async () => { + await this.webJobExecutor?.resendWaitingRequests() + await this.fileUploader?.resendWaitingRequests() + } +} diff --git a/src/minified/sas9/WebJobExecutor.ts b/src/minified/sas9/WebJobExecutor.ts new file mode 100644 index 0000000..4cf9393 --- /dev/null +++ b/src/minified/sas9/WebJobExecutor.ts @@ -0,0 +1,157 @@ +import { + AuthConfig, + ExtraResponseAttributes, + ServerType +} from '@sasjs/utils/types' +import { + ErrorResponse, + JobExecutionError, + LoginRequiredError +} from '../../types/errors' +import { RequestClient } from '../../request/RequestClient' +import { + isRelativePath, + parseSasViyaDebugResponse, + appendExtraResponseAttributes, + convertToCSV +} from '../../utils' +import { BaseJobExecutor } from '../../job-execution/JobExecutor' +import { parseWeboutResponse } from '../../utils/parseWeboutResponse' + +export interface WaitingRequstPromise { + promise: Promise | null + resolve: any + reject: any +} +export class WebJobExecutor extends BaseJobExecutor { + constructor( + serverUrl: string, + serverType: ServerType, + private jobsPath: string, + private requestClient: RequestClient + ) { + super(serverUrl, serverType) + } + + 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 + : sasJob + let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}` + + let requestParams = { + ...this.getRequestParams(config) + } + + let formData = new FormData() + + if (data) { + 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]) + } + } + + const requestPromise = new Promise((resolve, reject) => { + this.requestClient!.post(apiUrl, formData, authConfig?.access_token) + .then(async (res: any) => { + this.requestClient!.appendRequest(res, sasJob, config.debug) + + const jsonResponse = + config.debug && typeof res.result === 'string' + ? parseWeboutResponse(res.result, apiUrl) + : res.result + + const responseObject = appendExtraResponseAttributes( + { result: jsonResponse, log: res.log }, + 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 + } +} + +/** + * One of the approaches SASjs takes to send tables-formatted JSON (see README) + * to SAS is as multipart form data, where each table is provided as a specially + * formatted CSV file. + */ +const generateFileUploadForm = (formData: FormData, data: any): FormData => { + for (const tableName in data) { + if (!Array.isArray(data[tableName])) continue + + const name = tableName + const csv = convertToCSV(data, tableName) + + if (csv === 'ERROR: LARGE STRING LENGTH') { + throw new Error( + 'The max length of a string value in SASjs is 32765 characters.' + ) + } + + const file = new Blob([csv], { + type: 'application/csv' + }) + + formData.append(name, file, `${name}.csv`) + } + + return formData +} diff --git a/src/minified/sas9/index.ts b/src/minified/sas9/index.ts new file mode 100644 index 0000000..cb5b539 --- /dev/null +++ b/src/minified/sas9/index.ts @@ -0,0 +1,3 @@ +import SASjs from './SASjs' +export * from '../../types' +export default SASjs diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index e1c42e0..ef21871 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -691,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('')[1].split('')[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, '') } } diff --git a/src/request/Sas9RequestClient.ts b/src/request/Sas9RequestClient.ts index a9da3c3..3263948 100644 --- a/src/request/Sas9RequestClient.ts +++ b/src/request/Sas9RequestClient.ts @@ -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(response) }) .catch(async (e: any) => { + if (e instanceof JobExecutionError) throw e + return await this.handleError( e, () => diff --git a/src/request/SasjsRequestClient.ts b/src/request/SasjsRequestClient.ts index 5b99c56..30a4f2d 100644 --- a/src/request/SasjsRequestClient.ts +++ b/src/request/SasjsRequestClient.ts @@ -1,6 +1,14 @@ import { RequestClient } from './RequestClient' import { AxiosResponse } from 'axios' -import { SASJS_LOGS_SEPARATOR, getValidJson } from '../utils' +import { SASJS_LOGS_SEPARATOR } from '../utils' + +interface SasjsParsedResponse { + result: T + log: string + etag: string + status: number + printOutput?: string +} /** * Specific request client for SASJS. @@ -27,7 +35,7 @@ export class SasjsRequestClient extends RequestClient { protected parseResponse(response: AxiosResponse) { const etag = response?.headers ? response.headers['etag'] : '' let parsedResponse = {} - let log + let webout, log, printOutput try { if (typeof response.data === 'string') { @@ -38,17 +46,26 @@ export class SasjsRequestClient extends RequestClient { } catch { if (response.data.includes(SASJS_LOGS_SEPARATOR)) { const splittedResponse = response.data.split(SASJS_LOGS_SEPARATOR) + + webout = splittedResponse[0] + if (webout !== undefined) parsedResponse = webout + log = splittedResponse[1] - if (splittedResponse[0].trim()) - parsedResponse = getValidJson(splittedResponse[0]) - } else parsedResponse = response.data + printOutput = splittedResponse[2] + } else { + parsedResponse = response.data + } } - return { + const returnResult: SasjsParsedResponse = { result: parsedResponse as T, log, etag, status: response.status } + + if (printOutput) returnResult.printOutput = printOutput + + return returnResult } } diff --git a/src/test/RequestClient.spec.ts b/src/test/RequestClient.spec.ts index b428380..fb9cb89 100644 --- a/src/test/RequestClient.spec.ts +++ b/src/test/RequestClient.spec.ts @@ -2,7 +2,7 @@ import * as pem from 'pem' import * as http from 'http' import * as https from 'https' import { app, mockedAuthResponse } from './SAS_server_app' -import { ServerType } from '@sasjs/utils' +import { ServerType } from '@sasjs/utils/types' import SASjs from '../SASjs' import * as axiosModules from '../utils/createAxiosInstance' import { @@ -11,8 +11,8 @@ import { NotFoundError, InternalServerError } from '../types/errors' -import { prefixMessage } from '@sasjs/utils/error' import { RequestClient } from '../request/RequestClient' +import { getTokenRequestErrorPrefixResponse } from '../auth/getTokenRequestErrorPrefix' const axiosActual = jest.requireActual('axios') @@ -66,14 +66,18 @@ describe('RequestClient', () => { }) it('should response the POST method with Unauthorized', async () => { - await expect( - adapter.getAccessToken('clientId', 'clientSecret', 'incorrect') - ).rejects.toEqual( - prefixMessage( - new LoginRequiredError(incorrectAuthCodeErr), - 'Error while getting access token. ' + const expectedError = new LoginRequiredError({ + error: 'unauthorized', + error_description: 'Bad credentials' + }) + + const rejectionErrorMessage = await adapter + .getAccessToken('clientId', 'clientSecret', 'incorrect') + .catch((err) => + getTokenRequestErrorPrefixResponse(err.message, ServerType.SasViya) ) - ) + + expect(rejectionErrorMessage).toEqual(expectedError.message) }) describe('handleError', () => { @@ -209,15 +213,15 @@ describe('RequestClient - Self Signed Server', () => { serverType: ServerType.SasViya }) - await expect( - adapterWithoutCertificate.getAccessToken( - 'clientId', - 'clientSecret', - 'authCode' + const expectedError = 'self-signed certificate' + + const rejectionErrorMessage = await adapterWithoutCertificate + .getAccessToken('clientId', 'clientSecret', 'authCode') + .catch((err) => + getTokenRequestErrorPrefixResponse(err.message, ServerType.SasViya) ) - ).rejects.toThrow( - `Error while getting access token. ${ERROR_MESSAGES.selfSigned}` - ) + + expect(rejectionErrorMessage).toEqual(expectedError) }) it('should response the POST method using insecure flag', async () => { @@ -247,14 +251,18 @@ describe('RequestClient - Self Signed Server', () => { }) it('should response the POST method with Unauthorized', async () => { - await expect( - adapter.getAccessToken('clientId', 'clientSecret', 'incorrect') - ).rejects.toEqual( - prefixMessage( - new LoginRequiredError(incorrectAuthCodeErr), - 'Error while getting access token. ' + const expectedError = new LoginRequiredError({ + error: 'unauthorized', + error_description: 'Bad credentials' + }) + + const rejectionErrorMessage = await adapter + .getAccessToken('clientId', 'clientSecret', 'incorrect') + .catch((err) => + getTokenRequestErrorPrefixResponse(err.message, ServerType.SasViya) ) - ) + + expect(rejectionErrorMessage).toEqual(expectedError.message) }) }) diff --git a/src/test/SessionManager.spec.ts b/src/test/SessionManager.spec.ts index f40205a..8640fe4 100644 --- a/src/test/SessionManager.spec.ts +++ b/src/test/SessionManager.spec.ts @@ -2,11 +2,12 @@ import { SessionManager } from '../SessionManager' import { RequestClient } from '../request/RequestClient' import * as dotenv from 'dotenv' import axios from 'axios' -import { Logger, LogLevel } from '@sasjs/utils' -import { Session } from '../types' +import { Logger, LogLevel } from '@sasjs/utils/logger' +import { Session, Context } from '../types' jest.mock('axios') const mockedAxios = axios as jest.Mocked +const requestClient = new (>RequestClient)() describe('SessionManager', () => { dotenv.config() @@ -14,9 +15,23 @@ describe('SessionManager', () => { const sessionManager = new SessionManager( process.env.SERVER_URL as string, process.env.DEFAULT_COMPUTE_CONTEXT as string, - new RequestClient('https://sample.server.com') + requestClient ) + const getMockSession = () => ({ + id: ['id', new Date().getTime(), Math.random()].join('-'), + state: '', + links: [{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }], + attributes: { + sessionInactiveTimeout: 900 + }, + creationTimeStamp: `${new Date(new Date().getTime()).toISOString()}` + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + describe('getVariable', () => { it('should fetch session variable', async () => { const sampleResponse = { @@ -45,6 +60,30 @@ describe('SessionManager', () => { ) ).resolves.toEqual(expectedResponse) }) + + it('should throw an error if GET request failed', async () => { + const responseStatus = 500 + const responseErrorMessage = `The process timed out after 60 seconds. Request failed with status code ${responseStatus}` + const response = { + status: responseStatus, + data: { + message: responseErrorMessage + } + } + const testVariable = 'testVariable' + + jest.spyOn(requestClient, 'get').mockImplementation(() => + Promise.reject({ + response + }) + ) + + const expectedError = `Error while fetching session variable '${testVariable}'. GET request to ${process.env.SERVER_URL}/compute/sessions/testId/variables/${testVariable} failed with status code ${responseStatus}. ${responseErrorMessage}` + + await expect( + sessionManager.getVariable('testId', testVariable) + ).rejects.toEqual(expectedError) + }) }) describe('waitForSession', () => { @@ -115,11 +154,25 @@ describe('SessionManager', () => { }) it('should throw an error if could not get session state', async () => { - mockedAxios.get.mockImplementation(() => Promise.reject('Mocked error')) + const gettingSessionStatus = 500 + const sessionStatusError = `Getting session status timed out after 60 seconds. Request failed with status code ${gettingSessionStatus}` + + mockedAxios.get.mockImplementation(() => + Promise.reject({ + response: { + status: gettingSessionStatus, + data: { + message: sessionStatusError + } + } + }) + ) + + const expectedError = `Error while waiting for session. Error while getting session state. GET request to ${process.env.SERVER_URL}?wait=30 failed with status code ${gettingSessionStatus}. ${sessionStatusError}` await expect( sessionManager['waitForSession'](session, null, 'access_token') - ).rejects.toContain('Error while getting session state.') + ).rejects.toEqual(expectedError) }) it('should return session state', async () => { @@ -135,4 +188,243 @@ describe('SessionManager', () => { ).resolves.toEqual(customSession.state) }) }) + + describe('isSessionValid', () => { + const session: Session = getMockSession() + + it('should return false if not a session provided', () => { + expect(sessionManager['isSessionValid'](undefined as any)).toEqual(false) + }) + + it('should return true if session is not expired', () => { + expect(sessionManager['isSessionValid'](session)).toEqual(true) + }) + + it('should return false if session is expired', () => { + session.creationTimeStamp = `${new Date( + new Date().getTime() - + (session.attributes.sessionInactiveTimeout * 1000 + 1000) + ).toISOString()}` + expect(sessionManager['isSessionValid'](session)).toEqual(false) + }) + }) + + describe('removeSessionFromPool', () => { + it('should remove session from the pool of sessions', () => { + const session: Session = getMockSession() + const sessions: Session[] = [getMockSession(), session] + + sessionManager['sessions'] = sessions + sessionManager['removeSessionFromPool'](session) + + expect(sessionManager['sessions'].length).toEqual(1) + }) + }) + + describe('getSession', () => { + it('should return session if there is a valid session and create new session', async () => { + jest + .spyOn(sessionManager as any, 'createAndWaitForSession') + .mockImplementation(async () => Promise.resolve(getMockSession())) + + const session = getMockSession() + sessionManager['sessions'] = [session] + + await expect(sessionManager.getSession()).resolves.toEqual(session) + expect(sessionManager['createAndWaitForSession']).toHaveBeenCalled() + }) + + it('should return a session and keep one session if there is no sessions available', async () => { + jest + .spyOn(sessionManager as any, 'createAndWaitForSession') + .mockImplementation(async () => { + const session = getMockSession() + sessionManager['sessions'].push(session) + + return Promise.resolve(session) + }) + + const session = await sessionManager.getSession() + + expect(Object.keys(session)).toEqual(Object.keys(getMockSession())) + expect(sessionManager['createAndWaitForSession']).toHaveBeenCalledTimes(2) + expect(sessionManager['sessions'].length).toEqual(1) + }) + + it.concurrent( + 'should throw an error if session creation request returned 500', + async () => { + const sessionCreationStatus = 500 + const sessionCreationError = `The process initialization for the Compute server with the ID 'ed40398a-ec8a-422b-867a-61493ee8a57f' timed out after 60 seconds. Request failed with status code ${sessionCreationStatus}` + + jest.spyOn(requestClient, 'post').mockImplementation(() => + Promise.reject({ + response: { + status: sessionCreationStatus, + data: { + message: sessionCreationError + } + } + }) + ) + + const contextId = 'testContextId' + const context: Context = { + name: 'testContext', + id: contextId, + createdBy: 'createdBy', + version: 1 + } + + sessionManager['currentContext'] = context + + const expectedError = new Error( + `Error while creating session. POST request to ${process.env.SERVER_URL}/compute/contexts/${contextId}/sessions failed with status code ${sessionCreationStatus}. ${sessionCreationError}` + ) + + await expect(sessionManager.getSession()).rejects.toEqual(expectedError) + } + ) + }) + + describe('clearSession', () => { + it('should clear session', async () => { + jest + .spyOn(requestClient, 'delete') + .mockImplementation(() => + Promise.resolve({ result: '', etag: '', status: 200 }) + ) + + const sessionToBeCleared = getMockSession() + const sessionToStay = getMockSession() + + sessionManager['sessions'] = [sessionToBeCleared, sessionToStay] + + await sessionManager.clearSession(sessionToBeCleared.id) + + expect(sessionManager['sessions']).toEqual([sessionToStay]) + }) + + it('should throw error if DELETE request failed', async () => { + const sessionCreationStatus = 500 + const sessionDeleteError = `The process timed out after 60 seconds. Request failed with status code ${sessionCreationStatus}` + + jest.spyOn(requestClient, 'delete').mockImplementation(() => + Promise.reject({ + response: { + status: sessionCreationStatus, + data: { + message: sessionDeleteError + } + } + }) + ) + + const session = getMockSession() + + sessionManager['sessions'] = [session] + + const expectedError = `Error while deleting session. DELETE request to /compute/sessions/${session.id} failed with status code ${sessionCreationStatus}. ${sessionDeleteError}` + + await expect(sessionManager.clearSession(session.id)).rejects.toEqual( + expectedError + ) + }) + }) + + describe('waitForCurrentContext', () => { + it('should resolve when current context is ready', async () => { + sessionManager['settingContext'] = true + sessionManager['contextName'] = 'test context' + + await expect(sessionManager['waitForCurrentContext']()).toResolve() + expect(sessionManager['settingContext']).toEqual(false) + }) + }) + + describe('setCurrentContext', () => { + it('should set current context', async () => { + const contextName = 'test context' + const testContext: Context = { + name: contextName, + id: 'string', + createdBy: 'string', + version: 1 + } + + jest.spyOn(requestClient, 'get').mockImplementation(() => { + return Promise.resolve({ + result: { + items: [testContext] + }, + etag: '', + status: 200 + }) + }) + + sessionManager['currentContext'] = null + sessionManager['contextName'] = contextName + sessionManager['settingContext'] = false + + await expect(sessionManager['setCurrentContext']()).toResolve() + expect(sessionManager['currentContext']).toEqual(testContext) + }) + + it('should throw error if GET request failed', async () => { + const responseStatus = 500 + const responseErrorMessage = `The process timed out after 60 seconds. Request failed with status code ${responseStatus}` + const response = { + status: responseStatus, + data: { + message: responseErrorMessage + } + } + + jest.spyOn(requestClient, 'get').mockImplementation(() => + Promise.reject({ + response + }) + ) + + const expectedError = `Error while getting list of contexts. GET request to ${process.env.SERVER_URL}/compute/contexts?limit=10000 failed with status code ${responseStatus}. ${responseErrorMessage}` + + sessionManager['currentContext'] = null + + await expect(sessionManager['setCurrentContext']()).rejects.toEqual( + expectedError + ) + }) + + it('should throw an error if current context is not in the list of contexts', async () => { + const contextName = 'test context' + const testContext: Context = { + name: `${contextName} does not exist`, + id: 'string', + createdBy: 'string', + version: 1 + } + + jest.spyOn(requestClient, 'get').mockImplementation(() => { + return Promise.resolve({ + result: { + items: [testContext] + }, + etag: '', + status: 200 + }) + }) + + sessionManager['currentContext'] = null + sessionManager['contextName'] = contextName + sessionManager['settingContext'] = false + + const expectedError = new Error( + `The context '${contextName}' was not found on the server ${process.env.SERVER_URL}.` + ) + + await expect(sessionManager['setCurrentContext']()).rejects.toEqual( + expectedError + ) + }) + }) }) diff --git a/src/types/Login.ts b/src/types/Login.ts index 5b978bd..479d55e 100644 --- a/src/types/Login.ts +++ b/src/types/Login.ts @@ -5,5 +5,12 @@ export interface LoginOptions { export interface LoginResult { isLoggedIn: boolean userName: string + userLongName: string errorMessage?: string } +export interface LoginResultInternal { + isLoggedIn: boolean + userName: string + userLongName: string + loginForm?: any +} diff --git a/src/types/PollOptions.ts b/src/types/PollOptions.ts index 82daf9e..020e964 100644 --- a/src/types/PollOptions.ts +++ b/src/types/PollOptions.ts @@ -1,6 +1,9 @@ export interface PollOptions { maxPollCount: number - pollInterval: number - streamLog: boolean + pollInterval: number // milliseconds + pollStrategy?: PollStrategy + streamLog?: boolean logFolderPath?: string } + +export type PollStrategy = PollOptions[] diff --git a/src/types/errors/ErrorResponse.ts b/src/types/errors/ErrorResponse.ts index 0057f3a..5dcfd5d 100644 --- a/src/types/errors/ErrorResponse.ts +++ b/src/types/errors/ErrorResponse.ts @@ -21,8 +21,8 @@ export class ErrorResponse { } } -interface ErrorBody { +export interface ErrorBody { message: string - details: string + details: any raw: any } diff --git a/src/types/errors/RootFolderNotFoundError.spec.ts b/src/types/errors/RootFolderNotFoundError.spec.ts index a27e071..3bd97a4 100644 --- a/src/types/errors/RootFolderNotFoundError.spec.ts +++ b/src/types/errors/RootFolderNotFoundError.spec.ts @@ -7,7 +7,7 @@ describe('RootFolderNotFoundError', () => { const error = new RootFolderNotFoundError( '/myProject', - 'https://analytium.co.uk', + 'https://sas.4gl.io', token ) @@ -19,7 +19,7 @@ describe('RootFolderNotFoundError', () => { it('when access token is not provided, error message should not contain scopes', () => { const error = new RootFolderNotFoundError( '/myProject', - 'https://analytium.co.uk' + 'https://sas.4gl.io' ) expect(error).toBeInstanceOf(RootFolderNotFoundError) @@ -30,7 +30,7 @@ describe('RootFolderNotFoundError', () => { it('should include the folder path and SASDrive URL in the message', () => { const folderPath = '/myProject' - const serverUrl = 'https://analytium.co.uk' + const serverUrl = 'https://sas.4gl.io' const error = new RootFolderNotFoundError(folderPath, serverUrl) expect(error).toBeInstanceOf(RootFolderNotFoundError) diff --git a/src/utils/getFormData.ts b/src/utils/getFormData.ts new file mode 100644 index 0000000..8290af3 --- /dev/null +++ b/src/utils/getFormData.ts @@ -0,0 +1,5 @@ +import { isNode } from './' +import * as NodeFormData from 'form-data' + +export const getFormData = () => + isNode() ? new NodeFormData() : new FormData() diff --git a/src/utils/getValidJson.ts b/src/utils/getValidJson.ts index 7a81cb2..63387f0 100644 --- a/src/utils/getValidJson.ts +++ b/src/utils/getValidJson.ts @@ -17,6 +17,7 @@ export const getValidJson = (str: string | object): object => { return JSON.parse(str) } catch (e: any) { if (e instanceof JsonParseArrayError) throw e + throw new InvalidJsonError() } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 4f02fc8..68def1a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -20,3 +20,4 @@ export * from './parseWeboutResponse' export * from './serialize' export * from './splitChunks' export * from './validateInput' +export * from './getFormData' diff --git a/src/utils/sas9/extractUserLongNameSas9.ts b/src/utils/sas9/extractUserLongNameSas9.ts new file mode 100644 index 0000000..3da9f84 --- /dev/null +++ b/src/utils/sas9/extractUserLongNameSas9.ts @@ -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() +} diff --git a/src/utils/spec/extractUserLongNameSas9.spec.ts b/src/utils/spec/extractUserLongNameSas9.spec.ts new file mode 100644 index 0000000..851bf05 --- /dev/null +++ b/src/utils/spec/extractUserLongNameSas9.spec.ts @@ -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') + }) +}) diff --git a/src/utils/spec/getFormData.spec.ts b/src/utils/spec/getFormData.spec.ts new file mode 100644 index 0000000..1fd192c --- /dev/null +++ b/src/utils/spec/getFormData.spec.ts @@ -0,0 +1,20 @@ +import { getFormData } from '..' +import * as isNodeModule from '../isNode' +import * as NodeFormData from 'form-data' + +describe('getFormData', () => { + it('should return NodeFormData if environment is Node', () => { + jest.spyOn(isNodeModule, 'isNode').mockImplementation(() => true) + + expect(getFormData() instanceof NodeFormData).toEqual(true) + }) + + it('should return FormData if environment is not Node', () => { + const formDataMock = () => {} + ;(global as any).FormData = formDataMock + + jest.spyOn(isNodeModule, 'isNode').mockImplementation(() => false) + + expect(getFormData() instanceof FormData).toEqual(true) + }) +}) diff --git a/webpack.config.js b/webpack.config.js index ee0ac2e..c1d8b68 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -23,8 +23,16 @@ const optimization = { } const browserConfig = { - entry: './src/index.ts', - devtool: 'inline-source-map', + entry: { + index: './src/index.ts', + minified_sas9: './src/minified/sas9/index.ts' + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, 'build'), + libraryTarget: 'umd', + library: 'SASjs' + }, mode: 'production', optimization: optimization, module: { @@ -40,12 +48,6 @@ const browserConfig = { extensions: ['.ts', '.js'], fallback: { https: false, fs: false, readline: false } }, - output: { - filename: 'index.js', - path: path.resolve(__dirname, 'build'), - libraryTarget: 'umd', - library: 'SASjs' - }, plugins: [ ...defaultPlugins, new webpack.ProvidePlugin({ @@ -55,6 +57,18 @@ const browserConfig = { ] } +const browserConfigWithDevTool = { + ...browserConfig, + entry: './src/index.ts', + output: { + filename: 'index-dev.js', + path: path.resolve(__dirname, 'build'), + libraryTarget: 'umd', + library: 'SASjs' + }, + devtool: 'inline-source-map' +} + const browserConfigWithoutProcessPlugin = { entry: browserConfig.entry, devtool: browserConfig.devtool, @@ -72,8 +86,9 @@ const nodeConfig = { entry: './node/index.ts', output: { ...browserConfig.output, - path: path.resolve(__dirname, 'build', 'node') + path: path.resolve(__dirname, 'build', 'node'), + filename: 'index.js' } } -module.exports = [browserConfig, nodeConfig] +module.exports = [browserConfig, browserConfigWithDevTool, nodeConfig]